11 seo pages
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,343 +1,343 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import { FAQSection } from '@/components/aeo/FAQSection';
|
||||
import { MarketingPageTracker, TrackedCtaLink } from '@/components/marketing/MarketingAnalytics';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'QR Master Alternatives: Compare QR Code Platforms',
|
||||
},
|
||||
description:
|
||||
'Compare QR Master with QR-Code-Generator.com, Flowcode, Beaconstac / Uniqode, and Bitly. Find the right QR alternative for pricing, branding, analytics, and bulk creation.',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/alternatives',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'QR Master Alternatives: Compare QR Code Platforms',
|
||||
description:
|
||||
'Clean comparisons for the most common QR code platform alternatives, from pricing traps to branding limits and enterprise overkill.',
|
||||
url: 'https://www.qrmaster.net/alternatives',
|
||||
type: 'website',
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
twitter: {
|
||||
title: 'QR Master Alternatives: Compare QR Code Platforms',
|
||||
description:
|
||||
'Compare QR Master with Flowcode, Bitly, Beaconstac / Uniqode, and QR-Code-Generator.com.',
|
||||
},
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Alternatives', url: '/alternatives' },
|
||||
];
|
||||
|
||||
const pageSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: 'QR Code Platform Alternatives',
|
||||
url: 'https://www.qrmaster.net/alternatives',
|
||||
description:
|
||||
'Comparison pages for QR Master versus major QR code and adjacent link-management platforms.',
|
||||
};
|
||||
|
||||
const faqItems = [
|
||||
{
|
||||
question: 'What is the best QR code platform alternative?',
|
||||
answer:
|
||||
'The best alternative depends on the workflow. QR Master is strongest when you need dynamic QR codes, clean branding, bulk creation, and privacy-friendly analytics without enterprise pricing.',
|
||||
},
|
||||
{
|
||||
question: 'When should I choose QR Master over Bitly?',
|
||||
answer:
|
||||
'Choose QR Master when QR codes are the primary workflow. Bitly is useful for short links, but its QR code limits and link-first dashboard become restrictive for campaigns with many QR codes.',
|
||||
},
|
||||
{
|
||||
question: 'When should I choose QR Master over Flowcode?',
|
||||
answer:
|
||||
'Choose QR Master when you want a direct scan experience without third-party branding or branded interstitial pages. QR Master keeps the scan flow focused on your destination.',
|
||||
},
|
||||
{
|
||||
question: 'Is Beaconstac / Uniqode better than QR Master?',
|
||||
answer:
|
||||
'Beaconstac / Uniqode can make sense for enterprise teams that need a broad QR platform. QR Master is usually a better fit for smaller teams that want dynamic QR codes, analytics, and bulk creation at a simpler price point.',
|
||||
},
|
||||
];
|
||||
|
||||
const faqSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
'@id': 'https://www.qrmaster.net/alternatives#faq',
|
||||
mainEntity: faqItems.map((item) => ({
|
||||
'@type': 'Question',
|
||||
name: item.question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: item.answer,
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
||||
const alternativePages = [
|
||||
{
|
||||
href: '/alternatives/qr-code-generator',
|
||||
eyebrow: 'Trial trap',
|
||||
title: 'QR-Code-Generator.com Alternative',
|
||||
description:
|
||||
'For teams burned by 14-day dynamic QR trials and forced annual upgrades after print is already live.',
|
||||
},
|
||||
{
|
||||
href: '/alternatives/flowcode',
|
||||
eyebrow: 'Brand control',
|
||||
title: 'Flowcode Alternative',
|
||||
description:
|
||||
'For brands and agencies that need clean QR codes without third-party logos or branded scan interstitials.',
|
||||
},
|
||||
{
|
||||
href: '/alternatives/beaconstac',
|
||||
eyebrow: 'SMB fit',
|
||||
title: 'Beaconstac / Uniqode Alternative',
|
||||
description:
|
||||
'For smaller teams that want reliable analytics and bulk QR creation without paying enterprise-platform pricing.',
|
||||
},
|
||||
{
|
||||
href: '/alternatives/bitly',
|
||||
eyebrow: 'QR-first workflow',
|
||||
title: 'Bitly Alternative',
|
||||
description:
|
||||
'For campaigns where QR codes are the main workflow, not a side feature hidden inside a link shortener.',
|
||||
},
|
||||
];
|
||||
|
||||
const comparisonRows = [
|
||||
{
|
||||
platform: 'Bitly',
|
||||
href: '/alternatives/bitly',
|
||||
price: 'Link-first paid plans',
|
||||
freePlan: 'Limited QR use',
|
||||
dynamicCodes: 'QR feature inside link management',
|
||||
analytics: 'Link analytics first',
|
||||
gdpr: 'Review DPA for EU use',
|
||||
bulk: 'No QR-first bulk workflow',
|
||||
branding: 'Bitly-branded link context',
|
||||
qrMasterAngle: 'QR-first workflow with 50 dynamic codes on Pro',
|
||||
},
|
||||
{
|
||||
platform: 'Flowcode',
|
||||
href: '/alternatives/flowcode',
|
||||
price: 'Brand and team tiers',
|
||||
freePlan: 'Limited free path',
|
||||
dynamicCodes: 'Strong QR design workflow',
|
||||
analytics: 'Paid campaign analytics',
|
||||
gdpr: 'US vendor; review data terms',
|
||||
bulk: 'Best for larger plans',
|
||||
branding: 'Design-forward, possible platform branding',
|
||||
qrMasterAngle: 'Clean redirects and brand control without a branded scan page',
|
||||
},
|
||||
{
|
||||
platform: 'Beaconstac / Uniqode',
|
||||
href: '/alternatives/beaconstac',
|
||||
price: 'Enterprise-oriented tiers',
|
||||
freePlan: 'Trial or limited evaluation',
|
||||
dynamicCodes: 'Advanced enterprise QR platform',
|
||||
analytics: 'Advanced paid analytics',
|
||||
gdpr: 'DPA and configuration review needed',
|
||||
bulk: 'Strong, often enterprise-oriented',
|
||||
branding: 'Enterprise controls',
|
||||
qrMasterAngle: 'Simpler dynamic QR, analytics, and bulk creation for SMB teams',
|
||||
},
|
||||
{
|
||||
platform: 'QR-Code-Generator.com',
|
||||
href: '/alternatives/qr-code-generator',
|
||||
price: 'Paid dynamic QR after trial',
|
||||
freePlan: 'Static QR path',
|
||||
dynamicCodes: 'Dynamic QR behind paid upgrade',
|
||||
analytics: 'Paid analytics',
|
||||
gdpr: 'Review vendor terms',
|
||||
bulk: 'Paid workflow',
|
||||
branding: 'Template-driven branding',
|
||||
qrMasterAngle: 'Free dynamic codes and transparent paid tiers',
|
||||
},
|
||||
];
|
||||
|
||||
export default function AlternativesHubPage() {
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[pageSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<MarketingPageTracker pageType="commercial" cluster="competitor" />
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#F8F7F4', color: '#18181B' }}>
|
||||
<section className="border-b bg-white" style={{ borderColor: '#E4E0D9' }}>
|
||||
<div className="container mx-auto max-w-7xl px-4 pb-20 pt-10 sm:px-6 lg:px-8">
|
||||
<div className="mb-10">
|
||||
<Breadcrumbs items={breadcrumbItems} />
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl">
|
||||
<div
|
||||
className="mb-6 inline-block rounded px-3 py-1.5 text-xs font-semibold uppercase tracking-widest"
|
||||
style={{ backgroundColor: '#DCFCE7', color: '#166534', letterSpacing: '0.12em' }}
|
||||
>
|
||||
QR Master alternatives
|
||||
</div>
|
||||
<h1
|
||||
className="mb-6 text-4xl font-extrabold leading-[1.08] tracking-tight sm:text-5xl"
|
||||
style={{ color: '#111110' }}
|
||||
>
|
||||
Clean comparisons for the QR platforms people actually switch from.
|
||||
</h1>
|
||||
<p className="mb-8 text-lg leading-relaxed" style={{ color: '#52525B' }}>
|
||||
Some tools hide trial limits. Some force their brand into your scan flow. Some are simply priced for
|
||||
enterprise teams. These pages break down where each platform fits, where it does not, and when QR
|
||||
Master is the better tradeoff.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<TrackedCtaLink
|
||||
href="/signup"
|
||||
ctaLabel="Start Free"
|
||||
ctaLocation="hero_primary"
|
||||
pageType="commercial"
|
||||
cluster="competitor"
|
||||
>
|
||||
<Button size="lg" className="h-13 w-full px-8 text-base sm:w-auto">
|
||||
Start Free
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
<TrackedCtaLink
|
||||
href="/pricing"
|
||||
ctaLabel="See Pricing"
|
||||
ctaLocation="hero_secondary"
|
||||
pageType="commercial"
|
||||
cluster="competitor"
|
||||
>
|
||||
<Button variant="outline" size="lg" className="h-13 w-full px-8 text-base sm:w-auto">
|
||||
See Pricing
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-24" style={{ backgroundColor: '#F8F7F4' }}>
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 max-w-3xl">
|
||||
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl" style={{ color: '#111110' }}>
|
||||
Explore Alternatives
|
||||
</h2>
|
||||
<p className="text-lg" style={{ color: '#71717A' }}>
|
||||
Pick the comparison that matches the problem you are trying to solve.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2 xl:grid-cols-4">
|
||||
{alternativePages.map((item) => (
|
||||
<Link key={item.href} href={item.href} className="group block">
|
||||
<Card
|
||||
className="h-full rounded-xl border bg-white p-7 shadow-none transition-transform duration-150 group-hover:-translate-y-1"
|
||||
style={{ borderColor: '#E4E0D9' }}
|
||||
>
|
||||
<p
|
||||
className="mb-4 text-xs font-semibold uppercase tracking-widest"
|
||||
style={{ color: '#71717A', letterSpacing: '0.12em' }}
|
||||
>
|
||||
{item.eyebrow}
|
||||
</p>
|
||||
<h3 className="mb-3 text-xl font-bold" style={{ color: '#18181B' }}>
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed" style={{ color: '#52525B' }}>
|
||||
{item.description}
|
||||
</p>
|
||||
<p className="mt-6 text-sm font-semibold" style={{ color: '#166534' }}>
|
||||
Open comparison
|
||||
</p>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-y bg-white py-24" style={{ borderColor: '#E4E0D9' }}>
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 max-w-3xl">
|
||||
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl" style={{ color: '#111110' }}>
|
||||
QR Master vs Bitly, Flowcode, Beaconstac and other QR platforms
|
||||
</h2>
|
||||
<p className="text-lg" style={{ color: '#71717A' }}>
|
||||
Use this table to pick the comparison page that matches your search intent: pricing, branding, bulk
|
||||
creation, analytics, or migration risk.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto rounded-xl border" style={{ borderColor: '#E4E0D9' }}>
|
||||
<table className="w-full min-w-[980px] text-sm">
|
||||
<thead style={{ backgroundColor: '#F8F7F4' }}>
|
||||
<tr>
|
||||
{[
|
||||
'Platform',
|
||||
'Price',
|
||||
'Free plan',
|
||||
'Dynamic codes',
|
||||
'Analytics',
|
||||
'GDPR / EU fit',
|
||||
'Bulk',
|
||||
'Branding',
|
||||
'Compare',
|
||||
].map((heading) => (
|
||||
<th
|
||||
key={heading}
|
||||
className="p-4 text-left text-xs font-semibold uppercase tracking-wider"
|
||||
style={{ color: '#71717A' }}
|
||||
>
|
||||
{heading}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{comparisonRows.map((row, index) => (
|
||||
<tr
|
||||
key={row.platform}
|
||||
style={{
|
||||
borderTop: '1px solid #E4E0D9',
|
||||
backgroundColor: index % 2 === 0 ? '#FFFFFF' : '#FAFAF8',
|
||||
}}
|
||||
>
|
||||
<td className="p-4 font-semibold" style={{ color: '#18181B' }}>{row.platform}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.price}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.freePlan}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.dynamicCodes}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.analytics}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.gdpr}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.bulk}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.branding}</td>
|
||||
<td className="p-4">
|
||||
<Link href={row.href} className="font-semibold" style={{ color: '#166534' }}>
|
||||
QR Master vs {row.platform}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-24" style={{ backgroundColor: '#F8F7F4' }}>
|
||||
<div className="container mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<FAQSection items={faqItems} title="Common questions about QR platform alternatives" />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import { FAQSection } from '@/components/aeo/FAQSection';
|
||||
import { MarketingPageTracker, TrackedCtaLink } from '@/components/marketing/MarketingAnalytics';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'QR Master Alternatives: Compare QR Code Platforms',
|
||||
},
|
||||
description:
|
||||
'Compare QR Master with QR-Code-Generator.com, Flowcode, Beaconstac / Uniqode, and Bitly. Find the right QR alternative for pricing, branding, analytics, and bulk creation.',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/alternatives',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'QR Master Alternatives: Compare QR Code Platforms',
|
||||
description:
|
||||
'Clean comparisons for the most common QR code platform alternatives, from pricing traps to branding limits and enterprise overkill.',
|
||||
url: 'https://www.qrmaster.net/alternatives',
|
||||
type: 'website',
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
twitter: {
|
||||
title: 'QR Master Alternatives: Compare QR Code Platforms',
|
||||
description:
|
||||
'Compare QR Master with Flowcode, Bitly, Beaconstac / Uniqode, and QR-Code-Generator.com.',
|
||||
},
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Alternatives', url: '/alternatives' },
|
||||
];
|
||||
|
||||
const pageSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: 'QR Code Platform Alternatives',
|
||||
url: 'https://www.qrmaster.net/alternatives',
|
||||
description:
|
||||
'Comparison pages for QR Master versus major QR code and adjacent link-management platforms.',
|
||||
};
|
||||
|
||||
const faqItems = [
|
||||
{
|
||||
question: 'What is the best QR code platform alternative?',
|
||||
answer:
|
||||
'The best alternative depends on the workflow. QR Master is strongest when you need dynamic QR codes, clean branding, bulk creation, and privacy-friendly analytics without enterprise pricing.',
|
||||
},
|
||||
{
|
||||
question: 'When should I choose QR Master over Bitly?',
|
||||
answer:
|
||||
'Choose QR Master when QR codes are the primary workflow. Bitly is useful for short links, but its QR code limits and link-first dashboard become restrictive for campaigns with many QR codes.',
|
||||
},
|
||||
{
|
||||
question: 'When should I choose QR Master over Flowcode?',
|
||||
answer:
|
||||
'Choose QR Master when you want a direct scan experience without third-party branding or branded interstitial pages. QR Master keeps the scan flow focused on your destination.',
|
||||
},
|
||||
{
|
||||
question: 'Is Beaconstac / Uniqode better than QR Master?',
|
||||
answer:
|
||||
'Beaconstac / Uniqode can make sense for enterprise teams that need a broad QR platform. QR Master is usually a better fit for smaller teams that want dynamic QR codes, analytics, and bulk creation at a simpler price point.',
|
||||
},
|
||||
];
|
||||
|
||||
const faqSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
'@id': 'https://www.qrmaster.net/alternatives#faq',
|
||||
mainEntity: faqItems.map((item) => ({
|
||||
'@type': 'Question',
|
||||
name: item.question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: item.answer,
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
||||
const alternativePages = [
|
||||
{
|
||||
href: '/alternatives/qr-code-generator',
|
||||
eyebrow: 'Trial trap',
|
||||
title: 'QR-Code-Generator.com Alternative',
|
||||
description:
|
||||
'For teams burned by 14-day dynamic QR trials and forced annual upgrades after print is already live.',
|
||||
},
|
||||
{
|
||||
href: '/alternatives/flowcode',
|
||||
eyebrow: 'Brand control',
|
||||
title: 'Flowcode Alternative',
|
||||
description:
|
||||
'For brands and agencies that need clean QR codes without third-party logos or branded scan interstitials.',
|
||||
},
|
||||
{
|
||||
href: '/alternatives/beaconstac',
|
||||
eyebrow: 'SMB fit',
|
||||
title: 'Beaconstac / Uniqode Alternative',
|
||||
description:
|
||||
'For smaller teams that want reliable analytics and bulk QR creation without paying enterprise-platform pricing.',
|
||||
},
|
||||
{
|
||||
href: '/alternatives/bitly',
|
||||
eyebrow: 'QR-first workflow',
|
||||
title: 'Bitly Alternative',
|
||||
description:
|
||||
'For campaigns where QR codes are the main workflow, not a side feature hidden inside a link shortener.',
|
||||
},
|
||||
];
|
||||
|
||||
const comparisonRows = [
|
||||
{
|
||||
platform: 'Bitly',
|
||||
href: '/alternatives/bitly',
|
||||
price: 'Link-first paid plans',
|
||||
freePlan: 'Limited QR use',
|
||||
dynamicCodes: 'QR feature inside link management',
|
||||
analytics: 'Link analytics first',
|
||||
gdpr: 'Review DPA for EU use',
|
||||
bulk: 'No QR-first bulk workflow',
|
||||
branding: 'Bitly-branded link context',
|
||||
qrMasterAngle: 'QR-first workflow with 50 dynamic codes on Pro',
|
||||
},
|
||||
{
|
||||
platform: 'Flowcode',
|
||||
href: '/alternatives/flowcode',
|
||||
price: 'Brand and team tiers',
|
||||
freePlan: 'Limited free path',
|
||||
dynamicCodes: 'Strong QR design workflow',
|
||||
analytics: 'Paid campaign analytics',
|
||||
gdpr: 'US vendor; review data terms',
|
||||
bulk: 'Best for larger plans',
|
||||
branding: 'Design-forward, possible platform branding',
|
||||
qrMasterAngle: 'Clean redirects and brand control without a branded scan page',
|
||||
},
|
||||
{
|
||||
platform: 'Beaconstac / Uniqode',
|
||||
href: '/alternatives/beaconstac',
|
||||
price: 'Enterprise-oriented tiers',
|
||||
freePlan: 'Trial or limited evaluation',
|
||||
dynamicCodes: 'Advanced enterprise QR platform',
|
||||
analytics: 'Advanced paid analytics',
|
||||
gdpr: 'DPA and configuration review needed',
|
||||
bulk: 'Strong, often enterprise-oriented',
|
||||
branding: 'Enterprise controls',
|
||||
qrMasterAngle: 'Simpler dynamic QR, analytics, and bulk creation for SMB teams',
|
||||
},
|
||||
{
|
||||
platform: 'QR-Code-Generator.com',
|
||||
href: '/alternatives/qr-code-generator',
|
||||
price: 'Paid dynamic QR after trial',
|
||||
freePlan: 'Static QR path',
|
||||
dynamicCodes: 'Dynamic QR behind paid upgrade',
|
||||
analytics: 'Paid analytics',
|
||||
gdpr: 'Review vendor terms',
|
||||
bulk: 'Paid workflow',
|
||||
branding: 'Template-driven branding',
|
||||
qrMasterAngle: 'Free dynamic codes and transparent paid tiers',
|
||||
},
|
||||
];
|
||||
|
||||
export default function AlternativesHubPage() {
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[pageSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<MarketingPageTracker pageType="commercial" cluster="competitor" />
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#F8F7F4', color: '#18181B' }}>
|
||||
<section className="border-b bg-white" style={{ borderColor: '#E4E0D9' }}>
|
||||
<div className="container mx-auto max-w-7xl px-4 pb-20 pt-10 sm:px-6 lg:px-8">
|
||||
<div className="mb-10">
|
||||
<Breadcrumbs items={breadcrumbItems} />
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl">
|
||||
<div
|
||||
className="mb-6 inline-block rounded px-3 py-1.5 text-xs font-semibold uppercase tracking-widest"
|
||||
style={{ backgroundColor: '#DCFCE7', color: '#166534', letterSpacing: '0.12em' }}
|
||||
>
|
||||
QR Master alternatives
|
||||
</div>
|
||||
<h1
|
||||
className="mb-6 text-4xl font-extrabold leading-[1.08] tracking-tight sm:text-5xl"
|
||||
style={{ color: '#111110' }}
|
||||
>
|
||||
Clean comparisons for the QR platforms people actually switch from.
|
||||
</h1>
|
||||
<p className="mb-8 text-lg leading-relaxed" style={{ color: '#52525B' }}>
|
||||
Some tools hide trial limits. Some force their brand into your scan flow. Some are simply priced for
|
||||
enterprise teams. These pages break down where each platform fits, where it does not, and when QR
|
||||
Master is the better tradeoff.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<TrackedCtaLink
|
||||
href="/signup"
|
||||
ctaLabel="Start Free"
|
||||
ctaLocation="hero_primary"
|
||||
pageType="commercial"
|
||||
cluster="competitor"
|
||||
>
|
||||
<Button size="lg" className="h-13 w-full px-8 text-base sm:w-auto">
|
||||
Start Free
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
<TrackedCtaLink
|
||||
href="/pricing"
|
||||
ctaLabel="See Pricing"
|
||||
ctaLocation="hero_secondary"
|
||||
pageType="commercial"
|
||||
cluster="competitor"
|
||||
>
|
||||
<Button variant="outline" size="lg" className="h-13 w-full px-8 text-base sm:w-auto">
|
||||
See Pricing
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-24" style={{ backgroundColor: '#F8F7F4' }}>
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 max-w-3xl">
|
||||
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl" style={{ color: '#111110' }}>
|
||||
Explore Alternatives
|
||||
</h2>
|
||||
<p className="text-lg" style={{ color: '#71717A' }}>
|
||||
Pick the comparison that matches the problem you are trying to solve.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2 xl:grid-cols-4">
|
||||
{alternativePages.map((item) => (
|
||||
<Link key={item.href} href={item.href} className="group block">
|
||||
<Card
|
||||
className="h-full rounded-xl border bg-white p-7 shadow-none transition-transform duration-150 group-hover:-translate-y-1"
|
||||
style={{ borderColor: '#E4E0D9' }}
|
||||
>
|
||||
<p
|
||||
className="mb-4 text-xs font-semibold uppercase tracking-widest"
|
||||
style={{ color: '#71717A', letterSpacing: '0.12em' }}
|
||||
>
|
||||
{item.eyebrow}
|
||||
</p>
|
||||
<h3 className="mb-3 text-xl font-bold" style={{ color: '#18181B' }}>
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed" style={{ color: '#52525B' }}>
|
||||
{item.description}
|
||||
</p>
|
||||
<p className="mt-6 text-sm font-semibold" style={{ color: '#166534' }}>
|
||||
Open comparison
|
||||
</p>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-y bg-white py-24" style={{ borderColor: '#E4E0D9' }}>
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 max-w-3xl">
|
||||
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl" style={{ color: '#111110' }}>
|
||||
QR Master vs Bitly, Flowcode, Beaconstac and other QR platforms
|
||||
</h2>
|
||||
<p className="text-lg" style={{ color: '#71717A' }}>
|
||||
Use this table to pick the comparison page that matches your search intent: pricing, branding, bulk
|
||||
creation, analytics, or migration risk.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto rounded-xl border" style={{ borderColor: '#E4E0D9' }}>
|
||||
<table className="w-full min-w-[980px] text-sm">
|
||||
<thead style={{ backgroundColor: '#F8F7F4' }}>
|
||||
<tr>
|
||||
{[
|
||||
'Platform',
|
||||
'Price',
|
||||
'Free plan',
|
||||
'Dynamic codes',
|
||||
'Analytics',
|
||||
'GDPR / EU fit',
|
||||
'Bulk',
|
||||
'Branding',
|
||||
'Compare',
|
||||
].map((heading) => (
|
||||
<th
|
||||
key={heading}
|
||||
className="p-4 text-left text-xs font-semibold uppercase tracking-wider"
|
||||
style={{ color: '#71717A' }}
|
||||
>
|
||||
{heading}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{comparisonRows.map((row, index) => (
|
||||
<tr
|
||||
key={row.platform}
|
||||
style={{
|
||||
borderTop: '1px solid #E4E0D9',
|
||||
backgroundColor: index % 2 === 0 ? '#FFFFFF' : '#FAFAF8',
|
||||
}}
|
||||
>
|
||||
<td className="p-4 font-semibold" style={{ color: '#18181B' }}>{row.platform}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.price}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.freePlan}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.dynamicCodes}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.analytics}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.gdpr}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.bulk}</td>
|
||||
<td className="p-4" style={{ color: '#52525B' }}>{row.branding}</td>
|
||||
<td className="p-4">
|
||||
<Link href={row.href} className="font-semibold" style={{ color: '#166534' }}>
|
||||
QR Master vs {row.platform}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-24" style={{ backgroundColor: '#F8F7F4' }}>
|
||||
<div className="container mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<FAQSection items={faqItems} title="Common questions about QR platform alternatives" />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,165 +1,165 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { getPublishedPostBySlug, getAuthorBySlug, getRelatedPosts, getPublishedPosts } from "@/lib/content";
|
||||
import { AnswerBox } from "@/components/aeo/AnswerBox";
|
||||
import { StepList } from "@/components/aeo/StepList";
|
||||
import { FAQSection } from "@/components/aeo/FAQSection";
|
||||
import { SourcesList } from "@/components/aeo/SourcesList";
|
||||
import { AuthorCard } from "@/components/author/AuthorCard";
|
||||
import { RelatedPosts } from "@/components/blog/RelatedPosts";
|
||||
import { blogPostingSchema, howToSchema, faqPageSchema, breadcrumbSchema } from "@/lib/schema";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
function formatIsoDate(date: string) {
|
||||
return new Date(date).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||
const post = getPublishedPostBySlug(params.slug);
|
||||
if (!post) return {};
|
||||
|
||||
const ogImage = post.heroImage ? `https://www.qrmaster.net${post.heroImage}` : undefined;
|
||||
|
||||
return {
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
alternates: {
|
||||
canonical: `https://www.qrmaster.net/blog/${post.slug}`,
|
||||
},
|
||||
openGraph: {
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
type: "article",
|
||||
publishedTime: post.datePublished,
|
||||
modifiedTime: post.dateModified || post.datePublished,
|
||||
authors: ["https://www.qrmaster.net"],
|
||||
tags: post.keywords,
|
||||
images: ogImage ? [{ url: ogImage, alt: post.imageAlt || post.title }] : undefined,
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
images: ogImage ? [ogImage] : undefined,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
// Only generate static params for published posts
|
||||
return getPublishedPosts().map((post) => ({
|
||||
slug: post.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export default function BlogPostPage({ params }: { params: { slug: string } }) {
|
||||
const post = getPublishedPostBySlug(params.slug);
|
||||
if (!post) return notFound(); // STRICT 404 GATE
|
||||
|
||||
const author = getAuthorBySlug(post.authorSlug);
|
||||
const related = getRelatedPosts(post);
|
||||
|
||||
const jsonLd = blogPostingSchema(post, author);
|
||||
const howtoLd = post.keySteps?.length ? howToSchema(post, author) : null;
|
||||
const faqLd = post.faq ? faqPageSchema(post.faq) : null;
|
||||
const visibleUpdated = post.updatedAt || post.dateModified;
|
||||
const showUpdated = Boolean(visibleUpdated && visibleUpdated !== post.datePublished);
|
||||
|
||||
// Generate breadcrumb schema: Home -> Learn -> Pillar -> Post
|
||||
const pillarName = post.pillar ? post.pillar.charAt(0).toUpperCase() + post.pillar.slice(1) : "Blog";
|
||||
const breadcrumbLd = breadcrumbSchema([
|
||||
{ name: "Home", url: "/" },
|
||||
{ name: "Learn", url: "/learn" },
|
||||
{ name: pillarName, url: `/learn/${post.pillar}` },
|
||||
{ name: post.title, url: `/blog/${post.slug}` },
|
||||
]);
|
||||
|
||||
return (
|
||||
<main className="container mx-auto max-w-4xl py-12 px-4">
|
||||
<script type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
||||
{howtoLd && (
|
||||
<script type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(howtoLd) }} />
|
||||
)}
|
||||
{faqLd && (
|
||||
<script type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqLd) }} />
|
||||
)}
|
||||
<script type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbLd) }} />
|
||||
|
||||
<header className="space-y-6 text-center max-w-3xl mx-auto mb-10">
|
||||
<div className="flex justify-center gap-2 text-sm text-gray-500 font-medium">
|
||||
<Link href="/learn" className="hover:text-blue-600">Hub</Link>
|
||||
<span>/</span>
|
||||
<Link href={`/learn/${post.pillar}`} className="hover:text-blue-600 capitalize">{post.pillar}</Link>
|
||||
</div>
|
||||
<h1 className="text-3xl md:text-5xl font-extrabold text-gray-900 tracking-tight leading-tight">{post.title}</h1>
|
||||
<p className="text-xl text-gray-600 relaxed">{post.description}</p>
|
||||
|
||||
{author && (
|
||||
<div className="flex items-center justify-center gap-3 pt-4">
|
||||
{author.image ? (
|
||||
<Image src={author.image} alt={author.name} width={40} height={40} className="rounded-full" />
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-bold">
|
||||
{author.name.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-left text-sm">
|
||||
<div className="font-bold text-gray-900">{author.name}</div>
|
||||
<div className="text-gray-500 text-xs mt-0.5">
|
||||
Published {post.date}
|
||||
{showUpdated && visibleUpdated && (
|
||||
<> <span className="mx-1">|</span> Updated {formatIsoDate(visibleUpdated)}</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{post.heroImage && (
|
||||
<div className="relative aspect-video w-full rounded-2xl overflow-hidden shadow-lg mb-10">
|
||||
<Image
|
||||
src={post.heroImage}
|
||||
alt={post.imageAlt || post.title}
|
||||
fill
|
||||
unoptimized={post.heroImage.endsWith(".svg")}
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* AEO BLOCK: ANSWER */}
|
||||
<div className="max-w-3xl mx-auto">
|
||||
{post.quickAnswer && <AnswerBox html={post.quickAnswer} />}
|
||||
|
||||
{/* AEO BLOCK: STEPS (If defined separate from body) */}
|
||||
{!!post.keySteps?.length && <StepList steps={post.keySteps} title="Step-by-Step Guide" />}
|
||||
|
||||
{/* MAIN CONTENT */}
|
||||
<article className="prose prose-lg prose-blue max-w-none text-gray-800" dangerouslySetInnerHTML={{ __html: post.content }} />
|
||||
|
||||
{/* AEO BLOCK: FAQ */}
|
||||
{!!post.faq?.length && <div className="mt-12"><FAQSection items={post.faq} /></div>}
|
||||
|
||||
{/* AEO BLOCK: SOURCES */}
|
||||
{!!post.sources?.length && <SourcesList sources={post.sources} />}
|
||||
|
||||
<div className="border-t border-gray-100 my-12"></div>
|
||||
|
||||
{author && <AuthorCard author={author} />}
|
||||
|
||||
<div className="mt-12">
|
||||
<RelatedPosts posts={related} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
import { notFound } from "next/navigation";
|
||||
import { getPublishedPostBySlug, getAuthorBySlug, getRelatedPosts, getPublishedPosts } from "@/lib/content";
|
||||
import { AnswerBox } from "@/components/aeo/AnswerBox";
|
||||
import { StepList } from "@/components/aeo/StepList";
|
||||
import { FAQSection } from "@/components/aeo/FAQSection";
|
||||
import { SourcesList } from "@/components/aeo/SourcesList";
|
||||
import { AuthorCard } from "@/components/author/AuthorCard";
|
||||
import { RelatedPosts } from "@/components/blog/RelatedPosts";
|
||||
import { blogPostingSchema, howToSchema, faqPageSchema, breadcrumbSchema } from "@/lib/schema";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
function formatIsoDate(date: string) {
|
||||
return new Date(date).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||
const post = getPublishedPostBySlug(params.slug);
|
||||
if (!post) return {};
|
||||
|
||||
const ogImage = post.heroImage ? `https://www.qrmaster.net${post.heroImage}` : undefined;
|
||||
|
||||
return {
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
alternates: {
|
||||
canonical: `https://www.qrmaster.net/blog/${post.slug}`,
|
||||
},
|
||||
openGraph: {
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
type: "article",
|
||||
publishedTime: post.datePublished,
|
||||
modifiedTime: post.dateModified || post.datePublished,
|
||||
authors: ["https://www.qrmaster.net"],
|
||||
tags: post.keywords,
|
||||
images: ogImage ? [{ url: ogImage, alt: post.imageAlt || post.title }] : undefined,
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
images: ogImage ? [ogImage] : undefined,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
// Only generate static params for published posts
|
||||
return getPublishedPosts().map((post) => ({
|
||||
slug: post.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export default function BlogPostPage({ params }: { params: { slug: string } }) {
|
||||
const post = getPublishedPostBySlug(params.slug);
|
||||
if (!post) return notFound(); // STRICT 404 GATE
|
||||
|
||||
const author = getAuthorBySlug(post.authorSlug);
|
||||
const related = getRelatedPosts(post);
|
||||
|
||||
const jsonLd = blogPostingSchema(post, author);
|
||||
const howtoLd = post.keySteps?.length ? howToSchema(post, author) : null;
|
||||
const faqLd = post.faq ? faqPageSchema(post.faq) : null;
|
||||
const visibleUpdated = post.updatedAt || post.dateModified;
|
||||
const showUpdated = Boolean(visibleUpdated && visibleUpdated !== post.datePublished);
|
||||
|
||||
// Generate breadcrumb schema: Home -> Learn -> Pillar -> Post
|
||||
const pillarName = post.pillar ? post.pillar.charAt(0).toUpperCase() + post.pillar.slice(1) : "Blog";
|
||||
const breadcrumbLd = breadcrumbSchema([
|
||||
{ name: "Home", url: "/" },
|
||||
{ name: "Learn", url: "/learn" },
|
||||
{ name: pillarName, url: `/learn/${post.pillar}` },
|
||||
{ name: post.title, url: `/blog/${post.slug}` },
|
||||
]);
|
||||
|
||||
return (
|
||||
<main className="container mx-auto max-w-4xl py-12 px-4">
|
||||
<script type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
||||
{howtoLd && (
|
||||
<script type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(howtoLd) }} />
|
||||
)}
|
||||
{faqLd && (
|
||||
<script type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqLd) }} />
|
||||
)}
|
||||
<script type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbLd) }} />
|
||||
|
||||
<header className="space-y-6 text-center max-w-3xl mx-auto mb-10">
|
||||
<div className="flex justify-center gap-2 text-sm text-gray-500 font-medium">
|
||||
<Link href="/learn" className="hover:text-blue-600">Hub</Link>
|
||||
<span>/</span>
|
||||
<Link href={`/learn/${post.pillar}`} className="hover:text-blue-600 capitalize">{post.pillar}</Link>
|
||||
</div>
|
||||
<h1 className="text-3xl md:text-5xl font-extrabold text-gray-900 tracking-tight leading-tight">{post.title}</h1>
|
||||
<p className="text-xl text-gray-600 relaxed">{post.description}</p>
|
||||
|
||||
{author && (
|
||||
<div className="flex items-center justify-center gap-3 pt-4">
|
||||
{author.image ? (
|
||||
<Image src={author.image} alt={author.name} width={40} height={40} className="rounded-full" />
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-bold">
|
||||
{author.name.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-left text-sm">
|
||||
<div className="font-bold text-gray-900">{author.name}</div>
|
||||
<div className="text-gray-500 text-xs mt-0.5">
|
||||
Published {post.date}
|
||||
{showUpdated && visibleUpdated && (
|
||||
<> <span className="mx-1">|</span> Updated {formatIsoDate(visibleUpdated)}</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{post.heroImage && (
|
||||
<div className="relative aspect-video w-full rounded-2xl overflow-hidden shadow-lg mb-10">
|
||||
<Image
|
||||
src={post.heroImage}
|
||||
alt={post.imageAlt || post.title}
|
||||
fill
|
||||
unoptimized={post.heroImage.endsWith(".svg")}
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* AEO BLOCK: ANSWER */}
|
||||
<div className="max-w-3xl mx-auto">
|
||||
{post.quickAnswer && <AnswerBox html={post.quickAnswer} />}
|
||||
|
||||
{/* AEO BLOCK: STEPS (If defined separate from body) */}
|
||||
{!!post.keySteps?.length && <StepList steps={post.keySteps} title="Step-by-Step Guide" />}
|
||||
|
||||
{/* MAIN CONTENT */}
|
||||
<article className="prose prose-lg prose-blue max-w-none text-gray-800" dangerouslySetInnerHTML={{ __html: post.content }} />
|
||||
|
||||
{/* AEO BLOCK: FAQ */}
|
||||
{!!post.faq?.length && <div className="mt-12"><FAQSection items={post.faq} /></div>}
|
||||
|
||||
{/* AEO BLOCK: SOURCES */}
|
||||
{!!post.sources?.length && <SourcesList sources={post.sources} />}
|
||||
|
||||
<div className="border-t border-gray-100 my-12"></div>
|
||||
|
||||
{author && <AuthorCard author={author} />}
|
||||
|
||||
<div className="mt-12">
|
||||
<RelatedPosts posts={related} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,134 +1,134 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import {
|
||||
websiteSchema,
|
||||
softwareApplicationSchema,
|
||||
} from '@/lib/schema';
|
||||
import { getAggregateRating } from '@/lib/testimonial-data';
|
||||
import HomePageClient from '@/components/marketing/HomePageClient';
|
||||
|
||||
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 description = truncateAtWord(
|
||||
'QR Master is a free dynamic QR code generator with tracking, editable destinations, custom branding, and bulk QR creation. Create static QR codes without signup.',
|
||||
160
|
||||
);
|
||||
const brandTitle = 'QR Master - Free Dynamic QR Code Generator with Tracking';
|
||||
|
||||
return {
|
||||
title: brandTitle,
|
||||
description,
|
||||
keywords: [
|
||||
'qr generator',
|
||||
'free qr code generator',
|
||||
'custom qr code generator',
|
||||
'qr code maker',
|
||||
'online qr code generator',
|
||||
'dynamic qr code',
|
||||
'qr code with logo',
|
||||
'barcode generator',
|
||||
'free barcode generator',
|
||||
'qr master',
|
||||
'qrmaster',
|
||||
'qr code master',
|
||||
],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/',
|
||||
en: 'https://www.qrmaster.net/',
|
||||
de: 'https://www.qrmaster.net/qr-code-erstellen',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: brandTitle,
|
||||
description,
|
||||
url: 'https://www.qrmaster.net/',
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: 'https://www.qrmaster.net/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'QR Master - Dynamic QR Code Generator and Analytics Platform',
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
title: brandTitle,
|
||||
description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function HomePage() {
|
||||
const aggregateRating = getAggregateRating();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd
|
||||
data={[
|
||||
websiteSchema(),
|
||||
softwareApplicationSchema(aggregateRating),
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Server-rendered SEO content for crawlers */}
|
||||
<div className="sr-only" aria-hidden="false">
|
||||
<p>
|
||||
Create professional QR codes for your business with QR Master. Our
|
||||
dynamic QR code generator lets you create trackable QR codes, edit
|
||||
destinations anytime, and view detailed analytics. Perfect for
|
||||
restaurants, retail, events, and marketing campaigns.
|
||||
</p>
|
||||
<p>
|
||||
Features include: Dynamic QR codes with real-time tracking, bulk QR
|
||||
code generation from Excel/CSV, custom branding with colors and logos,
|
||||
advanced scan analytics showing device types and locations, vCard QR
|
||||
codes for digital business cards, restaurant menu QR codes, and a free{' '}
|
||||
<a href="/tools/barcode-generator">barcode generator</a> for EAN-13,
|
||||
UPC-A, and Code 128 barcodes.
|
||||
</p>
|
||||
<p>
|
||||
Popular QR Master workflows include the{' '}
|
||||
<a href="/dynamic-qr-code-generator">
|
||||
free dynamic QR code generator
|
||||
</a>
|
||||
, <a href="/qr-code-tracking">QR code tracking</a>, and the{' '}
|
||||
<a href="/custom-qr-code-generator">custom QR code generator</a> for
|
||||
branded print campaigns.
|
||||
</p>
|
||||
<p>
|
||||
Start free with 3 active dynamic QR codes and unlimited static codes.
|
||||
Upgrade to Pro for 50 codes with advanced analytics, or Business for
|
||||
500 codes with bulk creation and priority support.
|
||||
</p>
|
||||
<p>
|
||||
Frequently used QR Master tools and industry workflows include the{' '}
|
||||
<a href="/tools/teams-qr-code">Teams QR code generator</a>,{' '}
|
||||
<a href="/tools/wifi-qr-code">WiFi QR code generator</a>,{' '}
|
||||
<a href="/qr-code-erstellen">German QR code generator</a>, and{' '}
|
||||
<a href="/qr-code-for/barbershops">QR codes for barbershops</a>.
|
||||
</p>
|
||||
<p>
|
||||
High-intent QR Master pages include the{' '}
|
||||
<a href="/bulk-qr-code-generator">
|
||||
bulk QR code generator for Excel and CSV files
|
||||
</a>
|
||||
, <a href="/alternatives">QR code platform alternatives</a>,{' '}
|
||||
<a href="/alternatives/beaconstac">Beaconstac alternative</a>, and{' '}
|
||||
<a href="/alternatives/bitly">Bitly QR code alternative</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<HomePageClient />
|
||||
</>
|
||||
);
|
||||
}
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import {
|
||||
websiteSchema,
|
||||
softwareApplicationSchema,
|
||||
} from '@/lib/schema';
|
||||
import { getAggregateRating } from '@/lib/testimonial-data';
|
||||
import HomePageClient from '@/components/marketing/HomePageClient';
|
||||
|
||||
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 description = truncateAtWord(
|
||||
'QR Master is a free dynamic QR code generator with tracking, editable destinations, custom branding, and bulk QR creation. Create static QR codes without signup.',
|
||||
160
|
||||
);
|
||||
const brandTitle = 'QR Master - Free Dynamic QR Code Generator with Tracking';
|
||||
|
||||
return {
|
||||
title: brandTitle,
|
||||
description,
|
||||
keywords: [
|
||||
'qr generator',
|
||||
'free qr code generator',
|
||||
'custom qr code generator',
|
||||
'qr code maker',
|
||||
'online qr code generator',
|
||||
'dynamic qr code',
|
||||
'qr code with logo',
|
||||
'barcode generator',
|
||||
'free barcode generator',
|
||||
'qr master',
|
||||
'qrmaster',
|
||||
'qr code master',
|
||||
],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/',
|
||||
en: 'https://www.qrmaster.net/',
|
||||
de: 'https://www.qrmaster.net/qr-code-erstellen',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: brandTitle,
|
||||
description,
|
||||
url: 'https://www.qrmaster.net/',
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: 'https://www.qrmaster.net/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'QR Master - Dynamic QR Code Generator and Analytics Platform',
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
title: brandTitle,
|
||||
description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function HomePage() {
|
||||
const aggregateRating = getAggregateRating();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd
|
||||
data={[
|
||||
websiteSchema(),
|
||||
softwareApplicationSchema(aggregateRating),
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Server-rendered SEO content for crawlers */}
|
||||
<div className="sr-only" aria-hidden="false">
|
||||
<p>
|
||||
Create professional QR codes for your business with QR Master. Our
|
||||
dynamic QR code generator lets you create trackable QR codes, edit
|
||||
destinations anytime, and view detailed analytics. Perfect for
|
||||
restaurants, retail, events, and marketing campaigns.
|
||||
</p>
|
||||
<p>
|
||||
Features include: Dynamic QR codes with real-time tracking, bulk QR
|
||||
code generation from Excel/CSV, custom branding with colors and logos,
|
||||
advanced scan analytics showing device types and locations, vCard QR
|
||||
codes for digital business cards, restaurant menu QR codes, and a free{' '}
|
||||
<a href="/tools/barcode-generator">barcode generator</a> for EAN-13,
|
||||
UPC-A, and Code 128 barcodes.
|
||||
</p>
|
||||
<p>
|
||||
Popular QR Master workflows include the{' '}
|
||||
<a href="/dynamic-qr-code-generator">
|
||||
free dynamic QR code generator
|
||||
</a>
|
||||
, <a href="/qr-code-tracking">QR code tracking</a>, and the{' '}
|
||||
<a href="/custom-qr-code-generator">custom QR code generator</a> for
|
||||
branded print campaigns.
|
||||
</p>
|
||||
<p>
|
||||
Start free with 3 active dynamic QR codes and unlimited static codes.
|
||||
Upgrade to Pro for 50 codes with advanced analytics, or Business for
|
||||
500 codes with bulk creation and priority support.
|
||||
</p>
|
||||
<p>
|
||||
Frequently used QR Master tools and industry workflows include the{' '}
|
||||
<a href="/tools/teams-qr-code">Teams QR code generator</a>,{' '}
|
||||
<a href="/tools/wifi-qr-code">WiFi QR code generator</a>,{' '}
|
||||
<a href="/qr-code-erstellen">German QR code generator</a>, and{' '}
|
||||
<a href="/qr-code-for/barbershops">QR codes for barbershops</a>.
|
||||
</p>
|
||||
<p>
|
||||
High-intent QR Master pages include the{' '}
|
||||
<a href="/bulk-qr-code-generator">
|
||||
bulk QR code generator for Excel and CSV files
|
||||
</a>
|
||||
, <a href="/alternatives">QR code platform alternatives</a>,{' '}
|
||||
<a href="/alternatives/beaconstac">Beaconstac alternative</a>, and{' '}
|
||||
<a href="/alternatives/bitly">Bitly QR code alternative</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<HomePageClient />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,227 +1,227 @@
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import {
|
||||
buildUseCaseMetadata,
|
||||
UseCasePageTemplate,
|
||||
} from '@/components/marketing/UseCasePageTemplate';
|
||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||
|
||||
export const metadata: Metadata = buildUseCaseMetadata({
|
||||
title: 'QR Code Analytics: Measure Offline Campaigns',
|
||||
description:
|
||||
'Use QR code analytics to interpret scan data, compare placements, measure offline campaigns, and decide what to update or reprint next.',
|
||||
canonicalPath: '/qr-code-analytics',
|
||||
});
|
||||
|
||||
const softwareSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
'@id': 'https://www.qrmaster.net/qr-code-analytics#software',
|
||||
name: 'QR Master - QR Code Analytics',
|
||||
applicationCategory: 'BusinessApplication',
|
||||
operatingSystem: 'Web Browser',
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
price: '0',
|
||||
priceCurrency: 'USD',
|
||||
availability: 'https://schema.org/InStock',
|
||||
},
|
||||
description:
|
||||
'QR analytics software for measuring scans by placement, timing, device context, and offline campaign routing.',
|
||||
featureList: [
|
||||
'Placement-level scan reporting',
|
||||
'Device and timing context',
|
||||
'Offline-to-online campaign attribution',
|
||||
'Scan visibility across print workflows',
|
||||
'Destination updates without reprinting',
|
||||
],
|
||||
};
|
||||
|
||||
export default function QRCodeAnalyticsPage() {
|
||||
return (
|
||||
<>
|
||||
<UseCasePageTemplate
|
||||
title="QR Code Analytics: Measure Offline Campaigns"
|
||||
description="Use QR code analytics to interpret scan data, compare placements, measure offline campaigns, and decide what to update or reprint next."
|
||||
eyebrow="Analytics interpretation"
|
||||
intro="QR code analytics turns scan events into decisions: which printed placement worked, which destination needs work, and what should be changed before the next print run."
|
||||
pageType="commercial"
|
||||
cluster="qr-analytics"
|
||||
useCase="qr-analytics"
|
||||
breadcrumbs={[
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'QR Code Analytics', url: '/qr-code-analytics' },
|
||||
]}
|
||||
answer="QR code analytics helps teams interpret QR scan data by placement, timing, device context, and campaign route so offline marketing decisions are based on evidence rather than raw scan counts alone."
|
||||
whenToUse={[
|
||||
'You need more than raw scan counts from campaigns, packaging, or offline placements.',
|
||||
'You want to compare where scans happen and which printed surfaces actually drive action.',
|
||||
'You need a clearer bridge between QR scans and business outcomes such as signup, offers, or support engagement.',
|
||||
]}
|
||||
comparisonItems={[
|
||||
{
|
||||
label: 'Tracking collects scan events',
|
||||
text: 'Input data',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'Analytics explains performance',
|
||||
text: 'Decision layer',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'Reprint decisions',
|
||||
text: 'Based on evidence',
|
||||
value: true,
|
||||
},
|
||||
]}
|
||||
howToSteps={[
|
||||
'Create QR flows that map to real placements or workflow contexts instead of one generic code for every use case.',
|
||||
'Track scans with enough context to compare signs, flyers, inserts, or support surfaces cleanly.',
|
||||
'Use the reporting to decide which destinations, offers, or print placements deserve the next round of investment.',
|
||||
]}
|
||||
primaryCta={{
|
||||
href: '/signup',
|
||||
label: 'Start measuring QR scans',
|
||||
}}
|
||||
secondaryCta={{
|
||||
href: '/use-cases',
|
||||
label: 'Browse measured workflows',
|
||||
}}
|
||||
workflowTitle="Questions QR analytics should answer"
|
||||
workflowIntro="The point of analytics is not to produce dashboards for their own sake. It is to decide what to print again, where to place it, and what should happen after the scan."
|
||||
workflowCards={[
|
||||
{
|
||||
title: 'Which placement worked?',
|
||||
description:
|
||||
'Separate flyer, packaging, sign, event, or service-surface traffic so you know which printed context actually creates useful scan activity.',
|
||||
},
|
||||
{
|
||||
title: 'What should change next?',
|
||||
description:
|
||||
'Review performance and then improve the destination, offer, or next action without replacing every physical code already in circulation.',
|
||||
},
|
||||
{
|
||||
title: 'What should be reprinted?',
|
||||
description:
|
||||
'Give marketing, operations, or support teams a clearer view of which physical QR programs deserve another batch.',
|
||||
},
|
||||
]}
|
||||
checklistTitle="QR analytics checklist"
|
||||
checklist={[
|
||||
'Define which placements or workflow surfaces should be compared before launching the QR program.',
|
||||
'Use naming or routing that lets scans be grouped by real business context, not only by one generic campaign.',
|
||||
'Make the first post-scan step relevant enough that a scan can become a useful action, not a bounce.',
|
||||
'Review analytics before reprinting so the next batch reflects real-world performance.',
|
||||
]}
|
||||
supportLinks={[
|
||||
{
|
||||
href: '/use-cases/packaging-qr-codes',
|
||||
title: 'Use case: Packaging QR Codes',
|
||||
description:
|
||||
'See how packaging scans can become a measurable post-purchase signal instead of a blind spot.',
|
||||
},
|
||||
{
|
||||
href: '/qr-code-for-marketing-campaigns',
|
||||
title: 'QR Codes for Marketing Campaigns',
|
||||
description:
|
||||
'Plan campaign QR workflows around attribution, creative testing, and print distribution.',
|
||||
},
|
||||
{
|
||||
href: '/use-cases/flyer-qr-codes',
|
||||
title: 'Use case: Flyer QR Codes',
|
||||
description:
|
||||
'Useful when scan performance needs to be reviewed by distribution point or campaign wave.',
|
||||
},
|
||||
{
|
||||
href: '/blog/utm-parameter-qr-codes',
|
||||
title: 'UTM Parameters with QR Codes',
|
||||
description:
|
||||
'Use GA4 campaign parameters when QR scan data needs to connect to post-scan conversions.',
|
||||
},
|
||||
{
|
||||
href: '/blog/trackable-qr-codes',
|
||||
title: 'Trackable QR Codes',
|
||||
description:
|
||||
'Support article for understanding what measurable QR setups should capture and why it matters.',
|
||||
},
|
||||
]}
|
||||
faq={[
|
||||
{
|
||||
question: 'What can QR code analytics show?',
|
||||
answer:
|
||||
'QR code analytics can show scan activity by placement, time, device context, and campaign route so teams can see which physical programs are actually performing.',
|
||||
},
|
||||
{
|
||||
question:
|
||||
'What is the difference between QR tracking and QR analytics?',
|
||||
answer:
|
||||
'QR tracking collects scan events. QR analytics interprets those events so teams can compare placements, understand campaign performance, and decide what to update or reprint.',
|
||||
},
|
||||
{
|
||||
question: 'Why are QR code analytics useful for offline campaigns?',
|
||||
answer:
|
||||
'They help turn offline placements such as flyers, packaging, signs, or event materials into something measurable instead of relying on assumptions about what worked.',
|
||||
},
|
||||
{
|
||||
question: 'Do I need dynamic QR codes for analytics?',
|
||||
answer:
|
||||
'In most cases yes, because analytics usually depends on a managed redirect or reporting layer that also lets you update destinations without reprinting.',
|
||||
},
|
||||
]}
|
||||
schemaData={[softwareSchema]}
|
||||
/>
|
||||
<GrowthLinksSection
|
||||
eyebrow="Related workflows"
|
||||
title="Analytics is only the start"
|
||||
description="Tracking scans is more useful when it connects to destination flexibility, campaign comparison, and cost planning."
|
||||
links={[
|
||||
{
|
||||
href: '/qr-code-tracking',
|
||||
title: 'QR Code Tracking',
|
||||
description:
|
||||
'See device, time, and location context for every scan. Understand which placements drive real activity.',
|
||||
ctaLabel: 'Track QR code scans',
|
||||
},
|
||||
{
|
||||
href: '/dynamic-qr-code-generator',
|
||||
title: 'Dynamic QR Code Generator',
|
||||
description:
|
||||
'Create QR codes with updatable destinations so analytics can inform what to change — without reprinting.',
|
||||
ctaLabel: 'Create dynamic QR code',
|
||||
},
|
||||
{
|
||||
href: '/reprint-calculator',
|
||||
title: 'Reprint Cost Calculator',
|
||||
description:
|
||||
'Calculate how much static reprints are costing vs one dynamic QR subscription.',
|
||||
ctaLabel: 'Calculate reprint savings',
|
||||
},
|
||||
{
|
||||
href: '/qr-code-for-marketing-campaigns',
|
||||
title: 'QR Codes for Marketing Campaigns',
|
||||
description:
|
||||
'Plan offline campaigns where each placement has a measurable QR route and follow-up action.',
|
||||
ctaLabel: 'Measure offline QR campaigns',
|
||||
},
|
||||
{
|
||||
href: '/use-cases/qr-codes-for-review-collection',
|
||||
title: 'QR Codes for Review Collection',
|
||||
description:
|
||||
'Compare receipts, table cards, packaging, and counters as measurable review-request placements.',
|
||||
ctaLabel: 'Measure review QR placements',
|
||||
},
|
||||
{
|
||||
href: '/pricing',
|
||||
title: 'Compare Plans',
|
||||
description:
|
||||
'See which plan gives you the scan volume, analytics depth, and QR code count your workflows need.',
|
||||
ctaLabel: 'Compare plans',
|
||||
},
|
||||
]}
|
||||
pageType="commercial"
|
||||
cluster="qr-analytics"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import {
|
||||
buildUseCaseMetadata,
|
||||
UseCasePageTemplate,
|
||||
} from '@/components/marketing/UseCasePageTemplate';
|
||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||
|
||||
export const metadata: Metadata = buildUseCaseMetadata({
|
||||
title: 'QR Code Analytics: Measure Offline Campaigns',
|
||||
description:
|
||||
'Use QR code analytics to interpret scan data, compare placements, measure offline campaigns, and decide what to update or reprint next.',
|
||||
canonicalPath: '/qr-code-analytics',
|
||||
});
|
||||
|
||||
const softwareSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
'@id': 'https://www.qrmaster.net/qr-code-analytics#software',
|
||||
name: 'QR Master - QR Code Analytics',
|
||||
applicationCategory: 'BusinessApplication',
|
||||
operatingSystem: 'Web Browser',
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
price: '0',
|
||||
priceCurrency: 'USD',
|
||||
availability: 'https://schema.org/InStock',
|
||||
},
|
||||
description:
|
||||
'QR analytics software for measuring scans by placement, timing, device context, and offline campaign routing.',
|
||||
featureList: [
|
||||
'Placement-level scan reporting',
|
||||
'Device and timing context',
|
||||
'Offline-to-online campaign attribution',
|
||||
'Scan visibility across print workflows',
|
||||
'Destination updates without reprinting',
|
||||
],
|
||||
};
|
||||
|
||||
export default function QRCodeAnalyticsPage() {
|
||||
return (
|
||||
<>
|
||||
<UseCasePageTemplate
|
||||
title="QR Code Analytics: Measure Offline Campaigns"
|
||||
description="Use QR code analytics to interpret scan data, compare placements, measure offline campaigns, and decide what to update or reprint next."
|
||||
eyebrow="Analytics interpretation"
|
||||
intro="QR code analytics turns scan events into decisions: which printed placement worked, which destination needs work, and what should be changed before the next print run."
|
||||
pageType="commercial"
|
||||
cluster="qr-analytics"
|
||||
useCase="qr-analytics"
|
||||
breadcrumbs={[
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'QR Code Analytics', url: '/qr-code-analytics' },
|
||||
]}
|
||||
answer="QR code analytics helps teams interpret QR scan data by placement, timing, device context, and campaign route so offline marketing decisions are based on evidence rather than raw scan counts alone."
|
||||
whenToUse={[
|
||||
'You need more than raw scan counts from campaigns, packaging, or offline placements.',
|
||||
'You want to compare where scans happen and which printed surfaces actually drive action.',
|
||||
'You need a clearer bridge between QR scans and business outcomes such as signup, offers, or support engagement.',
|
||||
]}
|
||||
comparisonItems={[
|
||||
{
|
||||
label: 'Tracking collects scan events',
|
||||
text: 'Input data',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'Analytics explains performance',
|
||||
text: 'Decision layer',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'Reprint decisions',
|
||||
text: 'Based on evidence',
|
||||
value: true,
|
||||
},
|
||||
]}
|
||||
howToSteps={[
|
||||
'Create QR flows that map to real placements or workflow contexts instead of one generic code for every use case.',
|
||||
'Track scans with enough context to compare signs, flyers, inserts, or support surfaces cleanly.',
|
||||
'Use the reporting to decide which destinations, offers, or print placements deserve the next round of investment.',
|
||||
]}
|
||||
primaryCta={{
|
||||
href: '/signup',
|
||||
label: 'Start measuring QR scans',
|
||||
}}
|
||||
secondaryCta={{
|
||||
href: '/use-cases',
|
||||
label: 'Browse measured workflows',
|
||||
}}
|
||||
workflowTitle="Questions QR analytics should answer"
|
||||
workflowIntro="The point of analytics is not to produce dashboards for their own sake. It is to decide what to print again, where to place it, and what should happen after the scan."
|
||||
workflowCards={[
|
||||
{
|
||||
title: 'Which placement worked?',
|
||||
description:
|
||||
'Separate flyer, packaging, sign, event, or service-surface traffic so you know which printed context actually creates useful scan activity.',
|
||||
},
|
||||
{
|
||||
title: 'What should change next?',
|
||||
description:
|
||||
'Review performance and then improve the destination, offer, or next action without replacing every physical code already in circulation.',
|
||||
},
|
||||
{
|
||||
title: 'What should be reprinted?',
|
||||
description:
|
||||
'Give marketing, operations, or support teams a clearer view of which physical QR programs deserve another batch.',
|
||||
},
|
||||
]}
|
||||
checklistTitle="QR analytics checklist"
|
||||
checklist={[
|
||||
'Define which placements or workflow surfaces should be compared before launching the QR program.',
|
||||
'Use naming or routing that lets scans be grouped by real business context, not only by one generic campaign.',
|
||||
'Make the first post-scan step relevant enough that a scan can become a useful action, not a bounce.',
|
||||
'Review analytics before reprinting so the next batch reflects real-world performance.',
|
||||
]}
|
||||
supportLinks={[
|
||||
{
|
||||
href: '/use-cases/packaging-qr-codes',
|
||||
title: 'Use case: Packaging QR Codes',
|
||||
description:
|
||||
'See how packaging scans can become a measurable post-purchase signal instead of a blind spot.',
|
||||
},
|
||||
{
|
||||
href: '/qr-code-for-marketing-campaigns',
|
||||
title: 'QR Codes for Marketing Campaigns',
|
||||
description:
|
||||
'Plan campaign QR workflows around attribution, creative testing, and print distribution.',
|
||||
},
|
||||
{
|
||||
href: '/use-cases/flyer-qr-codes',
|
||||
title: 'Use case: Flyer QR Codes',
|
||||
description:
|
||||
'Useful when scan performance needs to be reviewed by distribution point or campaign wave.',
|
||||
},
|
||||
{
|
||||
href: '/blog/utm-parameter-qr-codes',
|
||||
title: 'UTM Parameters with QR Codes',
|
||||
description:
|
||||
'Use GA4 campaign parameters when QR scan data needs to connect to post-scan conversions.',
|
||||
},
|
||||
{
|
||||
href: '/blog/trackable-qr-codes',
|
||||
title: 'Trackable QR Codes',
|
||||
description:
|
||||
'Support article for understanding what measurable QR setups should capture and why it matters.',
|
||||
},
|
||||
]}
|
||||
faq={[
|
||||
{
|
||||
question: 'What can QR code analytics show?',
|
||||
answer:
|
||||
'QR code analytics can show scan activity by placement, time, device context, and campaign route so teams can see which physical programs are actually performing.',
|
||||
},
|
||||
{
|
||||
question:
|
||||
'What is the difference between QR tracking and QR analytics?',
|
||||
answer:
|
||||
'QR tracking collects scan events. QR analytics interprets those events so teams can compare placements, understand campaign performance, and decide what to update or reprint.',
|
||||
},
|
||||
{
|
||||
question: 'Why are QR code analytics useful for offline campaigns?',
|
||||
answer:
|
||||
'They help turn offline placements such as flyers, packaging, signs, or event materials into something measurable instead of relying on assumptions about what worked.',
|
||||
},
|
||||
{
|
||||
question: 'Do I need dynamic QR codes for analytics?',
|
||||
answer:
|
||||
'In most cases yes, because analytics usually depends on a managed redirect or reporting layer that also lets you update destinations without reprinting.',
|
||||
},
|
||||
]}
|
||||
schemaData={[softwareSchema]}
|
||||
/>
|
||||
<GrowthLinksSection
|
||||
eyebrow="Related workflows"
|
||||
title="Analytics is only the start"
|
||||
description="Tracking scans is more useful when it connects to destination flexibility, campaign comparison, and cost planning."
|
||||
links={[
|
||||
{
|
||||
href: '/qr-code-tracking',
|
||||
title: 'QR Code Tracking',
|
||||
description:
|
||||
'See device, time, and location context for every scan. Understand which placements drive real activity.',
|
||||
ctaLabel: 'Track QR code scans',
|
||||
},
|
||||
{
|
||||
href: '/dynamic-qr-code-generator',
|
||||
title: 'Dynamic QR Code Generator',
|
||||
description:
|
||||
'Create QR codes with updatable destinations so analytics can inform what to change — without reprinting.',
|
||||
ctaLabel: 'Create dynamic QR code',
|
||||
},
|
||||
{
|
||||
href: '/reprint-calculator',
|
||||
title: 'Reprint Cost Calculator',
|
||||
description:
|
||||
'Calculate how much static reprints are costing vs one dynamic QR subscription.',
|
||||
ctaLabel: 'Calculate reprint savings',
|
||||
},
|
||||
{
|
||||
href: '/qr-code-for-marketing-campaigns',
|
||||
title: 'QR Codes for Marketing Campaigns',
|
||||
description:
|
||||
'Plan offline campaigns where each placement has a measurable QR route and follow-up action.',
|
||||
ctaLabel: 'Measure offline QR campaigns',
|
||||
},
|
||||
{
|
||||
href: '/use-cases/qr-codes-for-review-collection',
|
||||
title: 'QR Codes for Review Collection',
|
||||
description:
|
||||
'Compare receipts, table cards, packaging, and counters as measurable review-request placements.',
|
||||
ctaLabel: 'Measure review QR placements',
|
||||
},
|
||||
{
|
||||
href: '/pricing',
|
||||
title: 'Compare Plans',
|
||||
description:
|
||||
'See which plan gives you the scan volume, analytics depth, and QR code count your workflows need.',
|
||||
ctaLabel: 'Compare plans',
|
||||
},
|
||||
]}
|
||||
pageType="commercial"
|
||||
cluster="qr-analytics"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,168 +1,168 @@
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import {
|
||||
buildIndustryMetadata,
|
||||
IndustryPageTemplate,
|
||||
} from "@/components/marketing/IndustryPageTemplate";
|
||||
import { industryPages } from "@/lib/industry-pages";
|
||||
|
||||
type Cta = {
|
||||
href: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const toolsMap: Record<string, { href: string; title: string; description: string }> = {
|
||||
"url-qr-code": { href: "/tools/url-qr-code", title: "URL QR Code Generator", description: "Create a scannable link to any webpage and update the destination any time." },
|
||||
"wifi-qr-code": { href: "/tools/wifi-qr-code", title: "WiFi QR Code Generator", description: "Print once, let guests scan to connect, and remove the friction of sharing passwords." },
|
||||
"vcard-qr-code": { href: "/tools/vcard-qr-code", title: "vCard QR Code", description: "Share contact details with a single scan so prospects can save your team instantly." },
|
||||
"email-qr-code": { href: "/tools/email-qr-code", title: "Email QR Code", description: "Open a pre-addressed email draft so customers can contact you faster." },
|
||||
"sms-qr-code": { href: "/tools/sms-qr-code", title: "SMS QR Code", description: "Pre-fill a text message to support bookings, follow-ups, and reminder flows." },
|
||||
"instagram-qr-code": { href: "/tools/instagram-qr-code", title: "Instagram QR Code", description: "Send visitors directly to your Instagram profile, gallery, or creator page." },
|
||||
"facebook-qr-code": { href: "/tools/facebook-qr-code", title: "Facebook QR Code", description: "Link to your Facebook Page or Group with a single scan." },
|
||||
"youtube-qr-code": { href: "/tools/youtube-qr-code", title: "YouTube QR Code", description: "Point visitors to a video, product demo, tutorial, or playlist." },
|
||||
"video-qr-code": { href: "/tools/youtube-qr-code", title: "Video QR Code", description: "Use a video-focused QR workflow for demos, walkthroughs, and visual proof." },
|
||||
"event-qr-code": { href: "/tools/event-qr-code", title: "Event QR Code", description: "Let visitors save event details directly to their phone calendar." },
|
||||
"app-store-qr-code": { href: "/tools/url-qr-code", title: "App Download QR Code", description: "Use a URL QR code to send scans to your App Store or Play Store listing." },
|
||||
"geolocation-qr-code": { href: "/tools/geolocation-qr-code", title: "Location QR Code", description: "Open Google Maps navigation to your store, venue, or office address." },
|
||||
"call-qr-code-generator": { href: "/tools/call-qr-code-generator", title: "Phone Call QR Code", description: "Open the dialer with your number pre-filled for instant contact." },
|
||||
"paypal-qr-code": { href: "/tools/paypal-qr-code", title: "PayPal QR Code", description: "Link directly to a PayPal payment, invoice, or donation page." },
|
||||
"pdf-qr-code": { href: "/tools/url-qr-code", title: "PDF QR Code", description: "Use a URL QR code to open a hosted PDF like a menu, brochure, or operating guide." },
|
||||
"whatsapp-qr-code": { href: "/tools/whatsapp-qr-code", title: "WhatsApp QR Code", description: "Open a WhatsApp chat with your number pre-loaded for faster support." },
|
||||
};
|
||||
|
||||
const industryPrimaryCtas: Record<string, Cta> = {
|
||||
restaurants: { href: "/dynamic-qr-code-generator", label: "Create a Dynamic Menu QR Code" },
|
||||
cafes: { href: "/tools/url-qr-code", label: "Create a Cafe QR Code" },
|
||||
hotels: { href: "/tools/wifi-qr-code", label: "Create a Hotel WiFi QR Code" },
|
||||
"real-estate": { href: "/qr-code-tracking", label: "Track Real Estate QR Scans" },
|
||||
retail: { href: "/bulk-qr-code-generator", label: "Create Bulk Retail QR Codes" },
|
||||
events: { href: "/tools/event-qr-code", label: "Create an Event QR Code" },
|
||||
"doctors-dentists": { href: "/tools/url-qr-code", label: "Create an Intake Form QR Code" },
|
||||
dentists: { href: "/tools/url-qr-code", label: "Create a Dental QR Code" },
|
||||
"trade-shows": { href: "/qr-code-tracking", label: "Track Trade Show QR Leads" },
|
||||
pharmacies: { href: "/tools/email-qr-code", label: "Create a Pharmacy Follow-Up QR Code" },
|
||||
"car-dealerships": { href: "/qr-code-tracking", label: "Track Dealership QR Scans" },
|
||||
"pet-grooming": { href: "/tools/url-qr-code", label: "Create a Grooming Booking QR Code" },
|
||||
veterinarians: { href: "/tools/url-qr-code", label: "Create a Veterinary QR Code" },
|
||||
};
|
||||
|
||||
const industrySecondaryCtas: Record<string, Cta> = {
|
||||
restaurants: { href: "/qr-code-tracking", label: "See QR Tracking Features" },
|
||||
cafes: { href: "/dynamic-qr-code-generator", label: "Try Dynamic QR Codes" },
|
||||
hotels: { href: "/dynamic-qr-code-generator", label: "Try Dynamic QR Codes" },
|
||||
"real-estate": { href: "/dynamic-qr-code-generator", label: "Try Dynamic QR Codes" },
|
||||
retail: { href: "/qr-code-tracking", label: "See QR Tracking Features" },
|
||||
events: { href: "/qr-code-tracking", label: "See QR Tracking Features" },
|
||||
"doctors-dentists": { href: "/qr-code-tracking", label: "See QR Tracking Features" },
|
||||
dentists: { href: "/qr-code-tracking", label: "See QR Tracking Features" },
|
||||
"trade-shows": { href: "/bulk-qr-code-generator", label: "Create Bulk QR Codes" },
|
||||
};
|
||||
|
||||
export function generateStaticParams() {
|
||||
return industryPages.map((page) => ({ industry: page.slug }));
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }: { params: { industry: string } }) {
|
||||
const page = industryPages.find((entry) => entry.slug === params.industry);
|
||||
if (!page) return {};
|
||||
|
||||
return buildIndustryMetadata({
|
||||
title: page.metaTitle || page.title,
|
||||
fallbackTitle: page.title,
|
||||
description: page.metaDescription,
|
||||
canonicalPath: `/qr-code-for/${page.slug}`,
|
||||
});
|
||||
}
|
||||
|
||||
function getToolCta(toolSlug?: string): Cta | null {
|
||||
if (!toolSlug) return null;
|
||||
const tool = toolsMap[toolSlug];
|
||||
if (!tool) return null;
|
||||
|
||||
if (toolSlug === "url-qr-code") {
|
||||
return { href: tool.href, label: "Create a QR Code" };
|
||||
}
|
||||
|
||||
return { href: tool.href, label: `Create a ${tool.title}` };
|
||||
}
|
||||
|
||||
function getPrimaryCta(page: (typeof industryPages)[number]): Cta {
|
||||
return (
|
||||
industryPrimaryCtas[page.slug] ??
|
||||
getToolCta(page.tools[0]) ?? {
|
||||
href: "/dynamic-qr-code-generator",
|
||||
label: "Create a Dynamic QR Code",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getSecondaryCta(page: (typeof industryPages)[number]): Cta {
|
||||
return (
|
||||
industrySecondaryCtas[page.slug] ?? {
|
||||
href: "/pricing",
|
||||
label: "View Plans",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getHeroImageAlt(page: (typeof industryPages)[number]) {
|
||||
return `${page.name} QR code example showing a real-world workflow for ${page.useCases[0]?.toLowerCase() ?? "customer engagement"}.`;
|
||||
}
|
||||
|
||||
export default function IndustryPage({ params }: { params: { industry: string } }) {
|
||||
const page = industryPages.find((entry) => entry.slug === params.industry);
|
||||
if (!page) notFound();
|
||||
|
||||
const primaryCta = getPrimaryCta(page);
|
||||
const secondaryCta = getSecondaryCta(page);
|
||||
const supportLinks = page.tools.map(
|
||||
(slug) =>
|
||||
toolsMap[slug] ?? {
|
||||
href: "/tools/url-qr-code",
|
||||
title: "QR Code Generator",
|
||||
description: "Create a QR code for your business.",
|
||||
}
|
||||
);
|
||||
const useCaseStrings = Array.isArray(page.useCases) ? page.useCases : [];
|
||||
|
||||
return (
|
||||
<IndustryPageTemplate
|
||||
title={page.title}
|
||||
description={page.metaDescription}
|
||||
eyebrow={page.name}
|
||||
intro={page.subheadline || page.headline}
|
||||
pageType="commercial"
|
||||
cluster="industry"
|
||||
useCase={page.slug}
|
||||
breadcrumbs={[
|
||||
{ name: "Home", url: "/" },
|
||||
{ name: "QR Codes by Industry", url: "/qr-code-for" },
|
||||
{ name: page.title, url: `/qr-code-for/${page.slug}` },
|
||||
]}
|
||||
answer={page.headline}
|
||||
whenToUse={useCaseStrings.slice(0, 3)}
|
||||
comparisonItems={[
|
||||
{ label: "Update destination after printing", value: true, text: "Dynamic QR codes" },
|
||||
{ label: "Track scans by date, device & location", value: true, text: "Built-in analytics" },
|
||||
{ label: "No app needed to scan", value: true, text: "Works with native camera" },
|
||||
]}
|
||||
howToSteps={page.placementTips.slice(0, 3)}
|
||||
primaryCta={primaryCta}
|
||||
secondaryCta={secondaryCta}
|
||||
workflowTitle={`How ${page.name} Use QR Codes`}
|
||||
workflowIntro={`Practical QR code workflows built around how ${page.name.toLowerCase()} actually operate.`}
|
||||
workflowCards={useCaseStrings.slice(0, 3).map((useCase, index) => ({
|
||||
title: `Workflow ${index + 1}`,
|
||||
description: useCase,
|
||||
}))}
|
||||
checklistTitle={`${page.name} QR Code Checklist`}
|
||||
checklist={page.placementTips}
|
||||
supportLinks={supportLinks}
|
||||
faq={page.faq}
|
||||
heroImage={page.heroImage}
|
||||
heroImageAlt={getHeroImageAlt(page)}
|
||||
statistics={page.statistics}
|
||||
benefits={page.benefits}
|
||||
/>
|
||||
);
|
||||
}
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import {
|
||||
buildIndustryMetadata,
|
||||
IndustryPageTemplate,
|
||||
} from "@/components/marketing/IndustryPageTemplate";
|
||||
import { industryPages } from "@/lib/industry-pages";
|
||||
|
||||
type Cta = {
|
||||
href: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const toolsMap: Record<string, { href: string; title: string; description: string }> = {
|
||||
"url-qr-code": { href: "/tools/url-qr-code", title: "URL QR Code Generator", description: "Create a scannable link to any webpage and update the destination any time." },
|
||||
"wifi-qr-code": { href: "/tools/wifi-qr-code", title: "WiFi QR Code Generator", description: "Print once, let guests scan to connect, and remove the friction of sharing passwords." },
|
||||
"vcard-qr-code": { href: "/tools/vcard-qr-code", title: "vCard QR Code", description: "Share contact details with a single scan so prospects can save your team instantly." },
|
||||
"email-qr-code": { href: "/tools/email-qr-code", title: "Email QR Code", description: "Open a pre-addressed email draft so customers can contact you faster." },
|
||||
"sms-qr-code": { href: "/tools/sms-qr-code", title: "SMS QR Code", description: "Pre-fill a text message to support bookings, follow-ups, and reminder flows." },
|
||||
"instagram-qr-code": { href: "/tools/instagram-qr-code", title: "Instagram QR Code", description: "Send visitors directly to your Instagram profile, gallery, or creator page." },
|
||||
"facebook-qr-code": { href: "/tools/facebook-qr-code", title: "Facebook QR Code", description: "Link to your Facebook Page or Group with a single scan." },
|
||||
"youtube-qr-code": { href: "/tools/youtube-qr-code", title: "YouTube QR Code", description: "Point visitors to a video, product demo, tutorial, or playlist." },
|
||||
"video-qr-code": { href: "/tools/youtube-qr-code", title: "Video QR Code", description: "Use a video-focused QR workflow for demos, walkthroughs, and visual proof." },
|
||||
"event-qr-code": { href: "/tools/event-qr-code", title: "Event QR Code", description: "Let visitors save event details directly to their phone calendar." },
|
||||
"app-store-qr-code": { href: "/tools/url-qr-code", title: "App Download QR Code", description: "Use a URL QR code to send scans to your App Store or Play Store listing." },
|
||||
"geolocation-qr-code": { href: "/tools/geolocation-qr-code", title: "Location QR Code", description: "Open Google Maps navigation to your store, venue, or office address." },
|
||||
"call-qr-code-generator": { href: "/tools/call-qr-code-generator", title: "Phone Call QR Code", description: "Open the dialer with your number pre-filled for instant contact." },
|
||||
"paypal-qr-code": { href: "/tools/paypal-qr-code", title: "PayPal QR Code", description: "Link directly to a PayPal payment, invoice, or donation page." },
|
||||
"pdf-qr-code": { href: "/tools/url-qr-code", title: "PDF QR Code", description: "Use a URL QR code to open a hosted PDF like a menu, brochure, or operating guide." },
|
||||
"whatsapp-qr-code": { href: "/tools/whatsapp-qr-code", title: "WhatsApp QR Code", description: "Open a WhatsApp chat with your number pre-loaded for faster support." },
|
||||
};
|
||||
|
||||
const industryPrimaryCtas: Record<string, Cta> = {
|
||||
restaurants: { href: "/dynamic-qr-code-generator", label: "Create a Dynamic Menu QR Code" },
|
||||
cafes: { href: "/tools/url-qr-code", label: "Create a Cafe QR Code" },
|
||||
hotels: { href: "/tools/wifi-qr-code", label: "Create a Hotel WiFi QR Code" },
|
||||
"real-estate": { href: "/qr-code-tracking", label: "Track Real Estate QR Scans" },
|
||||
retail: { href: "/bulk-qr-code-generator", label: "Create Bulk Retail QR Codes" },
|
||||
events: { href: "/tools/event-qr-code", label: "Create an Event QR Code" },
|
||||
"doctors-dentists": { href: "/tools/url-qr-code", label: "Create an Intake Form QR Code" },
|
||||
dentists: { href: "/tools/url-qr-code", label: "Create a Dental QR Code" },
|
||||
"trade-shows": { href: "/qr-code-tracking", label: "Track Trade Show QR Leads" },
|
||||
pharmacies: { href: "/tools/email-qr-code", label: "Create a Pharmacy Follow-Up QR Code" },
|
||||
"car-dealerships": { href: "/qr-code-tracking", label: "Track Dealership QR Scans" },
|
||||
"pet-grooming": { href: "/tools/url-qr-code", label: "Create a Grooming Booking QR Code" },
|
||||
veterinarians: { href: "/tools/url-qr-code", label: "Create a Veterinary QR Code" },
|
||||
};
|
||||
|
||||
const industrySecondaryCtas: Record<string, Cta> = {
|
||||
restaurants: { href: "/qr-code-tracking", label: "See QR Tracking Features" },
|
||||
cafes: { href: "/dynamic-qr-code-generator", label: "Try Dynamic QR Codes" },
|
||||
hotels: { href: "/dynamic-qr-code-generator", label: "Try Dynamic QR Codes" },
|
||||
"real-estate": { href: "/dynamic-qr-code-generator", label: "Try Dynamic QR Codes" },
|
||||
retail: { href: "/qr-code-tracking", label: "See QR Tracking Features" },
|
||||
events: { href: "/qr-code-tracking", label: "See QR Tracking Features" },
|
||||
"doctors-dentists": { href: "/qr-code-tracking", label: "See QR Tracking Features" },
|
||||
dentists: { href: "/qr-code-tracking", label: "See QR Tracking Features" },
|
||||
"trade-shows": { href: "/bulk-qr-code-generator", label: "Create Bulk QR Codes" },
|
||||
};
|
||||
|
||||
export function generateStaticParams() {
|
||||
return industryPages.map((page) => ({ industry: page.slug }));
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }: { params: { industry: string } }) {
|
||||
const page = industryPages.find((entry) => entry.slug === params.industry);
|
||||
if (!page) return {};
|
||||
|
||||
return buildIndustryMetadata({
|
||||
title: page.metaTitle || page.title,
|
||||
fallbackTitle: page.title,
|
||||
description: page.metaDescription,
|
||||
canonicalPath: `/qr-code-for/${page.slug}`,
|
||||
});
|
||||
}
|
||||
|
||||
function getToolCta(toolSlug?: string): Cta | null {
|
||||
if (!toolSlug) return null;
|
||||
const tool = toolsMap[toolSlug];
|
||||
if (!tool) return null;
|
||||
|
||||
if (toolSlug === "url-qr-code") {
|
||||
return { href: tool.href, label: "Create a QR Code" };
|
||||
}
|
||||
|
||||
return { href: tool.href, label: `Create a ${tool.title}` };
|
||||
}
|
||||
|
||||
function getPrimaryCta(page: (typeof industryPages)[number]): Cta {
|
||||
return (
|
||||
industryPrimaryCtas[page.slug] ??
|
||||
getToolCta(page.tools[0]) ?? {
|
||||
href: "/dynamic-qr-code-generator",
|
||||
label: "Create a Dynamic QR Code",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getSecondaryCta(page: (typeof industryPages)[number]): Cta {
|
||||
return (
|
||||
industrySecondaryCtas[page.slug] ?? {
|
||||
href: "/pricing",
|
||||
label: "View Plans",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getHeroImageAlt(page: (typeof industryPages)[number]) {
|
||||
return `${page.name} QR code example showing a real-world workflow for ${page.useCases[0]?.toLowerCase() ?? "customer engagement"}.`;
|
||||
}
|
||||
|
||||
export default function IndustryPage({ params }: { params: { industry: string } }) {
|
||||
const page = industryPages.find((entry) => entry.slug === params.industry);
|
||||
if (!page) notFound();
|
||||
|
||||
const primaryCta = getPrimaryCta(page);
|
||||
const secondaryCta = getSecondaryCta(page);
|
||||
const supportLinks = page.tools.map(
|
||||
(slug) =>
|
||||
toolsMap[slug] ?? {
|
||||
href: "/tools/url-qr-code",
|
||||
title: "QR Code Generator",
|
||||
description: "Create a QR code for your business.",
|
||||
}
|
||||
);
|
||||
const useCaseStrings = Array.isArray(page.useCases) ? page.useCases : [];
|
||||
|
||||
return (
|
||||
<IndustryPageTemplate
|
||||
title={page.title}
|
||||
description={page.metaDescription}
|
||||
eyebrow={page.name}
|
||||
intro={page.subheadline || page.headline}
|
||||
pageType="commercial"
|
||||
cluster="industry"
|
||||
useCase={page.slug}
|
||||
breadcrumbs={[
|
||||
{ name: "Home", url: "/" },
|
||||
{ name: "QR Codes by Industry", url: "/qr-code-for" },
|
||||
{ name: page.title, url: `/qr-code-for/${page.slug}` },
|
||||
]}
|
||||
answer={page.headline}
|
||||
whenToUse={useCaseStrings.slice(0, 3)}
|
||||
comparisonItems={[
|
||||
{ label: "Update destination after printing", value: true, text: "Dynamic QR codes" },
|
||||
{ label: "Track scans by date, device & location", value: true, text: "Built-in analytics" },
|
||||
{ label: "No app needed to scan", value: true, text: "Works with native camera" },
|
||||
]}
|
||||
howToSteps={page.placementTips.slice(0, 3)}
|
||||
primaryCta={primaryCta}
|
||||
secondaryCta={secondaryCta}
|
||||
workflowTitle={`How ${page.name} Use QR Codes`}
|
||||
workflowIntro={`Practical QR code workflows built around how ${page.name.toLowerCase()} actually operate.`}
|
||||
workflowCards={useCaseStrings.slice(0, 3).map((useCase, index) => ({
|
||||
title: `Workflow ${index + 1}`,
|
||||
description: useCase,
|
||||
}))}
|
||||
checklistTitle={`${page.name} QR Code Checklist`}
|
||||
checklist={page.placementTips}
|
||||
supportLinks={supportLinks}
|
||||
faq={page.faq}
|
||||
heroImage={page.heroImage}
|
||||
heroImageAlt={getHeroImageAlt(page)}
|
||||
statistics={page.statistics}
|
||||
benefits={page.benefits}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,335 +1,335 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { ArrowRight, Building2, CheckCircle2 } from "lucide-react";
|
||||
|
||||
import { FAQSection } from "@/components/aeo/FAQSection";
|
||||
import SeoJsonLd from "@/components/SeoJsonLd";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { industryPages } from "@/lib/industry-pages";
|
||||
import { breadcrumbSchema, faqPageSchema } from "@/lib/schema";
|
||||
|
||||
type IndustryCategory = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
slugs: string[];
|
||||
};
|
||||
|
||||
const categories: IndustryCategory[] = [
|
||||
{
|
||||
id: "food-hospitality",
|
||||
title: "Food, Hospitality, and Guest Experience",
|
||||
description: "Use QR codes to replace printed menus, simplify guest WiFi, speed up bookings, and keep in-venue information current without reprinting.",
|
||||
slugs: [
|
||||
"restaurants",
|
||||
"cafes",
|
||||
"hotels",
|
||||
"bars",
|
||||
"food-trucks",
|
||||
"bakeries",
|
||||
"breweries",
|
||||
"nightclubs",
|
||||
"catering",
|
||||
"wineries",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "health-lifestyle",
|
||||
title: "Health, Wellness, and Personal Services",
|
||||
description: "Support appointment booking, aftercare, digital treatment menus, and high-intent follow-up flows across health and lifestyle businesses.",
|
||||
slugs: [
|
||||
"gyms",
|
||||
"doctors-dentists",
|
||||
"dentists",
|
||||
"yoga-studios",
|
||||
"spas",
|
||||
"beauty-salons",
|
||||
"barbershops",
|
||||
"nail-salons",
|
||||
"tattoo-studios",
|
||||
"pharmacies",
|
||||
"pet-grooming",
|
||||
"veterinarians",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "retail-commerce",
|
||||
title: "Retail, Packaging, and Product Discovery",
|
||||
description: "Bridge shelf, packaging, and in-store discovery with QR codes for demos, manuals, size guides, reviews, and product detail pages.",
|
||||
slugs: [
|
||||
"retail",
|
||||
"clothing-stores",
|
||||
"car-dealerships",
|
||||
"florists",
|
||||
"pet-stores",
|
||||
"electronics-stores",
|
||||
"jewelry-stores",
|
||||
"hardware-stores",
|
||||
"bookstores",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "education-culture-public",
|
||||
title: "Education, Culture, and Public Spaces",
|
||||
description: "Improve access to maps, resources, ticketing, audio guides, and visitor information across campuses, venues, and cultural institutions.",
|
||||
slugs: [
|
||||
"universities",
|
||||
"schools",
|
||||
"museums",
|
||||
"libraries",
|
||||
"theaters",
|
||||
"cinemas",
|
||||
"churches",
|
||||
"art-galleries",
|
||||
"stadiums",
|
||||
"airports",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "services-b2b-events",
|
||||
title: "Services, Real Estate, and Lead Capture",
|
||||
description: "Turn printed materials into measurable lead-gen assets for agencies, property businesses, events, and professional service firms.",
|
||||
slugs: [
|
||||
"real-estate",
|
||||
"events",
|
||||
"wedding-planners",
|
||||
"photographers",
|
||||
"trade-shows",
|
||||
"law-firms",
|
||||
"accountants",
|
||||
"insurance-agencies",
|
||||
"travel-agencies",
|
||||
"coworking-spaces",
|
||||
"property-management",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const hubFaq = [
|
||||
{
|
||||
question: "Which industries get the most value from dynamic QR codes?",
|
||||
answer:
|
||||
"Industries that print menus, flyers, labels, in-store signage, or operational materials usually benefit most because they can update destinations without reprinting every asset.",
|
||||
},
|
||||
{
|
||||
question: "Should industry QR code pages focus on tools or workflows?",
|
||||
answer:
|
||||
"Both matter, but workflow intent usually ranks better. Searchers often want a job-to-be-done answer first, then the right QR code tool to implement it.",
|
||||
},
|
||||
{
|
||||
question: "Are industry-specific QR code pages good for SEO?",
|
||||
answer:
|
||||
"Yes, when each page targets a real search intent, has unique examples, and connects to a relevant next step such as tracking, dynamic QR management, or the matching QR tool.",
|
||||
},
|
||||
{
|
||||
question: "What makes a QR code industry page convert well?",
|
||||
answer:
|
||||
"Clear use cases, realistic placement advice, trust-building FAQs, and a CTA that matches the industry workflow outperform generic sign-up prompts.",
|
||||
},
|
||||
];
|
||||
|
||||
const highlightedLinks = [
|
||||
{ href: "/dynamic-qr-code-generator", title: "Dynamic QR Code Generator", description: "Best next step for menus, signage, packaging, and any printed asset that may change later." },
|
||||
{ href: "/qr-code-tracking", title: "QR Code Tracking", description: "Measure scans from flyers, property signage, retail displays, trade shows, and offline campaigns." },
|
||||
{ href: "/bulk-qr-code-generator", title: "Bulk QR Code Generator", description: "Best fit for packaging, product lines, multi-location rollouts, and large campaigns." },
|
||||
{ href: "/tools/wifi-qr-code", title: "WiFi QR Code Tool", description: "Popular for hotels, cafes, coworking spaces, clinics, and guest-facing venues." },
|
||||
{ href: "/tools/teams-qr-code", title: "Teams QR Code Tool", description: "Useful for offices, meeting rooms, coworking spaces, and event check-in workflows." },
|
||||
{ href: "/qr-code-for/barbershops", title: "QR Codes for Barbershops", description: "A high-intent local service page for bookings, reviews, WiFi, and social profile QR codes." },
|
||||
];
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "QR Codes by Industry: Use Cases and Examples",
|
||||
description:
|
||||
"Explore QR code ideas by industry, including restaurants, hotels, retail, healthcare, events, education, and professional services. Find use cases, examples, and the best QR workflow for your business.",
|
||||
alternates: {
|
||||
canonical: "https://www.qrmaster.net/qr-code-for",
|
||||
},
|
||||
openGraph: {
|
||||
title: "QR Codes by Industry | QR Master",
|
||||
description:
|
||||
"Find industry-specific QR code use cases, workflows, and examples for hospitality, retail, healthcare, events, education, and more.",
|
||||
url: "https://www.qrmaster.net/qr-code-for",
|
||||
type: "website",
|
||||
images: ["/og-image.png"],
|
||||
},
|
||||
twitter: {
|
||||
title: "QR Codes by Industry | QR Master",
|
||||
description:
|
||||
"Industry-specific QR code ideas and workflows for restaurants, hotels, retail, events, healthcare, and more.",
|
||||
},
|
||||
};
|
||||
|
||||
function getIndustryBySlug(slug: string) {
|
||||
return industryPages.find((industry) => industry.slug === slug);
|
||||
}
|
||||
|
||||
export default function IndustryOverviewPage() {
|
||||
const itemListSchema = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "CollectionPage",
|
||||
name: "QR Codes by Industry",
|
||||
url: "https://www.qrmaster.net/qr-code-for",
|
||||
mainEntity: {
|
||||
"@type": "ItemList",
|
||||
itemListElement: industryPages.map((industry, index) => ({
|
||||
"@type": "ListItem",
|
||||
position: index + 1,
|
||||
url: `https://www.qrmaster.net/qr-code-for/${industry.slug}`,
|
||||
name: industry.title,
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd
|
||||
data={[
|
||||
itemListSchema,
|
||||
breadcrumbSchema([
|
||||
{ name: "Home", url: "/" },
|
||||
{ name: "QR Codes by Industry", url: "/qr-code-for" },
|
||||
]),
|
||||
faqPageSchema(hubFaq),
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="min-h-screen bg-white text-slate-900 font-sans">
|
||||
<section className="relative overflow-hidden border-b border-slate-100 bg-slate-50/70 pt-24 pb-20 lg:pt-32 lg:pb-24">
|
||||
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_right,#cbd5e1_1px,transparent_1px),linear-gradient(to_bottom,#cbd5e1_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_85%_75%_at_50%_0%,#000_70%,transparent_110%)] opacity-25" />
|
||||
<div className="pointer-events-none absolute top-0 right-0 h-[28rem] w-[28rem] rounded-full bg-blue-200/40 blur-[110px]" />
|
||||
|
||||
<div className="relative z-10 container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-4xl text-center">
|
||||
<div className="mb-6 inline-flex items-center gap-2 rounded-full bg-blue-100 px-3 py-1.5 text-sm font-semibold text-blue-700">
|
||||
<Building2 className="h-4 w-4" />
|
||||
Industry Solutions
|
||||
</div>
|
||||
<h1 className="text-4xl font-extrabold tracking-tight text-slate-900 md:text-6xl">
|
||||
QR Codes by Industry
|
||||
</h1>
|
||||
<p className="mx-auto mt-6 max-w-3xl text-lg leading-relaxed text-slate-600 md:text-xl">
|
||||
Explore industry-specific QR code strategies for hospitality, retail, healthcare, events, education, and professional services. Each page is built around real workflows like menu updates, review collection, guest WiFi, bookings, packaging, and offline campaign tracking.
|
||||
</p>
|
||||
|
||||
<div className="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
|
||||
<Link href="/dynamic-qr-code-generator">
|
||||
<Button size="lg" className="rounded-full bg-blue-600 px-8 py-6 text-base font-bold text-white hover:bg-blue-700">
|
||||
Create a Dynamic QR Code
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/qr-code-tracking">
|
||||
<Button variant="outline" size="lg" className="rounded-full px-8 py-6 text-base font-bold">
|
||||
See QR Tracking Features
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-b border-slate-100 bg-white py-8">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{categories.map((category) => (
|
||||
<a
|
||||
key={category.id}
|
||||
href={`#${category.id}`}
|
||||
className="rounded-full border border-slate-200 bg-slate-50 px-4 py-2 text-sm font-semibold text-slate-700 transition-colors hover:border-blue-200 hover:bg-blue-50 hover:text-blue-700"
|
||||
>
|
||||
{category.title}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{highlightedLinks.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className="group rounded-3xl border border-slate-200 bg-white p-6 shadow-sm transition-all hover:-translate-y-1 hover:border-blue-200 hover:shadow-lg"
|
||||
>
|
||||
<h2 className="text-lg font-bold text-slate-900 group-hover:text-blue-600">{item.title}</h2>
|
||||
<p className="mt-3 text-sm leading-relaxed text-slate-600">{item.description}</p>
|
||||
<div className="mt-4 inline-flex items-center text-sm font-semibold text-blue-600">
|
||||
Explore <ArrowRight className="ml-2 h-4 w-4" />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{categories.map((category) => {
|
||||
const entries = category.slugs
|
||||
.map(getIndustryBySlug)
|
||||
.filter((industry): industry is NonNullable<typeof industry> => Boolean(industry));
|
||||
|
||||
return (
|
||||
<section
|
||||
key={category.id}
|
||||
id={category.id}
|
||||
className="border-t border-slate-100 py-20 scroll-mt-28"
|
||||
>
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 max-w-3xl">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
|
||||
{category.title}
|
||||
</h2>
|
||||
<p className="mt-4 text-lg leading-relaxed text-slate-600">
|
||||
{category.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{entries.map((industry) => (
|
||||
<Link
|
||||
key={industry.slug}
|
||||
href={`/qr-code-for/${industry.slug}`}
|
||||
className="group flex flex-col rounded-3xl border border-slate-200 bg-white p-8 shadow-sm transition-all duration-300 hover:-translate-y-1 hover:border-blue-200 hover:shadow-xl"
|
||||
>
|
||||
<h3 className="text-2xl font-bold text-slate-900 transition-colors group-hover:text-blue-600">
|
||||
{industry.name}
|
||||
</h3>
|
||||
<p className="mt-3 flex-grow text-slate-600">
|
||||
{industry.metaDescription}
|
||||
</p>
|
||||
<ul className="mt-6 space-y-2">
|
||||
{industry.useCases.slice(0, 2).map((useCase) => (
|
||||
<li key={useCase} className="flex items-start gap-2 text-sm text-slate-500">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-blue-600" />
|
||||
<span>{useCase}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-8 flex items-center text-blue-600 font-semibold transition-transform group-hover:translate-x-1">
|
||||
Explore Solutions <ArrowRight className="ml-2 h-5 w-5" />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
|
||||
<section className="border-t border-slate-100 bg-slate-50 py-20">
|
||||
<div className="container mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-10 text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
</div>
|
||||
<div className="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm sm:p-10">
|
||||
<FAQSection items={hubFaq} title="" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { ArrowRight, Building2, CheckCircle2 } from "lucide-react";
|
||||
|
||||
import { FAQSection } from "@/components/aeo/FAQSection";
|
||||
import SeoJsonLd from "@/components/SeoJsonLd";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { industryPages } from "@/lib/industry-pages";
|
||||
import { breadcrumbSchema, faqPageSchema } from "@/lib/schema";
|
||||
|
||||
type IndustryCategory = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
slugs: string[];
|
||||
};
|
||||
|
||||
const categories: IndustryCategory[] = [
|
||||
{
|
||||
id: "food-hospitality",
|
||||
title: "Food, Hospitality, and Guest Experience",
|
||||
description: "Use QR codes to replace printed menus, simplify guest WiFi, speed up bookings, and keep in-venue information current without reprinting.",
|
||||
slugs: [
|
||||
"restaurants",
|
||||
"cafes",
|
||||
"hotels",
|
||||
"bars",
|
||||
"food-trucks",
|
||||
"bakeries",
|
||||
"breweries",
|
||||
"nightclubs",
|
||||
"catering",
|
||||
"wineries",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "health-lifestyle",
|
||||
title: "Health, Wellness, and Personal Services",
|
||||
description: "Support appointment booking, aftercare, digital treatment menus, and high-intent follow-up flows across health and lifestyle businesses.",
|
||||
slugs: [
|
||||
"gyms",
|
||||
"doctors-dentists",
|
||||
"dentists",
|
||||
"yoga-studios",
|
||||
"spas",
|
||||
"beauty-salons",
|
||||
"barbershops",
|
||||
"nail-salons",
|
||||
"tattoo-studios",
|
||||
"pharmacies",
|
||||
"pet-grooming",
|
||||
"veterinarians",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "retail-commerce",
|
||||
title: "Retail, Packaging, and Product Discovery",
|
||||
description: "Bridge shelf, packaging, and in-store discovery with QR codes for demos, manuals, size guides, reviews, and product detail pages.",
|
||||
slugs: [
|
||||
"retail",
|
||||
"clothing-stores",
|
||||
"car-dealerships",
|
||||
"florists",
|
||||
"pet-stores",
|
||||
"electronics-stores",
|
||||
"jewelry-stores",
|
||||
"hardware-stores",
|
||||
"bookstores",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "education-culture-public",
|
||||
title: "Education, Culture, and Public Spaces",
|
||||
description: "Improve access to maps, resources, ticketing, audio guides, and visitor information across campuses, venues, and cultural institutions.",
|
||||
slugs: [
|
||||
"universities",
|
||||
"schools",
|
||||
"museums",
|
||||
"libraries",
|
||||
"theaters",
|
||||
"cinemas",
|
||||
"churches",
|
||||
"art-galleries",
|
||||
"stadiums",
|
||||
"airports",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "services-b2b-events",
|
||||
title: "Services, Real Estate, and Lead Capture",
|
||||
description: "Turn printed materials into measurable lead-gen assets for agencies, property businesses, events, and professional service firms.",
|
||||
slugs: [
|
||||
"real-estate",
|
||||
"events",
|
||||
"wedding-planners",
|
||||
"photographers",
|
||||
"trade-shows",
|
||||
"law-firms",
|
||||
"accountants",
|
||||
"insurance-agencies",
|
||||
"travel-agencies",
|
||||
"coworking-spaces",
|
||||
"property-management",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const hubFaq = [
|
||||
{
|
||||
question: "Which industries get the most value from dynamic QR codes?",
|
||||
answer:
|
||||
"Industries that print menus, flyers, labels, in-store signage, or operational materials usually benefit most because they can update destinations without reprinting every asset.",
|
||||
},
|
||||
{
|
||||
question: "Should industry QR code pages focus on tools or workflows?",
|
||||
answer:
|
||||
"Both matter, but workflow intent usually ranks better. Searchers often want a job-to-be-done answer first, then the right QR code tool to implement it.",
|
||||
},
|
||||
{
|
||||
question: "Are industry-specific QR code pages good for SEO?",
|
||||
answer:
|
||||
"Yes, when each page targets a real search intent, has unique examples, and connects to a relevant next step such as tracking, dynamic QR management, or the matching QR tool.",
|
||||
},
|
||||
{
|
||||
question: "What makes a QR code industry page convert well?",
|
||||
answer:
|
||||
"Clear use cases, realistic placement advice, trust-building FAQs, and a CTA that matches the industry workflow outperform generic sign-up prompts.",
|
||||
},
|
||||
];
|
||||
|
||||
const highlightedLinks = [
|
||||
{ href: "/dynamic-qr-code-generator", title: "Dynamic QR Code Generator", description: "Best next step for menus, signage, packaging, and any printed asset that may change later." },
|
||||
{ href: "/qr-code-tracking", title: "QR Code Tracking", description: "Measure scans from flyers, property signage, retail displays, trade shows, and offline campaigns." },
|
||||
{ href: "/bulk-qr-code-generator", title: "Bulk QR Code Generator", description: "Best fit for packaging, product lines, multi-location rollouts, and large campaigns." },
|
||||
{ href: "/tools/wifi-qr-code", title: "WiFi QR Code Tool", description: "Popular for hotels, cafes, coworking spaces, clinics, and guest-facing venues." },
|
||||
{ href: "/tools/teams-qr-code", title: "Teams QR Code Tool", description: "Useful for offices, meeting rooms, coworking spaces, and event check-in workflows." },
|
||||
{ href: "/qr-code-for/barbershops", title: "QR Codes for Barbershops", description: "A high-intent local service page for bookings, reviews, WiFi, and social profile QR codes." },
|
||||
];
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "QR Codes by Industry: Use Cases and Examples",
|
||||
description:
|
||||
"Explore QR code ideas by industry, including restaurants, hotels, retail, healthcare, events, education, and professional services. Find use cases, examples, and the best QR workflow for your business.",
|
||||
alternates: {
|
||||
canonical: "https://www.qrmaster.net/qr-code-for",
|
||||
},
|
||||
openGraph: {
|
||||
title: "QR Codes by Industry | QR Master",
|
||||
description:
|
||||
"Find industry-specific QR code use cases, workflows, and examples for hospitality, retail, healthcare, events, education, and more.",
|
||||
url: "https://www.qrmaster.net/qr-code-for",
|
||||
type: "website",
|
||||
images: ["/og-image.png"],
|
||||
},
|
||||
twitter: {
|
||||
title: "QR Codes by Industry | QR Master",
|
||||
description:
|
||||
"Industry-specific QR code ideas and workflows for restaurants, hotels, retail, events, healthcare, and more.",
|
||||
},
|
||||
};
|
||||
|
||||
function getIndustryBySlug(slug: string) {
|
||||
return industryPages.find((industry) => industry.slug === slug);
|
||||
}
|
||||
|
||||
export default function IndustryOverviewPage() {
|
||||
const itemListSchema = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "CollectionPage",
|
||||
name: "QR Codes by Industry",
|
||||
url: "https://www.qrmaster.net/qr-code-for",
|
||||
mainEntity: {
|
||||
"@type": "ItemList",
|
||||
itemListElement: industryPages.map((industry, index) => ({
|
||||
"@type": "ListItem",
|
||||
position: index + 1,
|
||||
url: `https://www.qrmaster.net/qr-code-for/${industry.slug}`,
|
||||
name: industry.title,
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd
|
||||
data={[
|
||||
itemListSchema,
|
||||
breadcrumbSchema([
|
||||
{ name: "Home", url: "/" },
|
||||
{ name: "QR Codes by Industry", url: "/qr-code-for" },
|
||||
]),
|
||||
faqPageSchema(hubFaq),
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="min-h-screen bg-white text-slate-900 font-sans">
|
||||
<section className="relative overflow-hidden border-b border-slate-100 bg-slate-50/70 pt-24 pb-20 lg:pt-32 lg:pb-24">
|
||||
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_right,#cbd5e1_1px,transparent_1px),linear-gradient(to_bottom,#cbd5e1_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_85%_75%_at_50%_0%,#000_70%,transparent_110%)] opacity-25" />
|
||||
<div className="pointer-events-none absolute top-0 right-0 h-[28rem] w-[28rem] rounded-full bg-blue-200/40 blur-[110px]" />
|
||||
|
||||
<div className="relative z-10 container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-4xl text-center">
|
||||
<div className="mb-6 inline-flex items-center gap-2 rounded-full bg-blue-100 px-3 py-1.5 text-sm font-semibold text-blue-700">
|
||||
<Building2 className="h-4 w-4" />
|
||||
Industry Solutions
|
||||
</div>
|
||||
<h1 className="text-4xl font-extrabold tracking-tight text-slate-900 md:text-6xl">
|
||||
QR Codes by Industry
|
||||
</h1>
|
||||
<p className="mx-auto mt-6 max-w-3xl text-lg leading-relaxed text-slate-600 md:text-xl">
|
||||
Explore industry-specific QR code strategies for hospitality, retail, healthcare, events, education, and professional services. Each page is built around real workflows like menu updates, review collection, guest WiFi, bookings, packaging, and offline campaign tracking.
|
||||
</p>
|
||||
|
||||
<div className="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
|
||||
<Link href="/dynamic-qr-code-generator">
|
||||
<Button size="lg" className="rounded-full bg-blue-600 px-8 py-6 text-base font-bold text-white hover:bg-blue-700">
|
||||
Create a Dynamic QR Code
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/qr-code-tracking">
|
||||
<Button variant="outline" size="lg" className="rounded-full px-8 py-6 text-base font-bold">
|
||||
See QR Tracking Features
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-b border-slate-100 bg-white py-8">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{categories.map((category) => (
|
||||
<a
|
||||
key={category.id}
|
||||
href={`#${category.id}`}
|
||||
className="rounded-full border border-slate-200 bg-slate-50 px-4 py-2 text-sm font-semibold text-slate-700 transition-colors hover:border-blue-200 hover:bg-blue-50 hover:text-blue-700"
|
||||
>
|
||||
{category.title}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{highlightedLinks.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className="group rounded-3xl border border-slate-200 bg-white p-6 shadow-sm transition-all hover:-translate-y-1 hover:border-blue-200 hover:shadow-lg"
|
||||
>
|
||||
<h2 className="text-lg font-bold text-slate-900 group-hover:text-blue-600">{item.title}</h2>
|
||||
<p className="mt-3 text-sm leading-relaxed text-slate-600">{item.description}</p>
|
||||
<div className="mt-4 inline-flex items-center text-sm font-semibold text-blue-600">
|
||||
Explore <ArrowRight className="ml-2 h-4 w-4" />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{categories.map((category) => {
|
||||
const entries = category.slugs
|
||||
.map(getIndustryBySlug)
|
||||
.filter((industry): industry is NonNullable<typeof industry> => Boolean(industry));
|
||||
|
||||
return (
|
||||
<section
|
||||
key={category.id}
|
||||
id={category.id}
|
||||
className="border-t border-slate-100 py-20 scroll-mt-28"
|
||||
>
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 max-w-3xl">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
|
||||
{category.title}
|
||||
</h2>
|
||||
<p className="mt-4 text-lg leading-relaxed text-slate-600">
|
||||
{category.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{entries.map((industry) => (
|
||||
<Link
|
||||
key={industry.slug}
|
||||
href={`/qr-code-for/${industry.slug}`}
|
||||
className="group flex flex-col rounded-3xl border border-slate-200 bg-white p-8 shadow-sm transition-all duration-300 hover:-translate-y-1 hover:border-blue-200 hover:shadow-xl"
|
||||
>
|
||||
<h3 className="text-2xl font-bold text-slate-900 transition-colors group-hover:text-blue-600">
|
||||
{industry.name}
|
||||
</h3>
|
||||
<p className="mt-3 flex-grow text-slate-600">
|
||||
{industry.metaDescription}
|
||||
</p>
|
||||
<ul className="mt-6 space-y-2">
|
||||
{industry.useCases.slice(0, 2).map((useCase) => (
|
||||
<li key={useCase} className="flex items-start gap-2 text-sm text-slate-500">
|
||||
<CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-blue-600" />
|
||||
<span>{useCase}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-8 flex items-center text-blue-600 font-semibold transition-transform group-hover:translate-x-1">
|
||||
Explore Solutions <ArrowRight className="ml-2 h-5 w-5" />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
|
||||
<section className="border-t border-slate-100 bg-slate-50 py-20">
|
||||
<div className="container mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-10 text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
</div>
|
||||
<div className="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm sm:p-10">
|
||||
<FAQSection items={hubFaq} title="" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,321 +1,321 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import Breadcrumbs from '@/components/Breadcrumbs';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'QR Code Print Size Guide: Minimum Sizes for Every Surface | QR Master',
|
||||
description: 'The minimum QR code size for print depends on scan distance. This guide gives you exact dimensions for business cards, flyers, posters, and billboards.',
|
||||
keywords: ['minimum qr code size', 'qr code print size', 'qr code size for print', 'how big should a qr code be', 'qr code dimensions'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/qr-code-print-size-guide',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'QR Code Print Size Guide | QR Master',
|
||||
description: 'Exact QR code dimensions for every print surface — from business cards to billboards.',
|
||||
type: 'article',
|
||||
url: 'https://www.qrmaster.net/qr-code-print-size-guide',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
const breadcrumbs = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'QR Code Print Size Guide', url: '/qr-code-print-size-guide' },
|
||||
];
|
||||
|
||||
const articleSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Article',
|
||||
'@id': 'https://www.qrmaster.net/qr-code-print-size-guide#article',
|
||||
headline: 'QR Code Print Size Guide: Minimum Sizes for Every Surface',
|
||||
description: 'The minimum QR code size for print depends on scan distance. This guide gives you exact dimensions for business cards, flyers, posters, and billboards.',
|
||||
author: {
|
||||
'@type': 'Organization',
|
||||
name: 'QR Master',
|
||||
url: 'https://www.qrmaster.net',
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'QR Master',
|
||||
url: 'https://www.qrmaster.net',
|
||||
},
|
||||
datePublished: '2026-04-07',
|
||||
dateModified: '2026-04-07',
|
||||
mainEntityOfPage: 'https://www.qrmaster.net/qr-code-print-size-guide',
|
||||
};
|
||||
|
||||
const faqSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: [
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'What is the minimum size for a QR code to be scannable?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'The minimum recommended size is 2 × 2 cm (0.8 × 0.8 in) for QR codes scanned at very close range (under 10 cm), such as on business cards. For most print materials scanned at arm\'s length (30–40 cm), use at least 3 × 3 cm.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'What DPI should I use for printing a QR code?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'For print, export your QR code at a minimum of 300 DPI. For large-format printing (posters, banners), use the vector SVG format instead of PNG — SVG scales to any size without quality loss.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'How big should a QR code be on a poster?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'For an A2 poster (420 × 594 mm) viewed at 1–2 metres, use a QR code that is at least 8 × 8 cm. Larger is always better — a 10 × 10 cm code scans reliably from 2–3 metres away.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'How big should a QR code be on a business card?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'On a standard business card (85 × 55 mm), a QR code of 2 × 2 cm to 2.5 × 2.5 cm works reliably when held at normal reading distance (15–20 cm).',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'Does adding a logo to a QR code affect scannability?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'Only if the logo covers more than about 30% of the QR code area. QR codes have built-in error correction. At "Q" (25%) or "H" (30%) error correction level, a logo is fine. Always test scan after adding a logo.',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const sizeData = [
|
||||
{ surface: 'Business Card', scanDistance: '15–20 cm', minSize: '2 × 2 cm', recommended: '2.5 × 2.5 cm', dpi: '300 DPI PNG', format: 'PNG or SVG' },
|
||||
{ surface: 'Flyer / Leaflet', scanDistance: '20–30 cm', minSize: '3 × 3 cm', recommended: '4 × 4 cm', dpi: '300 DPI PNG', format: 'PNG or SVG' },
|
||||
{ surface: 'A4 Poster', scanDistance: '30–60 cm', minSize: '5 × 5 cm', recommended: '7 × 7 cm', dpi: '300 DPI PNG', format: 'SVG recommended' },
|
||||
{ surface: 'A2 / A1 Poster', scanDistance: '1–2 m', minSize: '8 × 8 cm', recommended: '10 × 10 cm', dpi: '300 DPI', format: 'SVG required' },
|
||||
{ surface: 'Window / Banner', scanDistance: '1–3 m', minSize: '10 × 10 cm', recommended: '15 × 15 cm', dpi: 'Vector only', format: 'SVG required' },
|
||||
{ surface: 'Billboard', scanDistance: '3–10 m', minSize: '20 × 20 cm', recommended: '30 × 30 cm', dpi: 'Vector only', format: 'SVG required' },
|
||||
];
|
||||
|
||||
export default function QRCodePrintSizeGuidePage() {
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[articleSchema, faqSchema, breadcrumbSchema(breadcrumbs)]} />
|
||||
|
||||
<div className="min-h-screen bg-white">
|
||||
|
||||
{/* HERO */}
|
||||
<section className="pt-20 pb-12 bg-gradient-to-br from-slate-900 to-slate-700 text-white">
|
||||
<div className="container mx-auto px-4 max-w-4xl">
|
||||
<Breadcrumbs items={breadcrumbs} className="[&_ol]:text-white/70 [&_span.font-semibold]:text-white [&_a]:text-white/70 [&_span]:text-white/70" />
|
||||
<div className="mt-6 inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/80 text-sm font-medium mb-6 border border-white/10">
|
||||
Print Guide
|
||||
</div>
|
||||
<h1 className="text-4xl lg:text-5xl font-black mb-6 leading-tight">
|
||||
QR Code Print Size Guide
|
||||
</h1>
|
||||
<p className="text-xl text-slate-300 max-w-2xl leading-relaxed mb-4">
|
||||
The right QR code size depends on one thing: how far away will the scanner be?
|
||||
This guide gives you exact minimum dimensions for every print surface.
|
||||
</p>
|
||||
<div className="bg-white/10 border border-white/20 rounded-xl p-4 mt-6 max-w-xl">
|
||||
<p className="text-sm font-semibold text-white mb-1">Quick Rule of Thumb</p>
|
||||
<p className="text-slate-300 text-sm">
|
||||
QR code width = scan distance ÷ 10. Example: scanning from 30 cm → minimum 3 cm wide.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* SIZE TABLE */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<h2 className="text-2xl font-bold text-slate-900 mb-8">
|
||||
Recommended Size by Print Surface and Scan Distance
|
||||
</h2>
|
||||
|
||||
<div className="overflow-x-auto rounded-2xl border border-slate-200 shadow-sm">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-slate-900 text-white">
|
||||
<th className="px-5 py-4 text-left font-semibold">Surface</th>
|
||||
<th className="px-5 py-4 text-left font-semibold">Scan Distance</th>
|
||||
<th className="px-5 py-4 text-left font-semibold">Minimum Size</th>
|
||||
<th className="px-5 py-4 text-left font-semibold">Recommended</th>
|
||||
<th className="px-5 py-4 text-left font-semibold">Format</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sizeData.map((row, i) => (
|
||||
<tr key={row.surface} className={i % 2 === 0 ? 'bg-white' : 'bg-slate-50'}>
|
||||
<td className="px-5 py-4 font-semibold text-slate-900">{row.surface}</td>
|
||||
<td className="px-5 py-4 text-slate-600">{row.scanDistance}</td>
|
||||
<td className="px-5 py-4 text-red-700 font-semibold">{row.minSize}</td>
|
||||
<td className="px-5 py-4 text-green-700 font-semibold">{row.recommended}</td>
|
||||
<td className="px-5 py-4 text-slate-600">{row.format}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p className="mt-4 text-xs text-slate-500">
|
||||
Sizes are for QR codes with standard black-on-white contrast at error correction level Q or H. Reduce minimum sizes by 20% only in ideal lighting conditions.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* DPI & FORMAT GUIDE */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-slate-50 border-y border-slate-200">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-2xl font-bold text-slate-900 mb-8">DPI and Format Requirements</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<div className="bg-white p-6 rounded-2xl border border-slate-100 shadow-sm">
|
||||
<div className="w-10 h-10 rounded-xl bg-blue-100 flex items-center justify-center mb-4">
|
||||
<span className="font-bold text-blue-700 text-sm">PNG</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">PNG for Small Print</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">
|
||||
Use PNG at <strong>300 DPI minimum</strong> for business cards, flyers, and small posters. Export at 10× your target print size in pixels (e.g. 3 cm at 300 DPI = 354 px).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-2xl border border-emerald-100 shadow-sm ring-1 ring-emerald-200">
|
||||
<div className="w-10 h-10 rounded-xl bg-emerald-100 flex items-center justify-center mb-4">
|
||||
<span className="font-bold text-emerald-700 text-sm">SVG</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">SVG for Everything Larger</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">
|
||||
SVG is a vector format — it scales to any size without quality loss. Use SVG for all A2+ posters, banners, and billboards. QR Master exports SVG directly from the generator.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-2xl border border-slate-100 shadow-sm">
|
||||
<div className="w-10 h-10 rounded-xl bg-amber-100 flex items-center justify-center mb-4">
|
||||
<span className="font-bold text-amber-700 text-sm">ECL</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">Error Correction Level</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">
|
||||
Use <strong>Level Q</strong> (25% recovery) or <strong>Level H</strong> (30% recovery) for print — especially if you add a logo. Higher error correction means more modules and a slightly larger minimum size.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* PRINT CHECKLIST */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-2xl font-bold text-slate-900 mb-8">Print QR Code Checklist</h2>
|
||||
<ul className="space-y-3">
|
||||
{[
|
||||
'QR code is at least the minimum size for the intended scan distance',
|
||||
'High contrast between QR modules and background (black on white preferred)',
|
||||
'Quiet zone (white border) of at least 4 module widths on all sides',
|
||||
'Exported as SVG for large format, PNG at 300+ DPI for small format',
|
||||
'Scanned from target distance before sending to print',
|
||||
'Error correction set to Q or H if a logo is embedded',
|
||||
'Dynamic QR code used if the destination URL might change',
|
||||
].map((item) => (
|
||||
<li key={item} className="flex items-start gap-3">
|
||||
<span className="mt-0.5 w-5 h-5 rounded-full bg-green-100 flex items-center justify-center shrink-0">
|
||||
<svg className="w-3 h-3 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</span>
|
||||
<span className="text-slate-700 text-sm leading-relaxed">{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<GrowthLinksSection
|
||||
eyebrow="Ready to print?"
|
||||
title="Create your print-ready QR code"
|
||||
description="Generate, customize, and download QR codes in SVG or PNG at any size you need."
|
||||
links={[
|
||||
{
|
||||
href: '/reprint-calculator',
|
||||
title: 'Reprint Cost Calculator',
|
||||
description: 'Calculate how much static reprints are costing vs one dynamic QR subscription.',
|
||||
ctaLabel: 'Calculate reprint savings',
|
||||
},
|
||||
{
|
||||
href: '/dynamic-qr-code-generator',
|
||||
title: 'Dynamic QR Code Generator',
|
||||
description: 'Create QR codes you can update after printing. No reprint needed when links change.',
|
||||
ctaLabel: 'Create dynamic QR code',
|
||||
},
|
||||
{
|
||||
href: '/custom-qr-code-generator',
|
||||
title: 'Custom QR Code Generator',
|
||||
description: 'Add your logo, brand colors, and a frame to create print-ready branded QR codes.',
|
||||
ctaLabel: 'Create custom QR code',
|
||||
},
|
||||
]}
|
||||
pageType="commercial"
|
||||
cluster="print-size-guide"
|
||||
/>
|
||||
|
||||
{/* FAQ */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-slate-50">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-2xl font-bold text-slate-900 text-center mb-10">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{
|
||||
question: 'What is the minimum size for a QR code to be scannable?',
|
||||
answer: 'The absolute minimum is 2 × 2 cm for scanning at very close range (business cards, held 15 cm away). For most materials, use at least 3 × 3 cm. The general rule: minimum width = scan distance ÷ 10.',
|
||||
},
|
||||
{
|
||||
question: 'What DPI should I use for printing a QR code?',
|
||||
answer: 'Use 300 DPI minimum for all print. For large format (posters, banners, billboards), always use SVG — it is vector-based and scales to any size without quality loss.',
|
||||
},
|
||||
{
|
||||
question: 'How big should a QR code be on a poster?',
|
||||
answer: 'For an A2 poster viewed at 1–2 metres, use at least 8 × 8 cm. For A1 posters viewed from further away, 10 × 10 cm is recommended.',
|
||||
},
|
||||
{
|
||||
question: 'How big should a QR code be on a business card?',
|
||||
answer: 'On a standard business card (85 × 55 mm), a 2 × 2 cm to 2.5 × 2.5 cm QR code works reliably when scanned at normal reading distance.',
|
||||
},
|
||||
{
|
||||
question: 'Does adding a logo affect QR code scannability?',
|
||||
answer: 'Only if the logo covers more than ~30% of the QR area. Use error correction level Q or H when adding logos. Always test scan before printing at scale.',
|
||||
},
|
||||
].map((item) => (
|
||||
<details key={item.question} className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{item.question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{item.answer}
|
||||
</div>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import Breadcrumbs from '@/components/Breadcrumbs';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'QR Code Print Size Guide: Minimum Sizes for Every Surface | QR Master',
|
||||
description: 'The minimum QR code size for print depends on scan distance. This guide gives you exact dimensions for business cards, flyers, posters, and billboards.',
|
||||
keywords: ['minimum qr code size', 'qr code print size', 'qr code size for print', 'how big should a qr code be', 'qr code dimensions'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/qr-code-print-size-guide',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'QR Code Print Size Guide | QR Master',
|
||||
description: 'Exact QR code dimensions for every print surface — from business cards to billboards.',
|
||||
type: 'article',
|
||||
url: 'https://www.qrmaster.net/qr-code-print-size-guide',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
const breadcrumbs = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'QR Code Print Size Guide', url: '/qr-code-print-size-guide' },
|
||||
];
|
||||
|
||||
const articleSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Article',
|
||||
'@id': 'https://www.qrmaster.net/qr-code-print-size-guide#article',
|
||||
headline: 'QR Code Print Size Guide: Minimum Sizes for Every Surface',
|
||||
description: 'The minimum QR code size for print depends on scan distance. This guide gives you exact dimensions for business cards, flyers, posters, and billboards.',
|
||||
author: {
|
||||
'@type': 'Organization',
|
||||
name: 'QR Master',
|
||||
url: 'https://www.qrmaster.net',
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'QR Master',
|
||||
url: 'https://www.qrmaster.net',
|
||||
},
|
||||
datePublished: '2026-04-07',
|
||||
dateModified: '2026-04-07',
|
||||
mainEntityOfPage: 'https://www.qrmaster.net/qr-code-print-size-guide',
|
||||
};
|
||||
|
||||
const faqSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: [
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'What is the minimum size for a QR code to be scannable?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'The minimum recommended size is 2 × 2 cm (0.8 × 0.8 in) for QR codes scanned at very close range (under 10 cm), such as on business cards. For most print materials scanned at arm\'s length (30–40 cm), use at least 3 × 3 cm.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'What DPI should I use for printing a QR code?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'For print, export your QR code at a minimum of 300 DPI. For large-format printing (posters, banners), use the vector SVG format instead of PNG — SVG scales to any size without quality loss.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'How big should a QR code be on a poster?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'For an A2 poster (420 × 594 mm) viewed at 1–2 metres, use a QR code that is at least 8 × 8 cm. Larger is always better — a 10 × 10 cm code scans reliably from 2–3 metres away.',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'How big should a QR code be on a business card?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'On a standard business card (85 × 55 mm), a QR code of 2 × 2 cm to 2.5 × 2.5 cm works reliably when held at normal reading distance (15–20 cm).',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: 'Does adding a logo to a QR code affect scannability?',
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: 'Only if the logo covers more than about 30% of the QR code area. QR codes have built-in error correction. At "Q" (25%) or "H" (30%) error correction level, a logo is fine. Always test scan after adding a logo.',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const sizeData = [
|
||||
{ surface: 'Business Card', scanDistance: '15–20 cm', minSize: '2 × 2 cm', recommended: '2.5 × 2.5 cm', dpi: '300 DPI PNG', format: 'PNG or SVG' },
|
||||
{ surface: 'Flyer / Leaflet', scanDistance: '20–30 cm', minSize: '3 × 3 cm', recommended: '4 × 4 cm', dpi: '300 DPI PNG', format: 'PNG or SVG' },
|
||||
{ surface: 'A4 Poster', scanDistance: '30–60 cm', minSize: '5 × 5 cm', recommended: '7 × 7 cm', dpi: '300 DPI PNG', format: 'SVG recommended' },
|
||||
{ surface: 'A2 / A1 Poster', scanDistance: '1–2 m', minSize: '8 × 8 cm', recommended: '10 × 10 cm', dpi: '300 DPI', format: 'SVG required' },
|
||||
{ surface: 'Window / Banner', scanDistance: '1–3 m', minSize: '10 × 10 cm', recommended: '15 × 15 cm', dpi: 'Vector only', format: 'SVG required' },
|
||||
{ surface: 'Billboard', scanDistance: '3–10 m', minSize: '20 × 20 cm', recommended: '30 × 30 cm', dpi: 'Vector only', format: 'SVG required' },
|
||||
];
|
||||
|
||||
export default function QRCodePrintSizeGuidePage() {
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[articleSchema, faqSchema, breadcrumbSchema(breadcrumbs)]} />
|
||||
|
||||
<div className="min-h-screen bg-white">
|
||||
|
||||
{/* HERO */}
|
||||
<section className="pt-20 pb-12 bg-gradient-to-br from-slate-900 to-slate-700 text-white">
|
||||
<div className="container mx-auto px-4 max-w-4xl">
|
||||
<Breadcrumbs items={breadcrumbs} className="[&_ol]:text-white/70 [&_span.font-semibold]:text-white [&_a]:text-white/70 [&_span]:text-white/70" />
|
||||
<div className="mt-6 inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/80 text-sm font-medium mb-6 border border-white/10">
|
||||
Print Guide
|
||||
</div>
|
||||
<h1 className="text-4xl lg:text-5xl font-black mb-6 leading-tight">
|
||||
QR Code Print Size Guide
|
||||
</h1>
|
||||
<p className="text-xl text-slate-300 max-w-2xl leading-relaxed mb-4">
|
||||
The right QR code size depends on one thing: how far away will the scanner be?
|
||||
This guide gives you exact minimum dimensions for every print surface.
|
||||
</p>
|
||||
<div className="bg-white/10 border border-white/20 rounded-xl p-4 mt-6 max-w-xl">
|
||||
<p className="text-sm font-semibold text-white mb-1">Quick Rule of Thumb</p>
|
||||
<p className="text-slate-300 text-sm">
|
||||
QR code width = scan distance ÷ 10. Example: scanning from 30 cm → minimum 3 cm wide.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* SIZE TABLE */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<h2 className="text-2xl font-bold text-slate-900 mb-8">
|
||||
Recommended Size by Print Surface and Scan Distance
|
||||
</h2>
|
||||
|
||||
<div className="overflow-x-auto rounded-2xl border border-slate-200 shadow-sm">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-slate-900 text-white">
|
||||
<th className="px-5 py-4 text-left font-semibold">Surface</th>
|
||||
<th className="px-5 py-4 text-left font-semibold">Scan Distance</th>
|
||||
<th className="px-5 py-4 text-left font-semibold">Minimum Size</th>
|
||||
<th className="px-5 py-4 text-left font-semibold">Recommended</th>
|
||||
<th className="px-5 py-4 text-left font-semibold">Format</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sizeData.map((row, i) => (
|
||||
<tr key={row.surface} className={i % 2 === 0 ? 'bg-white' : 'bg-slate-50'}>
|
||||
<td className="px-5 py-4 font-semibold text-slate-900">{row.surface}</td>
|
||||
<td className="px-5 py-4 text-slate-600">{row.scanDistance}</td>
|
||||
<td className="px-5 py-4 text-red-700 font-semibold">{row.minSize}</td>
|
||||
<td className="px-5 py-4 text-green-700 font-semibold">{row.recommended}</td>
|
||||
<td className="px-5 py-4 text-slate-600">{row.format}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p className="mt-4 text-xs text-slate-500">
|
||||
Sizes are for QR codes with standard black-on-white contrast at error correction level Q or H. Reduce minimum sizes by 20% only in ideal lighting conditions.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* DPI & FORMAT GUIDE */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-slate-50 border-y border-slate-200">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-2xl font-bold text-slate-900 mb-8">DPI and Format Requirements</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<div className="bg-white p-6 rounded-2xl border border-slate-100 shadow-sm">
|
||||
<div className="w-10 h-10 rounded-xl bg-blue-100 flex items-center justify-center mb-4">
|
||||
<span className="font-bold text-blue-700 text-sm">PNG</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">PNG for Small Print</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">
|
||||
Use PNG at <strong>300 DPI minimum</strong> for business cards, flyers, and small posters. Export at 10× your target print size in pixels (e.g. 3 cm at 300 DPI = 354 px).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-2xl border border-emerald-100 shadow-sm ring-1 ring-emerald-200">
|
||||
<div className="w-10 h-10 rounded-xl bg-emerald-100 flex items-center justify-center mb-4">
|
||||
<span className="font-bold text-emerald-700 text-sm">SVG</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">SVG for Everything Larger</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">
|
||||
SVG is a vector format — it scales to any size without quality loss. Use SVG for all A2+ posters, banners, and billboards. QR Master exports SVG directly from the generator.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-2xl border border-slate-100 shadow-sm">
|
||||
<div className="w-10 h-10 rounded-xl bg-amber-100 flex items-center justify-center mb-4">
|
||||
<span className="font-bold text-amber-700 text-sm">ECL</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">Error Correction Level</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">
|
||||
Use <strong>Level Q</strong> (25% recovery) or <strong>Level H</strong> (30% recovery) for print — especially if you add a logo. Higher error correction means more modules and a slightly larger minimum size.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* PRINT CHECKLIST */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-2xl font-bold text-slate-900 mb-8">Print QR Code Checklist</h2>
|
||||
<ul className="space-y-3">
|
||||
{[
|
||||
'QR code is at least the minimum size for the intended scan distance',
|
||||
'High contrast between QR modules and background (black on white preferred)',
|
||||
'Quiet zone (white border) of at least 4 module widths on all sides',
|
||||
'Exported as SVG for large format, PNG at 300+ DPI for small format',
|
||||
'Scanned from target distance before sending to print',
|
||||
'Error correction set to Q or H if a logo is embedded',
|
||||
'Dynamic QR code used if the destination URL might change',
|
||||
].map((item) => (
|
||||
<li key={item} className="flex items-start gap-3">
|
||||
<span className="mt-0.5 w-5 h-5 rounded-full bg-green-100 flex items-center justify-center shrink-0">
|
||||
<svg className="w-3 h-3 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</span>
|
||||
<span className="text-slate-700 text-sm leading-relaxed">{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<GrowthLinksSection
|
||||
eyebrow="Ready to print?"
|
||||
title="Create your print-ready QR code"
|
||||
description="Generate, customize, and download QR codes in SVG or PNG at any size you need."
|
||||
links={[
|
||||
{
|
||||
href: '/reprint-calculator',
|
||||
title: 'Reprint Cost Calculator',
|
||||
description: 'Calculate how much static reprints are costing vs one dynamic QR subscription.',
|
||||
ctaLabel: 'Calculate reprint savings',
|
||||
},
|
||||
{
|
||||
href: '/dynamic-qr-code-generator',
|
||||
title: 'Dynamic QR Code Generator',
|
||||
description: 'Create QR codes you can update after printing. No reprint needed when links change.',
|
||||
ctaLabel: 'Create dynamic QR code',
|
||||
},
|
||||
{
|
||||
href: '/custom-qr-code-generator',
|
||||
title: 'Custom QR Code Generator',
|
||||
description: 'Add your logo, brand colors, and a frame to create print-ready branded QR codes.',
|
||||
ctaLabel: 'Create custom QR code',
|
||||
},
|
||||
]}
|
||||
pageType="commercial"
|
||||
cluster="print-size-guide"
|
||||
/>
|
||||
|
||||
{/* FAQ */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-slate-50">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-2xl font-bold text-slate-900 text-center mb-10">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{
|
||||
question: 'What is the minimum size for a QR code to be scannable?',
|
||||
answer: 'The absolute minimum is 2 × 2 cm for scanning at very close range (business cards, held 15 cm away). For most materials, use at least 3 × 3 cm. The general rule: minimum width = scan distance ÷ 10.',
|
||||
},
|
||||
{
|
||||
question: 'What DPI should I use for printing a QR code?',
|
||||
answer: 'Use 300 DPI minimum for all print. For large format (posters, banners, billboards), always use SVG — it is vector-based and scales to any size without quality loss.',
|
||||
},
|
||||
{
|
||||
question: 'How big should a QR code be on a poster?',
|
||||
answer: 'For an A2 poster viewed at 1–2 metres, use at least 8 × 8 cm. For A1 posters viewed from further away, 10 × 10 cm is recommended.',
|
||||
},
|
||||
{
|
||||
question: 'How big should a QR code be on a business card?',
|
||||
answer: 'On a standard business card (85 × 55 mm), a 2 × 2 cm to 2.5 × 2.5 cm QR code works reliably when scanned at normal reading distance.',
|
||||
},
|
||||
{
|
||||
question: 'Does adding a logo affect QR code scannability?',
|
||||
answer: 'Only if the logo covers more than ~30% of the QR area. Use error correction level Q or H when adding logos. Always test scan before printing at scale.',
|
||||
},
|
||||
].map((item) => (
|
||||
<details key={item.question} className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{item.question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{item.answer}
|
||||
</div>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,219 +1,219 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { toPng } from 'html-to-image';
|
||||
import { Star, Download, AlertCircle } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||
|
||||
const QR_COLORS = [
|
||||
{ name: 'Google Blue', value: '#1A73E8' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Indigo', value: '#4F46E5' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
{ name: 'Violet', value: '#7C3AED' },
|
||||
];
|
||||
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'review', label: 'Leave a Review' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'feedback', label: 'Share Feedback' },
|
||||
];
|
||||
|
||||
function isValidGoogleReviewLink(url: string): boolean {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
return (
|
||||
parsed.hostname.includes('google.com') ||
|
||||
parsed.hostname.includes('g.page') ||
|
||||
parsed.hostname.includes('maps.app.goo.gl')
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default function GoogleReviewGenerator() {
|
||||
const [reviewUrl, setReviewUrl] = useState('');
|
||||
const [qrColor, setQrColor] = useState('#1A73E8');
|
||||
const [frameType, setFrameType] = useState('review');
|
||||
const [error, setError] = useState('');
|
||||
const [showPopup, setShowPopup] = useState(false);
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleUrlChange = (value: string) => {
|
||||
setReviewUrl(value);
|
||||
if (value && !isValidGoogleReviewLink(value)) {
|
||||
setError('Please enter a valid Google Review link (google.com, g.page, or maps.app.goo.gl)');
|
||||
} else {
|
||||
setError('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = 'google-review-qr-code.png';
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'google-review-qr-code.svg';
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||
};
|
||||
|
||||
const frameLabel = FRAME_OPTIONS.find(f => f.id === frameType && f.id !== 'none')?.label ?? null;
|
||||
const isReady = reviewUrl && !error && isValidGoogleReviewLink(reviewUrl);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Star className="w-5 h-5 text-yellow-500" />
|
||||
Your Google Review Link
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<Input
|
||||
type="url"
|
||||
placeholder="https://g.page/r/YOUR_ID/review"
|
||||
value={reviewUrl}
|
||||
onChange={(e) => handleUrlChange(e.target.value)}
|
||||
className="w-full font-mono text-sm"
|
||||
/>
|
||||
{error && (
|
||||
<div className="mt-2 flex items-center gap-2 text-sm text-red-600">
|
||||
<AlertCircle className="w-4 h-4 shrink-0" />
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
{!reviewUrl && (
|
||||
<p className="mt-2 text-xs text-slate-500">
|
||||
Go to Google Maps → find your business → Share → Copy link
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-semibold text-slate-700">QR Color</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((color) => (
|
||||
<button
|
||||
key={color.value}
|
||||
onClick={() => setQrColor(color.value)}
|
||||
className={`w-8 h-8 rounded-full border-2 transition-all ${
|
||||
qrColor === color.value
|
||||
? 'border-slate-900 scale-110'
|
||||
: 'border-transparent hover:border-slate-300'
|
||||
}`}
|
||||
style={{ backgroundColor: color.value }}
|
||||
title={color.name}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-semibold text-slate-700">Frame Label</h3>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={`px-3 py-2 rounded-xl text-sm font-medium border transition-all ${
|
||||
frameType === frame.id
|
||||
? 'border-blue-600 bg-blue-50 text-blue-700'
|
||||
: 'border-slate-200 text-slate-600 hover:border-slate-300'
|
||||
}`}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="space-y-3">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
disabled={!isReady}
|
||||
className="w-full flex items-center justify-center gap-2"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<button
|
||||
onClick={() => handleDownload('svg')}
|
||||
disabled={!isReady}
|
||||
className="w-full px-4 py-2.5 rounded-xl border border-slate-200 text-slate-700 text-sm font-medium hover:border-slate-300 hover:bg-slate-50 transition-all disabled:opacity-40 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Download SVG
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center bg-slate-50">
|
||||
<h3 className="text-sm font-semibold text-slate-500 uppercase tracking-wider mb-6">Preview</h3>
|
||||
|
||||
<div ref={qrRef} className="flex flex-col items-center bg-white p-6 rounded-2xl shadow-sm border border-slate-100">
|
||||
<QRCodeSVG
|
||||
value={isReady ? reviewUrl : 'https://www.qrmaster.net/tools/google-review-qr-code'}
|
||||
size={200}
|
||||
fgColor={qrColor}
|
||||
level="Q"
|
||||
includeMargin={true}
|
||||
/>
|
||||
{frameLabel && (
|
||||
<div
|
||||
className="mt-3 px-4 py-1.5 rounded-full text-sm font-semibold text-white"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{frameLabel}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isReady && (
|
||||
<p className="mt-4 text-xs text-slate-400 text-center max-w-[200px]">
|
||||
Enter your Google Review link to generate your QR code
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { toPng } from 'html-to-image';
|
||||
import { Star, Download, AlertCircle } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import PostDownloadPopup, { shouldShowDownloadPopup } from '@/components/marketing/PostDownloadPopup';
|
||||
|
||||
const QR_COLORS = [
|
||||
{ name: 'Google Blue', value: '#1A73E8' },
|
||||
{ name: 'Classic Black', value: '#000000' },
|
||||
{ name: 'Indigo', value: '#4F46E5' },
|
||||
{ name: 'Emerald', value: '#10B981' },
|
||||
{ name: 'Rose', value: '#F43F5E' },
|
||||
{ name: 'Violet', value: '#7C3AED' },
|
||||
];
|
||||
|
||||
const FRAME_OPTIONS = [
|
||||
{ id: 'none', label: 'No Frame' },
|
||||
{ id: 'review', label: 'Leave a Review' },
|
||||
{ id: 'scanme', label: 'Scan Me' },
|
||||
{ id: 'feedback', label: 'Share Feedback' },
|
||||
];
|
||||
|
||||
function isValidGoogleReviewLink(url: string): boolean {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
return (
|
||||
parsed.hostname.includes('google.com') ||
|
||||
parsed.hostname.includes('g.page') ||
|
||||
parsed.hostname.includes('maps.app.goo.gl')
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default function GoogleReviewGenerator() {
|
||||
const [reviewUrl, setReviewUrl] = useState('');
|
||||
const [qrColor, setQrColor] = useState('#1A73E8');
|
||||
const [frameType, setFrameType] = useState('review');
|
||||
const [error, setError] = useState('');
|
||||
const [showPopup, setShowPopup] = useState(false);
|
||||
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleUrlChange = (value: string) => {
|
||||
setReviewUrl(value);
|
||||
if (value && !isValidGoogleReviewLink(value)) {
|
||||
setError('Please enter a valid Google Review link (google.com, g.page, or maps.app.goo.gl)');
|
||||
} else {
|
||||
setError('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async (format: 'png' | 'svg') => {
|
||||
if (!qrRef.current) return;
|
||||
try {
|
||||
if (format === 'png') {
|
||||
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 });
|
||||
const link = document.createElement('a');
|
||||
link.download = 'google-review-qr-code.png';
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} else {
|
||||
const svgData = qrRef.current.querySelector('svg')?.outerHTML;
|
||||
if (svgData) {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'google-review-qr-code.svg';
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Download failed', err);
|
||||
}
|
||||
if (shouldShowDownloadPopup()) setShowPopup(true);
|
||||
};
|
||||
|
||||
const frameLabel = FRAME_OPTIONS.find(f => f.id === frameType && f.id !== 'none')?.label ?? null;
|
||||
const isReady = reviewUrl && !error && isValidGoogleReviewLink(reviewUrl);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PostDownloadPopup open={showPopup} onClose={() => setShowPopup(false)} />
|
||||
<div className="w-full max-w-5xl mx-auto px-4 md:px-6">
|
||||
<div className="bg-white rounded-3xl shadow-2xl shadow-slate-900/10 overflow-hidden border border-slate-100">
|
||||
<div className="grid lg:grid-cols-2">
|
||||
|
||||
{/* LEFT: Input */}
|
||||
<div className="p-6 md:p-8 lg:p-10 space-y-8 border-r border-slate-100">
|
||||
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-bold text-slate-900 flex items-center gap-2">
|
||||
<Star className="w-5 h-5 text-yellow-500" />
|
||||
Your Google Review Link
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<Input
|
||||
type="url"
|
||||
placeholder="https://g.page/r/YOUR_ID/review"
|
||||
value={reviewUrl}
|
||||
onChange={(e) => handleUrlChange(e.target.value)}
|
||||
className="w-full font-mono text-sm"
|
||||
/>
|
||||
{error && (
|
||||
<div className="mt-2 flex items-center gap-2 text-sm text-red-600">
|
||||
<AlertCircle className="w-4 h-4 shrink-0" />
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
{!reviewUrl && (
|
||||
<p className="mt-2 text-xs text-slate-500">
|
||||
Go to Google Maps → find your business → Share → Copy link
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Color Picker */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-semibold text-slate-700">QR Color</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{QR_COLORS.map((color) => (
|
||||
<button
|
||||
key={color.value}
|
||||
onClick={() => setQrColor(color.value)}
|
||||
className={`w-8 h-8 rounded-full border-2 transition-all ${
|
||||
qrColor === color.value
|
||||
? 'border-slate-900 scale-110'
|
||||
: 'border-transparent hover:border-slate-300'
|
||||
}`}
|
||||
style={{ backgroundColor: color.value }}
|
||||
title={color.name}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Frame */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-semibold text-slate-700">Frame Label</h3>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{FRAME_OPTIONS.map((frame) => (
|
||||
<button
|
||||
key={frame.id}
|
||||
onClick={() => setFrameType(frame.id)}
|
||||
className={`px-3 py-2 rounded-xl text-sm font-medium border transition-all ${
|
||||
frameType === frame.id
|
||||
? 'border-blue-600 bg-blue-50 text-blue-700'
|
||||
: 'border-slate-200 text-slate-600 hover:border-slate-300'
|
||||
}`}
|
||||
>
|
||||
{frame.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="space-y-3">
|
||||
<Button
|
||||
onClick={() => handleDownload('png')}
|
||||
disabled={!isReady}
|
||||
className="w-full flex items-center justify-center gap-2"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Download PNG
|
||||
</Button>
|
||||
<button
|
||||
onClick={() => handleDownload('svg')}
|
||||
disabled={!isReady}
|
||||
className="w-full px-4 py-2.5 rounded-xl border border-slate-200 text-slate-700 text-sm font-medium hover:border-slate-300 hover:bg-slate-50 transition-all disabled:opacity-40 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Download SVG
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Preview */}
|
||||
<div className="p-6 md:p-8 lg:p-10 flex flex-col items-center justify-center bg-slate-50">
|
||||
<h3 className="text-sm font-semibold text-slate-500 uppercase tracking-wider mb-6">Preview</h3>
|
||||
|
||||
<div ref={qrRef} className="flex flex-col items-center bg-white p-6 rounded-2xl shadow-sm border border-slate-100">
|
||||
<QRCodeSVG
|
||||
value={isReady ? reviewUrl : 'https://www.qrmaster.net/tools/google-review-qr-code'}
|
||||
size={200}
|
||||
fgColor={qrColor}
|
||||
level="Q"
|
||||
includeMargin={true}
|
||||
/>
|
||||
{frameLabel && (
|
||||
<div
|
||||
className="mt-3 px-4 py-1.5 rounded-full text-sm font-semibold text-white"
|
||||
style={{ backgroundColor: qrColor }}
|
||||
>
|
||||
{frameLabel}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isReady && (
|
||||
<p className="mt-4 text-xs text-slate-400 text-center max-w-[200px]">
|
||||
Enter your Google Review link to generate your QR code
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,397 +1,397 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import InstagramGenerator from './InstagramGenerator';
|
||||
import { Instagram, Shield, Zap, Smartphone, Camera, Heart, Download, Share2, TrendingUp } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Instagram QR Code Generator | Get More Followers | QR Master',
|
||||
},
|
||||
description: 'Create a free Instagram QR code for your profile. Scanners follow you instantly — no app login required. Customizable & downloadable in seconds.',
|
||||
keywords: ['instagram qr code', 'insta qr generator', 'ig nametag generator', 'instagram follow qr', 'social media qr code', 'qr code for instagram', 'instagram profile qr code', 'insta qr code', 'instagram nametag generator'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/instagram-qr-code',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/tools/instagram-qr-code',
|
||||
en: 'https://www.qrmaster.net/tools/instagram-qr-code',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Instagram QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes to grow your Instagram following. Instant app redirect.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/instagram-qr-code',
|
||||
images: [{ url: '/og-instagram-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Instagram QR Code Generator',
|
||||
description: 'Create QR codes for Instagram. Boost your followers.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Instagram QR Code Generator',
|
||||
'Generate QR codes that direct users to an Instagram profile or post.',
|
||||
'/og-instagram-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create an Instagram QR Code',
|
||||
description: 'Create a QR code that opens an Instagram profile.',
|
||||
datePublished: '2024-01-01',
|
||||
dateModified: '2026-04-27',
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: 'Timo Knuth',
|
||||
url: 'https://www.qrmaster.net/authors/timo',
|
||||
},
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Username',
|
||||
text: 'Type your Instagram handle (e.g. @yourbrand) or paste your profile link.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Customize',
|
||||
text: 'Choose a gradient color that matches the Instagram vibe or your own brand.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Save the QR code image.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Test',
|
||||
text: 'Scan the code to ensure it opens the correct profile.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Put it on your packaging, business cards, or storefront.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Is this an Instagram Nametag?': {
|
||||
question: 'Is this an Instagram Nametag?',
|
||||
answer: 'It works similarly! While Instagram has its own internal "Nametag" or "QR Code" feature, our generator allows you to create a standard QR code that is more customizable and can be tracked with our Dynamic plans.',
|
||||
},
|
||||
'Does it open the Instagram app?': {
|
||||
question: 'Does it open the Instagram app?',
|
||||
answer: 'Yes. When scanned on a mobile device with Instagram installed, it will deep-link directly to the profile in the app.',
|
||||
},
|
||||
'Can I link to a specific photo or reel?': {
|
||||
question: 'Can I link to a specific photo or reel?',
|
||||
answer: 'Yes! Instead of your username, just paste the full link to the specific post or reel.',
|
||||
},
|
||||
'Is it free?': {
|
||||
question: 'Is it free?',
|
||||
answer: 'Yes, generating this QR code is 100% free.',
|
||||
},
|
||||
'Can I track scans?': {
|
||||
question: 'Can I track scans?',
|
||||
answer: 'Not with this static tool. If you need scan analytics, consider using our Dynamic QR Code solution.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function InstagramQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Instagram QR Code Generator" toolSlug="instagram-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-gradient-to-br from-[#833AB4] via-[#FD1D1D] to-[#FCA145]">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<circle cx="0" cy="0" r="40" fill="white" fillOpacity="0.1" />
|
||||
<circle cx="100" cy="100" r="50" fill="white" fillOpacity="0.1" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-pink-300 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-pink-300"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Instagram QR Code Generator<br className="hidden lg:block" />
|
||||
<span className="text-white drop-shadow-md">— Boost Your Following</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-pink-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Connect physically to digitally. Let customers scan to follow your Instagram profile instantly.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Grow your brand effortlessly.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Heart className="w-4 h-4 text-pink-200" />
|
||||
More Likes
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-yellow-200" />
|
||||
Instant Follow
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Smartphone className="w-4 h-4 text-white" />
|
||||
App Deep Link
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-white/10 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden flex flex-col items-center">
|
||||
<div className="w-16 h-16 rounded-full p-[2px] bg-gradient-to-tr from-[#FCA145] via-[#FD1D1D] to-[#833AB4] mb-2">
|
||||
<div className="w-full h-full rounded-full bg-white p-1">
|
||||
<div className="w-full h-full rounded-full bg-slate-200" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm font-bold text-slate-900">@yourbrand</div>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#E1306C" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-gradient-to-tr from-[#FCA145] to-[#E1306C] p-2 rounded-full text-white">
|
||||
<Camera className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Profile</div>
|
||||
<div className="text-sm font-bold text-slate-900">Following</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<InstagramGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Instagram QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Instagram className="w-7 h-7 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Username</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Enter your Instagram handle. No need to login or connect your account.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Print</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Add the QR code to your packaging, business cards, or table tents.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save your custom QR code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Heart className="w-6 h-6 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Fans scan to instantly visit your profile.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Share2 className="w-6 h-6 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Grow</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Convert offline traffic into followers.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* STATS SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-gradient-to-br from-[#833AB4]/5 via-[#FD1D1D]/5 to-[#FCA145]/5">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="text-center mb-10">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-[#E1306C]/10 text-[#E1306C] text-sm font-semibold mb-4">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
Why Instagram QR Codes Work
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-slate-900 mb-3">The Numbers Behind the Strategy</h2>
|
||||
<p className="text-slate-500 text-sm max-w-xl mx-auto">Independent research on Instagram reach and QR code effectiveness.</p>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="bg-white rounded-2xl p-7 shadow-sm border border-slate-100">
|
||||
<div className="text-4xl font-extrabold text-[#E1306C] mb-2">2 Billion</div>
|
||||
<div className="font-semibold text-slate-800 mb-1">Monthly Active Users on Instagram</div>
|
||||
<p className="text-slate-500 text-sm">Instagram has over 2 billion monthly active users globally, making it one of the largest social platforms for brand discovery. A single well-placed QR code taps directly into that audience.</p>
|
||||
<div className="mt-4 text-xs text-slate-400">Source: Meta, Instagram Press (2023)</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-2xl p-7 shadow-sm border border-slate-100">
|
||||
<div className="text-4xl font-extrabold text-[#833AB4] mb-2">83%</div>
|
||||
<div className="font-semibold text-slate-800 mb-1">of Instagram Users Discover New Brands There</div>
|
||||
<p className="text-slate-500 text-sm">83% of Instagram users say they discover new products and brands on the platform. An Instagram QR code on your packaging or storefront converts that discovery moment offline → online.</p>
|
||||
<div className="mt-4 text-xs text-slate-400">Source: Facebook for Business / Instagram Business (2019)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<RelatedTools />
|
||||
|
||||
<GrowthLinksSection
|
||||
eyebrow="Grow Your Social Presence"
|
||||
title="More Tools to Build Your Brand Online"
|
||||
description="Combine your Instagram QR code with other social and marketing tools from QR Master."
|
||||
pageType="use_case"
|
||||
cluster="social-media"
|
||||
useCase="instagram-qr-code"
|
||||
links={[
|
||||
{
|
||||
href: '/tools/whatsapp-qr-code',
|
||||
title: 'WhatsApp QR Code',
|
||||
description: 'Let customers message you instantly — no number sharing required.',
|
||||
ctaLabel: 'Create WhatsApp QR',
|
||||
},
|
||||
{
|
||||
href: '/tools/vcard-qr-code',
|
||||
title: 'Digital Business Card',
|
||||
description: 'Turn your vCard into a scannable QR code with all your contact details.',
|
||||
ctaLabel: 'Create vCard QR',
|
||||
},
|
||||
{
|
||||
href: '/dynamic-qr-code-generator',
|
||||
title: 'Dynamic QR Codes',
|
||||
description: 'Track how many people scan your Instagram QR code — by day, device, and city.',
|
||||
ctaLabel: 'Try Dynamic QR',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<p className="text-center text-xs text-slate-400 mb-8">
|
||||
By <a href="/authors/timo" className="underline hover:text-slate-600">Timo Knuth</a> · Last updated: June 2025
|
||||
</p>
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about Instagram QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Does this work for private accounts?"
|
||||
answer="Yes, the link will take users to your profile. If your account is private, they will still have to request to follow you."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I link to a Story?"
|
||||
answer="Yes, but Stories expire after 24 hours (unless saved as a Highlight). Linking to a Highlight or your main Profile is usually better for printed materials."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I customize the frame?"
|
||||
answer="Yes, we offer several frame options like 'Follow Us' or 'Scan Me' to encourage action."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it expire?"
|
||||
answer="No. The QR code will work as long as your Instagram username remains the same."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I track scans?"
|
||||
answer="Not with this static tool. If you need scan analytics, consider using our Dynamic QR Code solution."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import InstagramGenerator from './InstagramGenerator';
|
||||
import { Instagram, Shield, Zap, Smartphone, Camera, Heart, Download, Share2, TrendingUp } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free Instagram QR Code Generator | Get More Followers | QR Master',
|
||||
},
|
||||
description: 'Create a free Instagram QR code for your profile. Scanners follow you instantly — no app login required. Customizable & downloadable in seconds.',
|
||||
keywords: ['instagram qr code', 'insta qr generator', 'ig nametag generator', 'instagram follow qr', 'social media qr code', 'qr code for instagram', 'instagram profile qr code', 'insta qr code', 'instagram nametag generator'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/instagram-qr-code',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/tools/instagram-qr-code',
|
||||
en: 'https://www.qrmaster.net/tools/instagram-qr-code',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free Instagram QR Code Generator | QR Master',
|
||||
description: 'Generate QR codes to grow your Instagram following. Instant app redirect.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/instagram-qr-code',
|
||||
images: [{ url: '/og-instagram-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free Instagram QR Code Generator',
|
||||
description: 'Create QR codes for Instagram. Boost your followers.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Instagram QR Code Generator',
|
||||
'Generate QR codes that direct users to an Instagram profile or post.',
|
||||
'/og-instagram-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create an Instagram QR Code',
|
||||
description: 'Create a QR code that opens an Instagram profile.',
|
||||
datePublished: '2024-01-01',
|
||||
dateModified: '2026-04-27',
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: 'Timo Knuth',
|
||||
url: 'https://www.qrmaster.net/authors/timo',
|
||||
},
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Username',
|
||||
text: 'Type your Instagram handle (e.g. @yourbrand) or paste your profile link.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Customize',
|
||||
text: 'Choose a gradient color that matches the Instagram vibe or your own brand.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Save the QR code image.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Test',
|
||||
text: 'Scan the code to ensure it opens the correct profile.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Share',
|
||||
text: 'Put it on your packaging, business cards, or storefront.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT30S',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'Is this an Instagram Nametag?': {
|
||||
question: 'Is this an Instagram Nametag?',
|
||||
answer: 'It works similarly! While Instagram has its own internal "Nametag" or "QR Code" feature, our generator allows you to create a standard QR code that is more customizable and can be tracked with our Dynamic plans.',
|
||||
},
|
||||
'Does it open the Instagram app?': {
|
||||
question: 'Does it open the Instagram app?',
|
||||
answer: 'Yes. When scanned on a mobile device with Instagram installed, it will deep-link directly to the profile in the app.',
|
||||
},
|
||||
'Can I link to a specific photo or reel?': {
|
||||
question: 'Can I link to a specific photo or reel?',
|
||||
answer: 'Yes! Instead of your username, just paste the full link to the specific post or reel.',
|
||||
},
|
||||
'Is it free?': {
|
||||
question: 'Is it free?',
|
||||
answer: 'Yes, generating this QR code is 100% free.',
|
||||
},
|
||||
'Can I track scans?': {
|
||||
question: 'Can I track scans?',
|
||||
answer: 'Not with this static tool. If you need scan analytics, consider using our Dynamic QR Code solution.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function InstagramQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="Instagram QR Code Generator" toolSlug="instagram-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden bg-gradient-to-br from-[#833AB4] via-[#FD1D1D] to-[#FCA145]">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<circle cx="0" cy="0" r="40" fill="white" fillOpacity="0.1" />
|
||||
<circle cx="100" cy="100" r="50" fill="white" fillOpacity="0.1" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-pink-300 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-pink-300"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
Instagram QR Code Generator<br className="hidden lg:block" />
|
||||
<span className="text-white drop-shadow-md">— Boost Your Following</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-pink-50 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Connect physically to digitally. Let customers scan to follow your Instagram profile instantly.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Grow your brand effortlessly.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Heart className="w-4 h-4 text-pink-200" />
|
||||
More Likes
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-yellow-200" />
|
||||
Instant Follow
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/10 px-4 py-2.5 rounded-xl border border-white/10 backdrop-blur-sm">
|
||||
<Smartphone className="w-4 h-4 text-white" />
|
||||
App Deep Link
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-white/10 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform -rotate-3 hover:rotate-0 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
<div className="w-full bg-white rounded-xl shadow-lg p-4 mb-6 relative overflow-hidden flex flex-col items-center">
|
||||
<div className="w-16 h-16 rounded-full p-[2px] bg-gradient-to-tr from-[#FCA145] via-[#FD1D1D] to-[#833AB4] mb-2">
|
||||
<div className="w-full h-full rounded-full bg-white p-1">
|
||||
<div className="w-full h-full rounded-full bg-slate-200" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm font-bold text-slate-900">@yourbrand</div>
|
||||
</div>
|
||||
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#E1306C" level="Q" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-gradient-to-tr from-[#FCA145] to-[#E1306C] p-2 rounded-full text-white">
|
||||
<Camera className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Profile</div>
|
||||
<div className="text-sm font-bold text-slate-900">Following</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<InstagramGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How Instagram QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Instagram className="w-7 h-7 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Username</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Enter your Instagram handle. No need to login or connect your account.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Print</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Add the QR code to your packaging, business cards, or table tents.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Save your custom QR code.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Heart className="w-6 h-6 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Scan</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Fans scan to instantly visit your profile.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#E1306C]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Share2 className="w-6 h-6 text-[#E1306C]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Grow</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Convert offline traffic into followers.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* STATS SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-gradient-to-br from-[#833AB4]/5 via-[#FD1D1D]/5 to-[#FCA145]/5">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="text-center mb-10">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-[#E1306C]/10 text-[#E1306C] text-sm font-semibold mb-4">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
Why Instagram QR Codes Work
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-slate-900 mb-3">The Numbers Behind the Strategy</h2>
|
||||
<p className="text-slate-500 text-sm max-w-xl mx-auto">Independent research on Instagram reach and QR code effectiveness.</p>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="bg-white rounded-2xl p-7 shadow-sm border border-slate-100">
|
||||
<div className="text-4xl font-extrabold text-[#E1306C] mb-2">2 Billion</div>
|
||||
<div className="font-semibold text-slate-800 mb-1">Monthly Active Users on Instagram</div>
|
||||
<p className="text-slate-500 text-sm">Instagram has over 2 billion monthly active users globally, making it one of the largest social platforms for brand discovery. A single well-placed QR code taps directly into that audience.</p>
|
||||
<div className="mt-4 text-xs text-slate-400">Source: Meta, Instagram Press (2023)</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-2xl p-7 shadow-sm border border-slate-100">
|
||||
<div className="text-4xl font-extrabold text-[#833AB4] mb-2">83%</div>
|
||||
<div className="font-semibold text-slate-800 mb-1">of Instagram Users Discover New Brands There</div>
|
||||
<p className="text-slate-500 text-sm">83% of Instagram users say they discover new products and brands on the platform. An Instagram QR code on your packaging or storefront converts that discovery moment offline → online.</p>
|
||||
<div className="mt-4 text-xs text-slate-400">Source: Facebook for Business / Instagram Business (2019)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<RelatedTools />
|
||||
|
||||
<GrowthLinksSection
|
||||
eyebrow="Grow Your Social Presence"
|
||||
title="More Tools to Build Your Brand Online"
|
||||
description="Combine your Instagram QR code with other social and marketing tools from QR Master."
|
||||
pageType="use_case"
|
||||
cluster="social-media"
|
||||
useCase="instagram-qr-code"
|
||||
links={[
|
||||
{
|
||||
href: '/tools/whatsapp-qr-code',
|
||||
title: 'WhatsApp QR Code',
|
||||
description: 'Let customers message you instantly — no number sharing required.',
|
||||
ctaLabel: 'Create WhatsApp QR',
|
||||
},
|
||||
{
|
||||
href: '/tools/vcard-qr-code',
|
||||
title: 'Digital Business Card',
|
||||
description: 'Turn your vCard into a scannable QR code with all your contact details.',
|
||||
ctaLabel: 'Create vCard QR',
|
||||
},
|
||||
{
|
||||
href: '/dynamic-qr-code-generator',
|
||||
title: 'Dynamic QR Codes',
|
||||
description: 'Track how many people scan your Instagram QR code — by day, device, and city.',
|
||||
ctaLabel: 'Try Dynamic QR',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<p className="text-center text-xs text-slate-400 mb-8">
|
||||
By <a href="/authors/timo" className="underline hover:text-slate-600">Timo Knuth</a> · Last updated: June 2025
|
||||
</p>
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about Instagram QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Does this work for private accounts?"
|
||||
answer="Yes, the link will take users to your profile. If your account is private, they will still have to request to follow you."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I link to a Story?"
|
||||
answer="Yes, but Stories expire after 24 hours (unless saved as a Highlight). Linking to a Highlight or your main Profile is usually better for printed materials."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I customize the frame?"
|
||||
answer="Yes, we offer several frame options like 'Follow Us' or 'Scan Me' to encourage action."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it expire?"
|
||||
answer="No. The QR code will work as long as your Instagram username remains the same."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I track scans?"
|
||||
answer="Not with this static tool. If you need scan analytics, consider using our Dynamic QR Code solution."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,373 +1,373 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import VCardGenerator from './VCardGenerator';
|
||||
import { User, Shield, Zap, Smartphone, Contact, Share2, Check, UserPlus, TrendingUp } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free vCard QR Code Generator | QR Master',
|
||||
},
|
||||
description: 'Create a vCard QR code for your business card. Share contact details instantly — customers scan and save with one tap. Free, no signup required.',
|
||||
keywords: ['vcard qr code', 'business card qr code', 'contact qr generator', 'digital business card', 'add to contacts qr', 'visitenkarte qr code', 'digitale visitenkarte erstellen', 'kontakt qr code', 'elektronische visitenkarte', 'vcard erstellen kostenlos'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/vcard-qr-code',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/tools/vcard-qr-code',
|
||||
en: 'https://www.qrmaster.net/tools/vcard-qr-code',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free vCard QR Code Generator | QR Master',
|
||||
description: 'Turn your contact info into a QR code. The modern way to share your business card.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/vcard-qr-code',
|
||||
images: [{ url: '/og-vcard-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free vCard QR Code Generator',
|
||||
description: 'Create QR codes for contact sharing. Instant and free.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'vCard QR Code Generator',
|
||||
'Generate vCard (VCF) QR codes for business cards. Scanners can save contact info instantly.',
|
||||
'/og-vcard-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a vCard QR Code',
|
||||
datePublished: '2024-01-01',
|
||||
dateModified: '2026-04-27',
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: 'Timo Knuth',
|
||||
url: 'https://www.qrmaster.net/authors/timo',
|
||||
},
|
||||
description: 'Create a QR code that saves your contact details.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Details',
|
||||
text: 'Fill in your Name, Phone, Email, Company, and Address.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Customize',
|
||||
text: 'Select a color that matches your brand and add a frame.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Download the QR code image and place it on your physical business card.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT1M',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'How does a vCard QR code work?': {
|
||||
question: 'How does a vCard QR code work?',
|
||||
answer: 'A vCard QR code contains your contact information in a standardized format (VCF). When scanned, the phone recognizes it as a contact card and prompts the user to "Save Contact" to their address book.',
|
||||
},
|
||||
'Is there a limit to how much info I can add?': {
|
||||
question: 'Is there a limit to how much info I can add?',
|
||||
answer: 'Static QR codes hold data directly in the pattern. The more data you add (long addresses, bio), the denser and harder to scan the QR code becomes. We recommend sticking to essential contact info for static codes.',
|
||||
},
|
||||
'Can I update my info later?': {
|
||||
question: 'Can I update my info later?',
|
||||
answer: 'No. This is a static vCard QR code. Once created, the info cannot be changed. If you move jobs or change numbers, you must print a new code. For editable cards, use our Dynamic vCard Plus.',
|
||||
},
|
||||
'Does it work on iPhone and Android?': {
|
||||
question: 'Does it work on iPhone and Android?',
|
||||
answer: 'Yes. Both iOS (Camera app) and Android (Camera or Google Lens) natively support vCard QR codes and correctly import the contact data.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function VCardQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="vCard QR Code Generator" toolSlug="vcard-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#9F1239' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-rose-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-rose-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
The Modern Way to <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-rose-300 to-pink-300">Share Your Contact</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Create a scannable Digital Business Card. One scan saves your name, phone, email, and address instantly.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Free & Professional.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<UserPlus className="w-4 h-4 text-rose-300" />
|
||||
Instant Save
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Share2 className="w-4 h-4 text-amber-400" />
|
||||
Easy Share
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Shield className="w-4 h-4 text-purple-400" />
|
||||
No Data Stored
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-96 h-60 bg-white/10 backdrop-blur-2xl border border-white/30 rounded-2xl shadow-2xl p-6 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent" />
|
||||
|
||||
<div className="flex justify-between items-start relative z-10">
|
||||
<div className="space-y-4">
|
||||
<div className="w-16 h-16 rounded-full bg-white/20 border-2 border-white/30 flex items-center justify-center">
|
||||
<Contact className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="h-4 w-32 bg-white/90 rounded-sm" />
|
||||
<div className="h-3 w-20 bg-emerald-400/90 rounded-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-24 h-24 bg-white rounded-lg p-1.5 shadow-lg">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={84} fgColor="#1A1265" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-6 left-6 space-y-2 z-10">
|
||||
<div className="h-2 w-48 bg-white/40 rounded-full" />
|
||||
<div className="h-2 w-40 bg-white/30 rounded-full" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-4 -left-4 bg-white py-2 px-4 rounded-lg shadow-xl flex items-center gap-2 transform scale-90">
|
||||
<div className="bg-emerald-100 p-1.5 rounded-full">
|
||||
<Check className="w-3 h-3 text-emerald-600" />
|
||||
</div>
|
||||
<span className="text-xs font-bold text-slate-900">Saved to Contacts</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<VCardGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How vCard QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Contact className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Enter Details</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Fill in your professional contact information. Only add what you want to share.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
A potential client or partner scans your card with their phone camera.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<UserPlus className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Save</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
They tap "Create New Contact" to save your details instantly. No typing errors.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* WHY DIGITAL BUSINESS CARDS — STATISTICS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<TrendingUp className="w-5 h-5 text-rose-500" />
|
||||
<span className="text-sm font-semibold text-rose-600 uppercase tracking-wider">Research-backed</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-slate-900 mb-4">
|
||||
Why Digital Contact Sharing Outperforms Paper Cards
|
||||
</h2>
|
||||
<p className="text-slate-600 mb-10 max-w-2xl">
|
||||
A <strong>vCard QR code</strong> solves the single biggest problem with traditional business cards: they get discarded before anyone saves your details.
|
||||
</p>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6 mb-8">
|
||||
<div className="bg-rose-50 border border-rose-100 rounded-2xl p-6">
|
||||
<div className="text-4xl font-extrabold text-rose-600 mb-2">88%</div>
|
||||
<p className="text-slate-700 text-sm leading-relaxed mb-3">
|
||||
of traditional paper business cards are thrown away within a week of being handed out. A vCard QR code on your card saves contact details instantly — no manual typing, no lost connections.
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
Source: <a href="https://www.adobe.com/express/learn/blog/business-card-statistics" target="_blank" rel="noopener noreferrer" className="underline hover:text-slate-700">Adobe Business Research</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-orange-50 border border-orange-100 rounded-2xl p-6">
|
||||
<div className="text-4xl font-extrabold text-orange-600 mb-2">1-tap save</div>
|
||||
<p className="text-slate-700 text-sm leading-relaxed mb-3">
|
||||
Instead of asking someone to manually type your name, phone, and email — a vCard QR code transfers all contact fields (name, phone, email, company, URL) directly into their phone's address book with a single scan.
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
vCard 3.0 / VCF format — supported natively by iOS and Android
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-400 italic">
|
||||
By Timo Knuth, QR Master · Last updated: June 2025 · Based on independent academic and industry research
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GROWTH LINKS */}
|
||||
<GrowthLinksSection
|
||||
eyebrow="Level up your QR strategy"
|
||||
title="More ways to use QR codes for your brand"
|
||||
description="A vCard QR code is a great start. Add custom design, tracking, and dynamic destinations to get the most out of every print."
|
||||
links={[
|
||||
{
|
||||
href: '/custom-qr-code-generator',
|
||||
title: 'Custom QR Code Generator',
|
||||
description: 'Add your logo, brand colors, and a custom frame to your QR codes.',
|
||||
ctaLabel: 'Custom QR with logo',
|
||||
},
|
||||
{
|
||||
href: '/dynamic-qr-code-generator',
|
||||
title: 'Dynamic QR Code Generator',
|
||||
description: 'Update your contact destination after printing. No new QR code needed.',
|
||||
ctaLabel: 'Dynamic QR with tracking',
|
||||
},
|
||||
]}
|
||||
pageType="commercial"
|
||||
cluster="vcard-qr"
|
||||
/>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about vCard QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Can I add a profile picture?"
|
||||
answer="Not on a static vCard QR code. Static codes store data in the pixels, so adding an image would make the code too complex to scan. For profile pictures, social links, and rich media, use our Dynamic vCard Plus solution."
|
||||
/>
|
||||
<FaqItem
|
||||
question="How long does the QR code last?"
|
||||
answer="Forever. Static vCard QR codes do not expire because the data is embedded directly in the image."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What information is required?"
|
||||
answer="Nothing is strictly required, but we recommend at least a First Name and either a Phone Number or Email so the contact is useful."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is my data safe?"
|
||||
answer="Yes. This tool operates 100% in your browser. We do not store, see, or optimize your contact data. It goes directly from your input to the QR code."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import VCardGenerator from './VCardGenerator';
|
||||
import { User, Shield, Zap, Smartphone, Contact, Share2, Check, UserPlus, TrendingUp } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free vCard QR Code Generator | QR Master',
|
||||
},
|
||||
description: 'Create a vCard QR code for your business card. Share contact details instantly — customers scan and save with one tap. Free, no signup required.',
|
||||
keywords: ['vcard qr code', 'business card qr code', 'contact qr generator', 'digital business card', 'add to contacts qr', 'visitenkarte qr code', 'digitale visitenkarte erstellen', 'kontakt qr code', 'elektronische visitenkarte', 'vcard erstellen kostenlos'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/vcard-qr-code',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/tools/vcard-qr-code',
|
||||
en: 'https://www.qrmaster.net/tools/vcard-qr-code',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free vCard QR Code Generator | QR Master',
|
||||
description: 'Turn your contact info into a QR code. The modern way to share your business card.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/vcard-qr-code',
|
||||
images: [{ url: '/og-vcard-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free vCard QR Code Generator',
|
||||
description: 'Create QR codes for contact sharing. Instant and free.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'vCard QR Code Generator',
|
||||
'Generate vCard (VCF) QR codes for business cards. Scanners can save contact info instantly.',
|
||||
'/og-vcard-generator.png'
|
||||
),
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a vCard QR Code',
|
||||
datePublished: '2024-01-01',
|
||||
dateModified: '2026-04-27',
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: 'Timo Knuth',
|
||||
url: 'https://www.qrmaster.net/authors/timo',
|
||||
},
|
||||
description: 'Create a QR code that saves your contact details.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Details',
|
||||
text: 'Fill in your Name, Phone, Email, Company, and Address.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Customize',
|
||||
text: 'Select a color that matches your brand and add a frame.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Download',
|
||||
text: 'Download the QR code image and place it on your physical business card.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT1M',
|
||||
},
|
||||
generateFaqSchema({
|
||||
'How does a vCard QR code work?': {
|
||||
question: 'How does a vCard QR code work?',
|
||||
answer: 'A vCard QR code contains your contact information in a standardized format (VCF). When scanned, the phone recognizes it as a contact card and prompts the user to "Save Contact" to their address book.',
|
||||
},
|
||||
'Is there a limit to how much info I can add?': {
|
||||
question: 'Is there a limit to how much info I can add?',
|
||||
answer: 'Static QR codes hold data directly in the pattern. The more data you add (long addresses, bio), the denser and harder to scan the QR code becomes. We recommend sticking to essential contact info for static codes.',
|
||||
},
|
||||
'Can I update my info later?': {
|
||||
question: 'Can I update my info later?',
|
||||
answer: 'No. This is a static vCard QR code. Once created, the info cannot be changed. If you move jobs or change numbers, you must print a new code. For editable cards, use our Dynamic vCard Plus.',
|
||||
},
|
||||
'Does it work on iPhone and Android?': {
|
||||
question: 'Does it work on iPhone and Android?',
|
||||
answer: 'Yes. Both iOS (Camera app) and Android (Camera or Google Lens) natively support vCard QR codes and correctly import the contact data.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function VCardQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="vCard QR Code Generator" toolSlug="vcard-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#9F1239' }}>
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
<div className="text-center lg:text-left">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-rose-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-rose-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
The Modern Way to <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-rose-300 to-pink-300">Share Your Contact</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Create a scannable Digital Business Card. One scan saves your name, phone, email, and address instantly.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> Free & Professional.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<UserPlus className="w-4 h-4 text-rose-300" />
|
||||
Instant Save
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Share2 className="w-4 h-4 text-amber-400" />
|
||||
Easy Share
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Shield className="w-4 h-4 text-purple-400" />
|
||||
No Data Stored
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Abstract */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
<div className="relative w-96 h-60 bg-white/10 backdrop-blur-2xl border border-white/30 rounded-2xl shadow-2xl p-6 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent" />
|
||||
|
||||
<div className="flex justify-between items-start relative z-10">
|
||||
<div className="space-y-4">
|
||||
<div className="w-16 h-16 rounded-full bg-white/20 border-2 border-white/30 flex items-center justify-center">
|
||||
<Contact className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="h-4 w-32 bg-white/90 rounded-sm" />
|
||||
<div className="h-3 w-20 bg-emerald-400/90 rounded-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-24 h-24 bg-white rounded-lg p-1.5 shadow-lg">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={84} fgColor="#1A1265" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-6 left-6 space-y-2 z-10">
|
||||
<div className="h-2 w-48 bg-white/40 rounded-full" />
|
||||
<div className="h-2 w-40 bg-white/30 rounded-full" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-4 -left-4 bg-white py-2 px-4 rounded-lg shadow-xl flex items-center gap-2 transform scale-90">
|
||||
<div className="bg-emerald-100 p-1.5 rounded-full">
|
||||
<Check className="w-3 h-3 text-emerald-600" />
|
||||
</div>
|
||||
<span className="text-xs font-bold text-slate-900">Saved to Contacts</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<VCardGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How vCard QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Contact className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Enter Details</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
Fill in your professional contact information. Only add what you want to share.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Scan</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
A potential client or partner scans your card with their phone camera.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<UserPlus className="w-7 h-7 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Save</h3>
|
||||
<p className="text-slate-600 text-sm">
|
||||
They tap "Create New Contact" to save your details instantly. No typing errors.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* WHY DIGITAL BUSINESS CARDS — STATISTICS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<TrendingUp className="w-5 h-5 text-rose-500" />
|
||||
<span className="text-sm font-semibold text-rose-600 uppercase tracking-wider">Research-backed</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-slate-900 mb-4">
|
||||
Why Digital Contact Sharing Outperforms Paper Cards
|
||||
</h2>
|
||||
<p className="text-slate-600 mb-10 max-w-2xl">
|
||||
A <strong>vCard QR code</strong> solves the single biggest problem with traditional business cards: they get discarded before anyone saves your details.
|
||||
</p>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6 mb-8">
|
||||
<div className="bg-rose-50 border border-rose-100 rounded-2xl p-6">
|
||||
<div className="text-4xl font-extrabold text-rose-600 mb-2">88%</div>
|
||||
<p className="text-slate-700 text-sm leading-relaxed mb-3">
|
||||
of traditional paper business cards are thrown away within a week of being handed out. A vCard QR code on your card saves contact details instantly — no manual typing, no lost connections.
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
Source: <a href="https://www.adobe.com/express/learn/blog/business-card-statistics" target="_blank" rel="noopener noreferrer" className="underline hover:text-slate-700">Adobe Business Research</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-orange-50 border border-orange-100 rounded-2xl p-6">
|
||||
<div className="text-4xl font-extrabold text-orange-600 mb-2">1-tap save</div>
|
||||
<p className="text-slate-700 text-sm leading-relaxed mb-3">
|
||||
Instead of asking someone to manually type your name, phone, and email — a vCard QR code transfers all contact fields (name, phone, email, company, URL) directly into their phone's address book with a single scan.
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
vCard 3.0 / VCF format — supported natively by iOS and Android
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-400 italic">
|
||||
By Timo Knuth, QR Master · Last updated: June 2025 · Based on independent academic and industry research
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GROWTH LINKS */}
|
||||
<GrowthLinksSection
|
||||
eyebrow="Level up your QR strategy"
|
||||
title="More ways to use QR codes for your brand"
|
||||
description="A vCard QR code is a great start. Add custom design, tracking, and dynamic destinations to get the most out of every print."
|
||||
links={[
|
||||
{
|
||||
href: '/custom-qr-code-generator',
|
||||
title: 'Custom QR Code Generator',
|
||||
description: 'Add your logo, brand colors, and a custom frame to your QR codes.',
|
||||
ctaLabel: 'Custom QR with logo',
|
||||
},
|
||||
{
|
||||
href: '/dynamic-qr-code-generator',
|
||||
title: 'Dynamic QR Code Generator',
|
||||
description: 'Update your contact destination after printing. No new QR code needed.',
|
||||
ctaLabel: 'Dynamic QR with tracking',
|
||||
},
|
||||
]}
|
||||
pageType="commercial"
|
||||
cluster="vcard-qr"
|
||||
/>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Common questions about vCard QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Can I add a profile picture?"
|
||||
answer="Not on a static vCard QR code. Static codes store data in the pixels, so adding an image would make the code too complex to scan. For profile pictures, social links, and rich media, use our Dynamic vCard Plus solution."
|
||||
/>
|
||||
<FaqItem
|
||||
question="How long does the QR code last?"
|
||||
answer="Forever. Static vCard QR codes do not expire because the data is embedded directly in the image."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What information is required?"
|
||||
answer="Nothing is strictly required, but we recommend at least a First Name and either a Phone Number or Email so the contact is useful."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Is my data safe?"
|
||||
answer="Yes. This tool operates 100% in your browser. We do not store, see, or optimize your contact data. It goes directly from your input to the QR code."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,394 +1,394 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import WiFiGenerator from './WiFiGenerator';
|
||||
import { Wifi, Shield, Zap, Smartphone, Lock, QrCode, Download, Share2, TrendingUp } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free WiFi QR Code Generator | WLAN QR Code | QR Master',
|
||||
},
|
||||
description: 'Create a WiFi QR code in seconds. Erstelle kostenlos deinen WLAN QR Code ohne Passwort-Eingabe. Guests scan to connect instantly. 100% Secure & Free.',
|
||||
keywords: ['wifi qr code', 'qr code generator', 'wifi qr code generator', 'share wifi', 'wifi password qr', 'guest wifi', 'wlan qr code', 'wlan qr code erstellen', 'wifi passwort qr code', 'wlan zugang teilen', 'wifi qr code kostenlos'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/wifi-qr-code',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/tools/wifi-qr-code',
|
||||
en: 'https://www.qrmaster.net/tools/wifi-qr-code',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free WiFi QR Code Generator | QR Master',
|
||||
description: 'Share your WiFi without sharing your password. Guests scan the QR code to connect instantly.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/wifi-qr-code',
|
||||
images: [{ url: '/og-wifi-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free WiFi QR Code Generator',
|
||||
description: 'Share WiFi instantly with a QR code. No typing passwords.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
// SoftwareApplication Schema
|
||||
generateSoftwareAppSchema(
|
||||
'WiFi QR Code Generator',
|
||||
'Generate QR codes for WiFi networks. Guests scan to connect without typing passwords.',
|
||||
'/og-wifi-generator.png'
|
||||
),
|
||||
// HowTo Schema for Featured Snippets
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a WiFi QR Code',
|
||||
datePublished: '2024-01-01',
|
||||
dateModified: '2026-04-27',
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: 'Timo Knuth',
|
||||
url: 'https://www.qrmaster.net/authors/timo',
|
||||
},
|
||||
description: 'Create a QR code that connects devices to your WiFi network automatically.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Network Name',
|
||||
text: 'Type your WiFi network name (SSID) in the Network Name field.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Enter Password',
|
||||
text: 'Enter your WiFi password. This is processed locally and never sent to any server.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Select Security Type',
|
||||
text: 'Choose WPA/WPA2 (most common), WEP, or No Password for open networks.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Download QR Code',
|
||||
text: 'Click Download PNG or SVG to save your QR code. Print it or share digitally.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Connect',
|
||||
text: 'Print the code. Guests can scan it to join your network instantly.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT1M',
|
||||
},
|
||||
// FAQPage Schema
|
||||
generateFaqSchema({
|
||||
'Is it safe to enter my WiFi password?': {
|
||||
question: 'Is it safe to enter my WiFi password?',
|
||||
answer: 'Yes, completely safe. This tool processes everything in your browser (client-side). Your password never leaves your device and is not sent to any server.',
|
||||
},
|
||||
'Do WiFi QR codes work on iPhone and Android?': {
|
||||
question: 'Do WiFi QR codes work on iPhone and Android?',
|
||||
answer: 'Yes. Both iOS (11+) and Android devices can scan WiFi QR codes using their built-in camera app. No additional apps required.',
|
||||
},
|
||||
'What happens if I change my WiFi password?': {
|
||||
question: 'What happens if I change my WiFi password?',
|
||||
answer: 'If you change your WiFi password, the old QR code will stop working. You\'ll need to generate a new QR code with the updated credentials.For frequently changing passwords, consider using dynamic QR codes.',
|
||||
},
|
||||
'Can I customize the QR code design?': {
|
||||
question: 'Can I customize the QR code design?',
|
||||
answer: 'Yes. You can change the QR code color and add frame labels like "Scan Me" or "WiFi" to make it more recognizable and user-friendly.',
|
||||
},
|
||||
'Does it work for hidden networks?': {
|
||||
question: 'Does it work for hidden networks?',
|
||||
answer: 'Yes, just check the "Hidden Network" box if your SSID is hidden. The QR code contains the standard WiFi string configuration.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function WiFiQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
{/* JSON-LD Script */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="WiFi QR Code Generator" toolSlug="wifi-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
||||
{/* Background Pattern */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
|
||||
{/* Left: Text Content */}
|
||||
<div className="text-center lg:text-left">
|
||||
{/* Badge */}
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
The Safest Way to <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Share Your WiFi</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Generate a secure QR code in seconds. No more spelling out complicated passwords.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> 100% Client-Side & Private.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Lock className="w-4 h-4 text-emerald-400" />
|
||||
No Server Uploads
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-amber-400" />
|
||||
Instant Connect
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Smartphone className="w-4 h-4 text-purple-400" />
|
||||
iOS & Android
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Visual Abstract Composition */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
{/* Decorative Glow */}
|
||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
{/* Floating Glass Card */}
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
{/* Mock QR */}
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
||||
{/* Scan Line */}
|
||||
<div className="absolute top-1/2 left-0 w-full h-1 bg-emerald-500 shadow-[0_0_20px_rgba(16,185,129,1)] animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-3">
|
||||
<div className="h-2 w-32 bg-white/20 rounded-full mx-auto" />
|
||||
<div className="h-2 w-20 bg-white/10 rounded-full mx-auto" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-emerald-100 p-2 rounded-full">
|
||||
<Wifi className="w-5 h-5 text-emerald-600" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
||||
<div className="text-sm font-bold text-slate-900">Connected</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<WiFiGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS - AEO/GEO Content */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How WiFi QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Wifi className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Network</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Enter your WiFi SSID and password.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Shield className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Security</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Select WPA/WPA2 encryption.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Zap className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Style</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Customize colors and add a frame.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Get your high-quality QR image.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Connect</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Print it out. Guests scan to join!
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* WHY WIFI QR CODES MATTER — STATISTICS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<TrendingUp className="w-5 h-5 text-emerald-500" />
|
||||
<span className="text-sm font-semibold text-emerald-600 uppercase tracking-wider">Research-backed</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-slate-900 mb-4">
|
||||
Why WiFi QR Codes Improve Customer Experience
|
||||
</h2>
|
||||
<p className="text-slate-600 mb-10 max-w-2xl">
|
||||
A <strong>WiFi QR code</strong> eliminates the single biggest friction point between your guest and your network — manual password entry.
|
||||
</p>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6 mb-8">
|
||||
<div className="bg-emerald-50 border border-emerald-100 rounded-2xl p-6">
|
||||
<div className="text-3xl font-extrabold text-emerald-600 mb-2">#1 Amenity</div>
|
||||
<p className="text-slate-700 text-sm leading-relaxed mb-3">
|
||||
Free WiFi is rated the most important hotel amenity by guests — ahead of breakfast, parking, and loyalty points. Instant, frictionless access directly impacts satisfaction scores and repeat bookings.
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
Source: <a href="https://www.jdpower.com/business/travel-hospitality/hotel-guest-satisfaction-study" target="_blank" rel="noopener noreferrer" className="underline hover:text-slate-700">J.D. Power Hotel Guest Satisfaction Study</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50 border border-blue-100 rounded-2xl p-6">
|
||||
<div className="text-3xl font-extrabold text-blue-600 mb-2">Effort = Loyalty</div>
|
||||
<p className="text-slate-700 text-sm leading-relaxed mb-3">
|
||||
Reducing customer effort — like eliminating manual password entry — is the single strongest predictor of customer loyalty. The lower the effort, the higher the repeat visit rate and positive word-of-mouth.
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
Source: <a href="https://hbr.org/2010/07/stop-trying-to-delight-your-customers" target="_blank" rel="noopener noreferrer" className="underline hover:text-slate-700">Harvard Business Review — "Stop Trying to Delight Your Customers"</a> (Customer Effort Score research)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-400 italic">
|
||||
By Timo Knuth, QR Master · Last updated: June 2025 · Based on independent academic and industry research
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION - Featured Snippet Optimized */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Everything you need to know about WiFi QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Is it safe to enter my WiFi password here?"
|
||||
answer="Yes, completely safe. This tool uses client-side processing, meaning your WiFi password never leaves your device. It's processed locally in your browser to generate the QR code—no data is sent to any server."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Do WiFi QR codes work on iPhone and Android?"
|
||||
answer="Yes. iOS 11 and later, as well as all modern Android devices, can scan WiFi QR codes using the built-in camera app. Simply point the camera at the QR code and tap the notification to connect."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What happens if I change my WiFi password?"
|
||||
answer="If you change your WiFi password, the old QR code will stop working. You'll need to generate a new QR code with the updated credentials. For frequently changing passwords, consider using dynamic QR codes."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I customize the QR code design?"
|
||||
answer="Yes. You can change the foreground color of the QR code and add frame labels such as 'Scan Me', 'WiFi', or 'Connect' to make your QR code more recognizable and user-friendly."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it work for hidden networks?"
|
||||
answer="Yes, just check the 'Hidden Network' box if your SSID is hidden. The QR code contains the standard WiFi string configuration."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// FAQ Item Component
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import WiFiGenerator from './WiFiGenerator';
|
||||
import { Wifi, Shield, Zap, Smartphone, Lock, QrCode, Download, Share2, TrendingUp } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { ToolBreadcrumb } from '@/components/seo/BreadcrumbSchema';
|
||||
import { RelatedTools } from '@/components/marketing/RelatedTools';
|
||||
import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils';
|
||||
|
||||
// SEO Optimized Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Free WiFi QR Code Generator | WLAN QR Code | QR Master',
|
||||
},
|
||||
description: 'Create a WiFi QR code in seconds. Erstelle kostenlos deinen WLAN QR Code ohne Passwort-Eingabe. Guests scan to connect instantly. 100% Secure & Free.',
|
||||
keywords: ['wifi qr code', 'qr code generator', 'wifi qr code generator', 'share wifi', 'wifi password qr', 'guest wifi', 'wlan qr code', 'wlan qr code erstellen', 'wifi passwort qr code', 'wlan zugang teilen', 'wifi qr code kostenlos'],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/wifi-qr-code',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/tools/wifi-qr-code',
|
||||
en: 'https://www.qrmaster.net/tools/wifi-qr-code',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free WiFi QR Code Generator | QR Master',
|
||||
description: 'Share your WiFi without sharing your password. Guests scan the QR code to connect instantly.',
|
||||
type: 'website',
|
||||
url: 'https://www.qrmaster.net/tools/wifi-qr-code',
|
||||
images: [{ url: '/og-wifi-generator.png', width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Free WiFi QR Code Generator',
|
||||
description: 'Share WiFi instantly with a QR code. No typing passwords.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
// JSON-LD Structured Data
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
// SoftwareApplication Schema
|
||||
generateSoftwareAppSchema(
|
||||
'WiFi QR Code Generator',
|
||||
'Generate QR codes for WiFi networks. Guests scan to connect without typing passwords.',
|
||||
'/og-wifi-generator.png'
|
||||
),
|
||||
// HowTo Schema for Featured Snippets
|
||||
{
|
||||
'@type': 'HowTo',
|
||||
name: 'How to Create a WiFi QR Code',
|
||||
datePublished: '2024-01-01',
|
||||
dateModified: '2026-04-27',
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: 'Timo Knuth',
|
||||
url: 'https://www.qrmaster.net/authors/timo',
|
||||
},
|
||||
description: 'Create a QR code that connects devices to your WiFi network automatically.',
|
||||
step: [
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 1,
|
||||
name: 'Enter Network Name',
|
||||
text: 'Type your WiFi network name (SSID) in the Network Name field.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 2,
|
||||
name: 'Enter Password',
|
||||
text: 'Enter your WiFi password. This is processed locally and never sent to any server.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 3,
|
||||
name: 'Select Security Type',
|
||||
text: 'Choose WPA/WPA2 (most common), WEP, or No Password for open networks.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 4,
|
||||
name: 'Download QR Code',
|
||||
text: 'Click Download PNG or SVG to save your QR code. Print it or share digitally.',
|
||||
},
|
||||
{
|
||||
'@type': 'HowToStep',
|
||||
position: 5,
|
||||
name: 'Connect',
|
||||
text: 'Print the code. Guests can scan it to join your network instantly.',
|
||||
},
|
||||
],
|
||||
totalTime: 'PT1M',
|
||||
},
|
||||
// FAQPage Schema
|
||||
generateFaqSchema({
|
||||
'Is it safe to enter my WiFi password?': {
|
||||
question: 'Is it safe to enter my WiFi password?',
|
||||
answer: 'Yes, completely safe. This tool processes everything in your browser (client-side). Your password never leaves your device and is not sent to any server.',
|
||||
},
|
||||
'Do WiFi QR codes work on iPhone and Android?': {
|
||||
question: 'Do WiFi QR codes work on iPhone and Android?',
|
||||
answer: 'Yes. Both iOS (11+) and Android devices can scan WiFi QR codes using their built-in camera app. No additional apps required.',
|
||||
},
|
||||
'What happens if I change my WiFi password?': {
|
||||
question: 'What happens if I change my WiFi password?',
|
||||
answer: 'If you change your WiFi password, the old QR code will stop working. You\'ll need to generate a new QR code with the updated credentials.For frequently changing passwords, consider using dynamic QR codes.',
|
||||
},
|
||||
'Can I customize the QR code design?': {
|
||||
question: 'Can I customize the QR code design?',
|
||||
answer: 'Yes. You can change the QR code color and add frame labels like "Scan Me" or "WiFi" to make it more recognizable and user-friendly.',
|
||||
},
|
||||
'Does it work for hidden networks?': {
|
||||
question: 'Does it work for hidden networks?',
|
||||
answer: 'Yes, just check the "Hidden Network" box if your SSID is hidden. The QR code contains the standard WiFi string configuration.',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default function WiFiQRCodePage() {
|
||||
return (
|
||||
<>
|
||||
{/* JSON-LD Script */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ToolBreadcrumb toolName="WiFi QR Code Generator" toolSlug="wifi-qr-code" />
|
||||
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative pt-20 pb-20 lg:pt-32 lg:pb-32 px-4 sm:px-6 lg:px-8 overflow-hidden" style={{ backgroundColor: '#1A1265' }}>
|
||||
{/* Background Pattern */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<path d="M0 100 C 20 0 50 0 100 100 Z" fill="url(#grad1)" />
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style={{ stopColor: 'white', stopOpacity: 1 }} />
|
||||
<stop offset="100%" style={{ stopColor: 'white', stopOpacity: 0 }} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-12 items-center relative z-10">
|
||||
|
||||
{/* Left: Text Content */}
|
||||
<div className="text-center lg:text-left">
|
||||
{/* Badge */}
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10 hover:bg-white/20 transition-colors cursor-default">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400"></span>
|
||||
</span>
|
||||
Free Tool — No Signup Required
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold text-white tracking-tight leading-tight mb-6">
|
||||
The Safest Way to <br className="hidden lg:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400">Share Your WiFi</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-indigo-100 max-w-2xl mx-auto lg:mx-0 mb-8 leading-relaxed">
|
||||
Generate a secure QR code in seconds. No more spelling out complicated passwords.
|
||||
<strong className="text-white block sm:inline mt-2 sm:mt-0"> 100% Client-Side & Private.</strong>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 text-sm font-medium text-white/80">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Lock className="w-4 h-4 text-emerald-400" />
|
||||
No Server Uploads
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Zap className="w-4 h-4 text-amber-400" />
|
||||
Instant Connect
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/5 backdrop-blur-sm">
|
||||
<Smartphone className="w-4 h-4 text-purple-400" />
|
||||
iOS & Android
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Visual Abstract Composition */}
|
||||
<div className="hidden lg:flex relative items-center justify-center min-h-[400px]">
|
||||
{/* Decorative Glow */}
|
||||
<div className="absolute w-[500px] h-[500px] bg-indigo-500/30 rounded-full blur-[100px] -top-20 -right-20 animate-pulse" />
|
||||
|
||||
{/* Floating Glass Card */}
|
||||
<div className="relative w-80 h-96 bg-white/5 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl flex flex-col items-center justify-center p-8 transform rotate-6 hover:rotate-3 transition-all duration-700 group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent rounded-3xl" />
|
||||
|
||||
{/* Mock QR */}
|
||||
<div className="w-48 h-48 bg-white rounded-xl p-2 shadow-inner mb-6 relative overflow-hidden flex items-center justify-center">
|
||||
<QRCodeSVG value="https://www.qrmaster.net" size={170} fgColor="#0f172a" level="Q" />
|
||||
{/* Scan Line */}
|
||||
<div className="absolute top-1/2 left-0 w-full h-1 bg-emerald-500 shadow-[0_0_20px_rgba(16,185,129,1)] animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-3">
|
||||
<div className="h-2 w-32 bg-white/20 rounded-full mx-auto" />
|
||||
<div className="h-2 w-20 bg-white/10 rounded-full mx-auto" />
|
||||
</div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<div className="absolute -bottom-6 -left-6 bg-white py-3 px-5 rounded-xl shadow-xl flex items-center gap-3 transform transition-transform hover:scale-105 duration-300">
|
||||
<div className="bg-emerald-100 p-2 rounded-full">
|
||||
<Wifi className="w-5 h-5 text-emerald-600" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Status</div>
|
||||
<div className="text-sm font-bold text-slate-900">Connected</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERATOR SECTION */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 -mt-8">
|
||||
<WiFiGenerator />
|
||||
</section>
|
||||
|
||||
{/* HOW IT WORKS - AEO/GEO Content */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-12">
|
||||
How WiFi QR Codes Work
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-3 lg:grid-cols-5 gap-8">
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Wifi className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">1. Network</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Enter your WiFi SSID and password.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Shield className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">2. Security</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Select WPA/WPA2 encryption.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Zap className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">3. Style</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Customize colors and add a frame.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Download className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">4. Download</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Get your high-quality QR image.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="text-center">
|
||||
<div className="w-12 h-12 rounded-2xl bg-[#1A1265]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Smartphone className="w-6 h-6 text-[#1A1265]" />
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-900 mb-2">5. Connect</h3>
|
||||
<p className="text-slate-600 text-xs leading-relaxed">
|
||||
Print it out. Guests scan to join!
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* WHY WIFI QR CODES MATTER — STATISTICS */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<TrendingUp className="w-5 h-5 text-emerald-500" />
|
||||
<span className="text-sm font-semibold text-emerald-600 uppercase tracking-wider">Research-backed</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-slate-900 mb-4">
|
||||
Why WiFi QR Codes Improve Customer Experience
|
||||
</h2>
|
||||
<p className="text-slate-600 mb-10 max-w-2xl">
|
||||
A <strong>WiFi QR code</strong> eliminates the single biggest friction point between your guest and your network — manual password entry.
|
||||
</p>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6 mb-8">
|
||||
<div className="bg-emerald-50 border border-emerald-100 rounded-2xl p-6">
|
||||
<div className="text-3xl font-extrabold text-emerald-600 mb-2">#1 Amenity</div>
|
||||
<p className="text-slate-700 text-sm leading-relaxed mb-3">
|
||||
Free WiFi is rated the most important hotel amenity by guests — ahead of breakfast, parking, and loyalty points. Instant, frictionless access directly impacts satisfaction scores and repeat bookings.
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
Source: <a href="https://www.jdpower.com/business/travel-hospitality/hotel-guest-satisfaction-study" target="_blank" rel="noopener noreferrer" className="underline hover:text-slate-700">J.D. Power Hotel Guest Satisfaction Study</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50 border border-blue-100 rounded-2xl p-6">
|
||||
<div className="text-3xl font-extrabold text-blue-600 mb-2">Effort = Loyalty</div>
|
||||
<p className="text-slate-700 text-sm leading-relaxed mb-3">
|
||||
Reducing customer effort — like eliminating manual password entry — is the single strongest predictor of customer loyalty. The lower the effort, the higher the repeat visit rate and positive word-of-mouth.
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
Source: <a href="https://hbr.org/2010/07/stop-trying-to-delight-your-customers" target="_blank" rel="noopener noreferrer" className="underline hover:text-slate-700">Harvard Business Review — "Stop Trying to Delight Your Customers"</a> (Customer Effort Score research)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-400 italic">
|
||||
By Timo Knuth, QR Master · Last updated: June 2025 · Based on independent academic and industry research
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RELATED TOOLS */}
|
||||
<RelatedTools />
|
||||
|
||||
{/* FAQ SECTION - Featured Snippet Optimized */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8" style={{ backgroundColor: '#EBEBDF' }}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-slate-900 text-center mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-slate-600 text-center mb-10">
|
||||
Everything you need to know about WiFi QR codes.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<FaqItem
|
||||
question="Is it safe to enter my WiFi password here?"
|
||||
answer="Yes, completely safe. This tool uses client-side processing, meaning your WiFi password never leaves your device. It's processed locally in your browser to generate the QR code—no data is sent to any server."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Do WiFi QR codes work on iPhone and Android?"
|
||||
answer="Yes. iOS 11 and later, as well as all modern Android devices, can scan WiFi QR codes using the built-in camera app. Simply point the camera at the QR code and tap the notification to connect."
|
||||
/>
|
||||
<FaqItem
|
||||
question="What happens if I change my WiFi password?"
|
||||
answer="If you change your WiFi password, the old QR code will stop working. You'll need to generate a new QR code with the updated credentials. For frequently changing passwords, consider using dynamic QR codes."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Can I customize the QR code design?"
|
||||
answer="Yes. You can change the foreground color of the QR code and add frame labels such as 'Scan Me', 'WiFi', or 'Connect' to make your QR code more recognizable and user-friendly."
|
||||
/>
|
||||
<FaqItem
|
||||
question="Does it work for hidden networks?"
|
||||
answer="Yes, just check the 'Hidden Network' box if your SSID is hidden. The QR code contains the standard WiFi string configuration."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// FAQ Item Component
|
||||
function FaqItem({ question, answer }: { question: string; answer: string }) {
|
||||
return (
|
||||
<details className="group bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<summary className="flex items-center justify-between p-5 cursor-pointer list-none text-slate-900 font-semibold hover:bg-slate-50 transition-colors">
|
||||
{question}
|
||||
<span className="transition group-open:rotate-180 text-slate-400">
|
||||
<svg fill="none" height="20" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="20">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="text-slate-600 px-5 pb-5 pt-0 leading-relaxed text-sm">
|
||||
{answer}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,77 +1,79 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import {
|
||||
buildUseCaseMetadata,
|
||||
UseCasePageTemplate,
|
||||
} from '@/components/marketing/UseCasePageTemplate';
|
||||
import { allUseCases, getUseCasePage } from '@/lib/growth-pages';
|
||||
|
||||
export function generateStaticParams() {
|
||||
return allUseCases.map((item) => ({
|
||||
slug: item.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||
const page = getUseCasePage(params.slug);
|
||||
|
||||
if (!page) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return buildUseCaseMetadata({
|
||||
title: page.title,
|
||||
description: page.metaDescription,
|
||||
canonicalPath: page.href,
|
||||
});
|
||||
}
|
||||
|
||||
export default function UseCaseDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string };
|
||||
}) {
|
||||
const page = getUseCasePage(params.slug);
|
||||
|
||||
if (!page) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<UseCasePageTemplate
|
||||
title={page.title}
|
||||
description={page.metaDescription}
|
||||
eyebrow={page.eyebrow}
|
||||
intro={page.intro}
|
||||
pageType="use_case"
|
||||
cluster={page.cluster}
|
||||
useCase={page.slug}
|
||||
breadcrumbs={[
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Use Cases', url: '/use-cases' },
|
||||
{ name: page.title, url: page.href },
|
||||
]}
|
||||
answer={page.answer}
|
||||
whenToUse={page.whenToUse}
|
||||
comparisonItems={page.comparisonItems}
|
||||
howToSteps={page.howToSteps}
|
||||
primaryCta={{
|
||||
href: page.parentHref,
|
||||
label: page.ctaLabel,
|
||||
}}
|
||||
secondaryCta={{
|
||||
href: '/use-cases',
|
||||
label: 'Explore more use cases',
|
||||
}}
|
||||
workflowTitle={page.workflowTitle}
|
||||
workflowIntro={page.workflowIntro}
|
||||
workflowCards={page.workflowCards}
|
||||
checklistTitle={page.checklistTitle}
|
||||
checklist={page.checklist}
|
||||
supportLinks={page.supportLinks}
|
||||
faq={page.faq}
|
||||
heroImage={page.heroImage}
|
||||
heroImageAlt={page.heroImageAlt}
|
||||
/>
|
||||
);
|
||||
}
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import {
|
||||
buildUseCaseMetadata,
|
||||
UseCasePageTemplate,
|
||||
} from '@/components/marketing/UseCasePageTemplate';
|
||||
import { allUseCases, getUseCasePage } from '@/lib/growth-pages';
|
||||
|
||||
export function generateStaticParams() {
|
||||
return allUseCases.map((item) => ({
|
||||
slug: item.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||
const page = getUseCasePage(params.slug);
|
||||
|
||||
if (!page) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return buildUseCaseMetadata({
|
||||
title: page.title,
|
||||
description: page.metaDescription,
|
||||
canonicalPath: page.href,
|
||||
});
|
||||
}
|
||||
|
||||
export default function UseCaseDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string };
|
||||
}) {
|
||||
const page = getUseCasePage(params.slug);
|
||||
|
||||
if (!page) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<UseCasePageTemplate
|
||||
title={page.title}
|
||||
description={page.metaDescription}
|
||||
eyebrow={page.eyebrow}
|
||||
intro={page.intro}
|
||||
pageType="use_case"
|
||||
cluster={page.cluster}
|
||||
useCase={page.slug}
|
||||
breadcrumbs={[
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Use Cases', url: '/use-cases' },
|
||||
{ name: page.title, url: page.href },
|
||||
]}
|
||||
answer={page.answer}
|
||||
whenToUse={page.whenToUse}
|
||||
comparisonItems={page.comparisonItems}
|
||||
howToSteps={page.howToSteps}
|
||||
primaryCta={{
|
||||
href: page.parentHref,
|
||||
label: page.ctaLabel,
|
||||
}}
|
||||
secondaryCta={{
|
||||
href: '/use-cases',
|
||||
label: 'Explore more use cases',
|
||||
}}
|
||||
workflowTitle={page.workflowTitle}
|
||||
workflowIntro={page.workflowIntro}
|
||||
workflowCards={page.workflowCards}
|
||||
checklistTitle={page.checklistTitle}
|
||||
checklist={page.checklist}
|
||||
supportLinks={page.supportLinks}
|
||||
faq={page.faq}
|
||||
heroImage={page.heroImage}
|
||||
heroImageAlt={page.heroImageAlt}
|
||||
authoritySignals={page.authoritySignals}
|
||||
directAnswer={page.directAnswer}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,158 +1,158 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import { MarketingPageTracker, TrackedCtaLink } from '@/components/marketing/MarketingAnalytics';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'QR Platform Comparisons | QR Master',
|
||||
},
|
||||
description:
|
||||
'Head-to-head QR platform comparisons. See where QR Master fits against Beaconstac / Uniqode on pricing, analytics, privacy, and enterprise requirements.',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/vs',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'QR Platform Comparisons | QR Master',
|
||||
description:
|
||||
'Head-to-head QR platform comparisons focused on pricing, analytics, GDPR, bulk creation, and enterprise fit.',
|
||||
url: 'https://www.qrmaster.net/vs',
|
||||
type: 'website',
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
twitter: {
|
||||
title: 'QR Platform Comparisons | QR Master',
|
||||
description:
|
||||
'Compare QR Master head-to-head with Beaconstac / Uniqode.',
|
||||
},
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Comparisons', url: '/vs' },
|
||||
];
|
||||
|
||||
const pageSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: 'QR Platform Comparisons',
|
||||
url: 'https://www.qrmaster.net/vs',
|
||||
description: 'Head-to-head comparison pages for QR Master against competing QR platforms.',
|
||||
};
|
||||
|
||||
const comparisons = [
|
||||
{
|
||||
href: '/vs/beaconstac',
|
||||
eyebrow: 'Head-to-head',
|
||||
title: 'QR Master vs Beaconstac / Uniqode',
|
||||
description:
|
||||
'A detailed comparison of pricing, GDPR handling, analytics depth, bulk creation, simplicity, and enterprise requirements.',
|
||||
},
|
||||
];
|
||||
|
||||
export default function ComparisonsHubPage() {
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[pageSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<MarketingPageTracker pageType="commercial" cluster="competitor" />
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#F8F7F4', color: '#18181B' }}>
|
||||
<section className="border-b bg-white" style={{ borderColor: '#E4E0D9' }}>
|
||||
<div className="container mx-auto max-w-7xl px-4 pb-20 pt-10 sm:px-6 lg:px-8">
|
||||
<div className="mb-10">
|
||||
<Breadcrumbs items={breadcrumbItems} />
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl">
|
||||
<div
|
||||
className="mb-6 inline-block rounded px-3 py-1.5 text-xs font-semibold uppercase tracking-widest"
|
||||
style={{ backgroundColor: '#DCFCE7', color: '#166534', letterSpacing: '0.12em' }}
|
||||
>
|
||||
QR Master comparisons
|
||||
</div>
|
||||
<h1
|
||||
className="mb-6 text-4xl font-extrabold leading-[1.08] tracking-tight sm:text-5xl"
|
||||
style={{ color: '#111110' }}
|
||||
>
|
||||
Head-to-head comparisons for teams choosing the right QR platform.
|
||||
</h1>
|
||||
<p className="mb-8 text-lg leading-relaxed" style={{ color: '#52525B' }}>
|
||||
These pages are for direct platform decisions, not broad alternative browsing. If you already have two
|
||||
tools in mind and want the tradeoffs laid out clearly, start here.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<TrackedCtaLink
|
||||
href="/vs/beaconstac"
|
||||
ctaLabel="Open Comparison"
|
||||
ctaLocation="hero_primary"
|
||||
pageType="commercial"
|
||||
cluster="competitor"
|
||||
>
|
||||
<Button size="lg" className="h-13 w-full px-8 text-base sm:w-auto">
|
||||
Open Comparison
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
<TrackedCtaLink
|
||||
href="/alternatives"
|
||||
ctaLabel="Browse Alternatives"
|
||||
ctaLocation="hero_secondary"
|
||||
pageType="commercial"
|
||||
cluster="competitor"
|
||||
>
|
||||
<Button variant="outline" size="lg" className="h-13 w-full px-8 text-base sm:w-auto">
|
||||
Browse Alternatives
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-24" style={{ backgroundColor: '#F8F7F4' }}>
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 max-w-3xl">
|
||||
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl" style={{ color: '#111110' }}>
|
||||
Available Comparisons
|
||||
</h2>
|
||||
<p className="text-lg" style={{ color: '#71717A' }}>
|
||||
Direct side-by-side analysis for pricing, privacy, analytics, and operational fit.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{comparisons.map((item) => (
|
||||
<Link key={item.href} href={item.href} className="group block">
|
||||
<Card
|
||||
className="h-full rounded-xl border bg-white p-7 shadow-none transition-transform duration-150 group-hover:-translate-y-1"
|
||||
style={{ borderColor: '#E4E0D9' }}
|
||||
>
|
||||
<p
|
||||
className="mb-4 text-xs font-semibold uppercase tracking-widest"
|
||||
style={{ color: '#71717A', letterSpacing: '0.12em' }}
|
||||
>
|
||||
{item.eyebrow}
|
||||
</p>
|
||||
<h3 className="mb-3 text-xl font-bold" style={{ color: '#18181B' }}>
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed" style={{ color: '#52525B' }}>
|
||||
{item.description}
|
||||
</p>
|
||||
<p className="mt-6 text-sm font-semibold" style={{ color: '#166534' }}>
|
||||
Read comparison
|
||||
</p>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import { MarketingPageTracker, TrackedCtaLink } from '@/components/marketing/MarketingAnalytics';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'QR Platform Comparisons | QR Master',
|
||||
},
|
||||
description:
|
||||
'Head-to-head QR platform comparisons. See where QR Master fits against Beaconstac / Uniqode on pricing, analytics, privacy, and enterprise requirements.',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/vs',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'QR Platform Comparisons | QR Master',
|
||||
description:
|
||||
'Head-to-head QR platform comparisons focused on pricing, analytics, GDPR, bulk creation, and enterprise fit.',
|
||||
url: 'https://www.qrmaster.net/vs',
|
||||
type: 'website',
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
twitter: {
|
||||
title: 'QR Platform Comparisons | QR Master',
|
||||
description:
|
||||
'Compare QR Master head-to-head with Beaconstac / Uniqode.',
|
||||
},
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Comparisons', url: '/vs' },
|
||||
];
|
||||
|
||||
const pageSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: 'QR Platform Comparisons',
|
||||
url: 'https://www.qrmaster.net/vs',
|
||||
description: 'Head-to-head comparison pages for QR Master against competing QR platforms.',
|
||||
};
|
||||
|
||||
const comparisons = [
|
||||
{
|
||||
href: '/vs/beaconstac',
|
||||
eyebrow: 'Head-to-head',
|
||||
title: 'QR Master vs Beaconstac / Uniqode',
|
||||
description:
|
||||
'A detailed comparison of pricing, GDPR handling, analytics depth, bulk creation, simplicity, and enterprise requirements.',
|
||||
},
|
||||
];
|
||||
|
||||
export default function ComparisonsHubPage() {
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[pageSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<MarketingPageTracker pageType="commercial" cluster="competitor" />
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#F8F7F4', color: '#18181B' }}>
|
||||
<section className="border-b bg-white" style={{ borderColor: '#E4E0D9' }}>
|
||||
<div className="container mx-auto max-w-7xl px-4 pb-20 pt-10 sm:px-6 lg:px-8">
|
||||
<div className="mb-10">
|
||||
<Breadcrumbs items={breadcrumbItems} />
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl">
|
||||
<div
|
||||
className="mb-6 inline-block rounded px-3 py-1.5 text-xs font-semibold uppercase tracking-widest"
|
||||
style={{ backgroundColor: '#DCFCE7', color: '#166534', letterSpacing: '0.12em' }}
|
||||
>
|
||||
QR Master comparisons
|
||||
</div>
|
||||
<h1
|
||||
className="mb-6 text-4xl font-extrabold leading-[1.08] tracking-tight sm:text-5xl"
|
||||
style={{ color: '#111110' }}
|
||||
>
|
||||
Head-to-head comparisons for teams choosing the right QR platform.
|
||||
</h1>
|
||||
<p className="mb-8 text-lg leading-relaxed" style={{ color: '#52525B' }}>
|
||||
These pages are for direct platform decisions, not broad alternative browsing. If you already have two
|
||||
tools in mind and want the tradeoffs laid out clearly, start here.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<TrackedCtaLink
|
||||
href="/vs/beaconstac"
|
||||
ctaLabel="Open Comparison"
|
||||
ctaLocation="hero_primary"
|
||||
pageType="commercial"
|
||||
cluster="competitor"
|
||||
>
|
||||
<Button size="lg" className="h-13 w-full px-8 text-base sm:w-auto">
|
||||
Open Comparison
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
<TrackedCtaLink
|
||||
href="/alternatives"
|
||||
ctaLabel="Browse Alternatives"
|
||||
ctaLocation="hero_secondary"
|
||||
pageType="commercial"
|
||||
cluster="competitor"
|
||||
>
|
||||
<Button variant="outline" size="lg" className="h-13 w-full px-8 text-base sm:w-auto">
|
||||
Browse Alternatives
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-24" style={{ backgroundColor: '#F8F7F4' }}>
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 max-w-3xl">
|
||||
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl" style={{ color: '#111110' }}>
|
||||
Available Comparisons
|
||||
</h2>
|
||||
<p className="text-lg" style={{ color: '#71717A' }}>
|
||||
Direct side-by-side analysis for pricing, privacy, analytics, and operational fit.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{comparisons.map((item) => (
|
||||
<Link key={item.href} href={item.href} className="group block">
|
||||
<Card
|
||||
className="h-full rounded-xl border bg-white p-7 shadow-none transition-transform duration-150 group-hover:-translate-y-1"
|
||||
style={{ borderColor: '#E4E0D9' }}
|
||||
>
|
||||
<p
|
||||
className="mb-4 text-xs font-semibold uppercase tracking-widest"
|
||||
style={{ color: '#71717A', letterSpacing: '0.12em' }}
|
||||
>
|
||||
{item.eyebrow}
|
||||
</p>
|
||||
<h3 className="mb-3 text-xl font-bold" style={{ color: '#18181B' }}>
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed" style={{ color: '#52525B' }}>
|
||||
{item.description}
|
||||
</p>
|
||||
<p className="mt-6 text-sm font-semibold" style={{ color: '#166534' }}>
|
||||
Read comparison
|
||||
</p>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { ATTRIBUTION_COOKIE_NAME } from '@/lib/revops';
|
||||
|
||||
export async function POST() {
|
||||
const response = NextResponse.json({ success: true });
|
||||
|
||||
response.cookies.set('userId', '', {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
maxAge: 0,
|
||||
});
|
||||
response.cookies.set('newsletter-admin', '', {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
maxAge: 0,
|
||||
});
|
||||
response.cookies.set(ATTRIBUTION_COOKIE_NAME, '', {
|
||||
httpOnly: false,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
maxAge: 0,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
import { NextResponse } from 'next/server';
|
||||
import { ATTRIBUTION_COOKIE_NAME } from '@/lib/revops';
|
||||
|
||||
export async function POST() {
|
||||
const response = NextResponse.json({ success: true });
|
||||
|
||||
response.cookies.set('userId', '', {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
maxAge: 0,
|
||||
});
|
||||
response.cookies.set('newsletter-admin', '', {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
maxAge: 0,
|
||||
});
|
||||
response.cookies.set(ATTRIBUTION_COOKIE_NAME, '', {
|
||||
httpOnly: false,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
maxAge: 0,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -1,113 +1,113 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { sendActivationNudgeEmail, sendUpgradeNudgeEmail, sendThirtyDayNudgeEmail } from '@/lib/email';
|
||||
|
||||
// Protect with a shared secret — set CRON_SECRET in Vercel env vars
|
||||
function isAuthorized(request: NextRequest): boolean {
|
||||
const authHeader = request.headers.get('authorization');
|
||||
const cronSecret = process.env.CRON_SECRET;
|
||||
if (!cronSecret) return false;
|
||||
return authHeader === `Bearer ${cronSecret}`;
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
if (!isAuthorized(request)) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const threeDaysAgo = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000);
|
||||
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
let activationSent = 0;
|
||||
let upgradeSent = 0;
|
||||
let thirtyDaySent = 0;
|
||||
|
||||
// Day-3: signed up > 3 days ago, never created a QR code, hasn't received this email yet
|
||||
const activationCandidates = await db.user.findMany({
|
||||
where: {
|
||||
createdAt: { lt: threeDaysAgo },
|
||||
activationNudgeSentAt: null,
|
||||
},
|
||||
include: {
|
||||
_count: { select: { qrCodes: true } },
|
||||
},
|
||||
});
|
||||
|
||||
for (const user of activationCandidates) {
|
||||
if (user._count.qrCodes === 0 && user.email) {
|
||||
try {
|
||||
await sendActivationNudgeEmail(user.email, user.name ?? 'there');
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: { activationNudgeSentAt: now },
|
||||
});
|
||||
activationSent++;
|
||||
} catch (err) {
|
||||
console.error(`Activation nudge failed for ${user.email}:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Day-7: signed up > 7 days ago, has ≥1 QR code, still FREE, hasn't received this email yet
|
||||
const upgradeCandidates = await db.user.findMany({
|
||||
where: {
|
||||
createdAt: { lt: sevenDaysAgo },
|
||||
upgradeNudgeSentAt: null,
|
||||
plan: 'FREE',
|
||||
},
|
||||
include: {
|
||||
_count: { select: { qrCodes: true } },
|
||||
},
|
||||
});
|
||||
|
||||
for (const user of upgradeCandidates) {
|
||||
if (user._count.qrCodes > 0 && user.email) {
|
||||
try {
|
||||
await sendUpgradeNudgeEmail(user.email, user.name ?? 'there', user._count.qrCodes);
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: { upgradeNudgeSentAt: now },
|
||||
});
|
||||
upgradeSent++;
|
||||
} catch (err) {
|
||||
console.error(`Upgrade nudge failed for ${user.email}:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Day-30: signed up > 30 days ago, has ≥1 QR code, still FREE, hasn't received this email yet
|
||||
const thirtyDayCandidates = await db.user.findMany({
|
||||
where: {
|
||||
createdAt: { lt: thirtyDaysAgo },
|
||||
thirtyDayNudgeSentAt: null,
|
||||
plan: 'FREE',
|
||||
},
|
||||
include: {
|
||||
_count: { select: { qrCodes: true } },
|
||||
},
|
||||
});
|
||||
|
||||
for (const user of thirtyDayCandidates) {
|
||||
if (user._count.qrCodes > 0 && user.email) {
|
||||
try {
|
||||
await sendThirtyDayNudgeEmail(user.email, user.name ?? 'there', user._count.qrCodes);
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: { thirtyDayNudgeSentAt: now },
|
||||
});
|
||||
thirtyDaySent++;
|
||||
} catch (err) {
|
||||
console.error(`30-day nudge failed for ${user.email}:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
activationNudgesSent: activationSent,
|
||||
upgradeNudgesSent: upgradeSent,
|
||||
thirtyDayNudgesSent: thirtyDaySent,
|
||||
});
|
||||
}
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { sendActivationNudgeEmail, sendUpgradeNudgeEmail, sendThirtyDayNudgeEmail } from '@/lib/email';
|
||||
|
||||
// Protect with a shared secret — set CRON_SECRET in Vercel env vars
|
||||
function isAuthorized(request: NextRequest): boolean {
|
||||
const authHeader = request.headers.get('authorization');
|
||||
const cronSecret = process.env.CRON_SECRET;
|
||||
if (!cronSecret) return false;
|
||||
return authHeader === `Bearer ${cronSecret}`;
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
if (!isAuthorized(request)) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const threeDaysAgo = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000);
|
||||
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
let activationSent = 0;
|
||||
let upgradeSent = 0;
|
||||
let thirtyDaySent = 0;
|
||||
|
||||
// Day-3: signed up > 3 days ago, never created a QR code, hasn't received this email yet
|
||||
const activationCandidates = await db.user.findMany({
|
||||
where: {
|
||||
createdAt: { lt: threeDaysAgo },
|
||||
activationNudgeSentAt: null,
|
||||
},
|
||||
include: {
|
||||
_count: { select: { qrCodes: true } },
|
||||
},
|
||||
});
|
||||
|
||||
for (const user of activationCandidates) {
|
||||
if (user._count.qrCodes === 0 && user.email) {
|
||||
try {
|
||||
await sendActivationNudgeEmail(user.email, user.name ?? 'there');
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: { activationNudgeSentAt: now },
|
||||
});
|
||||
activationSent++;
|
||||
} catch (err) {
|
||||
console.error(`Activation nudge failed for ${user.email}:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Day-7: signed up > 7 days ago, has ≥1 QR code, still FREE, hasn't received this email yet
|
||||
const upgradeCandidates = await db.user.findMany({
|
||||
where: {
|
||||
createdAt: { lt: sevenDaysAgo },
|
||||
upgradeNudgeSentAt: null,
|
||||
plan: 'FREE',
|
||||
},
|
||||
include: {
|
||||
_count: { select: { qrCodes: true } },
|
||||
},
|
||||
});
|
||||
|
||||
for (const user of upgradeCandidates) {
|
||||
if (user._count.qrCodes > 0 && user.email) {
|
||||
try {
|
||||
await sendUpgradeNudgeEmail(user.email, user.name ?? 'there', user._count.qrCodes);
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: { upgradeNudgeSentAt: now },
|
||||
});
|
||||
upgradeSent++;
|
||||
} catch (err) {
|
||||
console.error(`Upgrade nudge failed for ${user.email}:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Day-30: signed up > 30 days ago, has ≥1 QR code, still FREE, hasn't received this email yet
|
||||
const thirtyDayCandidates = await db.user.findMany({
|
||||
where: {
|
||||
createdAt: { lt: thirtyDaysAgo },
|
||||
thirtyDayNudgeSentAt: null,
|
||||
plan: 'FREE',
|
||||
},
|
||||
include: {
|
||||
_count: { select: { qrCodes: true } },
|
||||
},
|
||||
});
|
||||
|
||||
for (const user of thirtyDayCandidates) {
|
||||
if (user._count.qrCodes > 0 && user.email) {
|
||||
try {
|
||||
await sendThirtyDayNudgeEmail(user.email, user.name ?? 'there', user._count.qrCodes);
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: { thirtyDayNudgeSentAt: now },
|
||||
});
|
||||
thirtyDaySent++;
|
||||
} catch (err) {
|
||||
console.error(`30-day nudge failed for ${user.email}:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
activationNudgesSent: activationSent,
|
||||
upgradeNudgesSent: upgradeSent,
|
||||
thirtyDayNudgesSent: thirtyDaySent,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,119 +1,119 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { db } from '@/lib/db';
|
||||
import { csrfProtection } from '@/lib/csrf';
|
||||
import { getClientIdentifier, rateLimit, RateLimits } from '@/lib/rateLimit';
|
||||
import { onboardingUpdateSchema, validateRequest } from '@/lib/validationSchemas';
|
||||
import { getOnboardingState, triggerLifecycleScoring } from '@/lib/revops-server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const userId = cookies().get('userId')?.value;
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const state = await getOnboardingState(userId);
|
||||
|
||||
if (!state) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json(state);
|
||||
} catch (error) {
|
||||
console.error('Error fetching onboarding state:', error);
|
||||
return NextResponse.json({ error: 'Failed to fetch onboarding state' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const csrfCheck = csrfProtection(request);
|
||||
if (!csrfCheck.valid) {
|
||||
return NextResponse.json({ error: csrfCheck.error }, { status: 403 });
|
||||
}
|
||||
|
||||
const userId = cookies().get('userId')?.value;
|
||||
const clientId = userId || getClientIdentifier(request);
|
||||
const rateLimitResult = rateLimit(clientId, RateLimits.PROFILE_UPDATE);
|
||||
|
||||
if (!rateLimitResult.success) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Too many requests. Please try again later.',
|
||||
retryAfter: Math.ceil((rateLimitResult.reset - Date.now()) / 1000),
|
||||
},
|
||||
{ status: 429 }
|
||||
);
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const validation = await validateRequest(onboardingUpdateSchema, body);
|
||||
|
||||
if (!validation.success) {
|
||||
return NextResponse.json(validation.error, { status: 400 });
|
||||
}
|
||||
|
||||
const data = validation.data;
|
||||
const now = new Date();
|
||||
const existingUser = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
onboardingStartedAt: true,
|
||||
sourceConfirmedAt: true,
|
||||
useCaseSelectedAt: true,
|
||||
goalSelectedAt: true,
|
||||
profileCompletedAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingUser) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
await db.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
onboardingStartedAt: existingUser.onboardingStartedAt ?? now,
|
||||
signupSourceSelfReported: data.signupSourceSelfReported,
|
||||
primaryUseCase: data.primaryUseCase,
|
||||
primaryGoal: data.primaryGoal,
|
||||
jobRole: data.jobRole,
|
||||
companyName: data.companyName,
|
||||
companyWebsite: data.companyWebsite,
|
||||
teamSizeBucket: data.teamSizeBucket,
|
||||
sourceConfirmedAt:
|
||||
data.signupSourceSelfReported && !existingUser.sourceConfirmedAt
|
||||
? now
|
||||
: undefined,
|
||||
useCaseSelectedAt:
|
||||
data.primaryUseCase && !existingUser.useCaseSelectedAt
|
||||
? now
|
||||
: undefined,
|
||||
goalSelectedAt:
|
||||
data.primaryGoal && !existingUser.goalSelectedAt
|
||||
? now
|
||||
: undefined,
|
||||
profileCompletedAt:
|
||||
data.markProfileComplete && !existingUser.profileCompletedAt
|
||||
? now
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
triggerLifecycleScoring(userId, 'onboarding_update');
|
||||
const state = await getOnboardingState(userId);
|
||||
|
||||
return NextResponse.json({ success: true, state });
|
||||
} catch (error) {
|
||||
console.error('Error updating onboarding state:', error);
|
||||
return NextResponse.json({ error: 'Failed to update onboarding state' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { db } from '@/lib/db';
|
||||
import { csrfProtection } from '@/lib/csrf';
|
||||
import { getClientIdentifier, rateLimit, RateLimits } from '@/lib/rateLimit';
|
||||
import { onboardingUpdateSchema, validateRequest } from '@/lib/validationSchemas';
|
||||
import { getOnboardingState, triggerLifecycleScoring } from '@/lib/revops-server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const userId = cookies().get('userId')?.value;
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const state = await getOnboardingState(userId);
|
||||
|
||||
if (!state) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json(state);
|
||||
} catch (error) {
|
||||
console.error('Error fetching onboarding state:', error);
|
||||
return NextResponse.json({ error: 'Failed to fetch onboarding state' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const csrfCheck = csrfProtection(request);
|
||||
if (!csrfCheck.valid) {
|
||||
return NextResponse.json({ error: csrfCheck.error }, { status: 403 });
|
||||
}
|
||||
|
||||
const userId = cookies().get('userId')?.value;
|
||||
const clientId = userId || getClientIdentifier(request);
|
||||
const rateLimitResult = rateLimit(clientId, RateLimits.PROFILE_UPDATE);
|
||||
|
||||
if (!rateLimitResult.success) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Too many requests. Please try again later.',
|
||||
retryAfter: Math.ceil((rateLimitResult.reset - Date.now()) / 1000),
|
||||
},
|
||||
{ status: 429 }
|
||||
);
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const validation = await validateRequest(onboardingUpdateSchema, body);
|
||||
|
||||
if (!validation.success) {
|
||||
return NextResponse.json(validation.error, { status: 400 });
|
||||
}
|
||||
|
||||
const data = validation.data;
|
||||
const now = new Date();
|
||||
const existingUser = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
onboardingStartedAt: true,
|
||||
sourceConfirmedAt: true,
|
||||
useCaseSelectedAt: true,
|
||||
goalSelectedAt: true,
|
||||
profileCompletedAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingUser) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
await db.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
onboardingStartedAt: existingUser.onboardingStartedAt ?? now,
|
||||
signupSourceSelfReported: data.signupSourceSelfReported,
|
||||
primaryUseCase: data.primaryUseCase,
|
||||
primaryGoal: data.primaryGoal,
|
||||
jobRole: data.jobRole,
|
||||
companyName: data.companyName,
|
||||
companyWebsite: data.companyWebsite,
|
||||
teamSizeBucket: data.teamSizeBucket,
|
||||
sourceConfirmedAt:
|
||||
data.signupSourceSelfReported && !existingUser.sourceConfirmedAt
|
||||
? now
|
||||
: undefined,
|
||||
useCaseSelectedAt:
|
||||
data.primaryUseCase && !existingUser.useCaseSelectedAt
|
||||
? now
|
||||
: undefined,
|
||||
goalSelectedAt:
|
||||
data.primaryGoal && !existingUser.goalSelectedAt
|
||||
? now
|
||||
: undefined,
|
||||
profileCompletedAt:
|
||||
data.markProfileComplete && !existingUser.profileCompletedAt
|
||||
? now
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
triggerLifecycleScoring(userId, 'onboarding_update');
|
||||
const state = await getOnboardingState(userId);
|
||||
|
||||
return NextResponse.json({ success: true, state });
|
||||
} catch (error) {
|
||||
console.error('Error updating onboarding state:', error);
|
||||
return NextResponse.json({ error: 'Failed to update onboarding state' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const userId = cookies().get('userId')?.value;
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Get user with plan info
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
plan: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Count dynamic QR codes
|
||||
const dynamicQRCount = await db.qRCode.count({
|
||||
where: {
|
||||
userId,
|
||||
type: 'DYNAMIC',
|
||||
},
|
||||
});
|
||||
|
||||
// Count static QR codes
|
||||
const staticQRCount = await db.qRCode.count({
|
||||
where: {
|
||||
userId,
|
||||
type: 'STATIC',
|
||||
},
|
||||
});
|
||||
|
||||
// Determine limits based on plan
|
||||
let dynamicLimit = 3; // FREE plan default
|
||||
if (user.plan === 'PRO') {
|
||||
dynamicLimit = 50;
|
||||
} else if (user.plan === 'BUSINESS') {
|
||||
dynamicLimit = 500;
|
||||
} else if ((user.plan as string) === 'ENTERPRISE') {
|
||||
dynamicLimit = 99999;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
dynamicUsed: dynamicQRCount,
|
||||
dynamicLimit,
|
||||
staticUsed: staticQRCount,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching user stats:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const userId = cookies().get('userId')?.value;
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Get user with plan info
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
plan: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Count dynamic QR codes
|
||||
const dynamicQRCount = await db.qRCode.count({
|
||||
where: {
|
||||
userId,
|
||||
type: 'DYNAMIC',
|
||||
},
|
||||
});
|
||||
|
||||
// Count static QR codes
|
||||
const staticQRCount = await db.qRCode.count({
|
||||
where: {
|
||||
userId,
|
||||
type: 'STATIC',
|
||||
},
|
||||
});
|
||||
|
||||
// Determine limits based on plan
|
||||
let dynamicLimit = 3; // FREE plan default
|
||||
if (user.plan === 'PRO') {
|
||||
dynamicLimit = 50;
|
||||
} else if (user.plan === 'BUSINESS') {
|
||||
dynamicLimit = 500;
|
||||
} else if ((user.plan as string) === 'ENTERPRISE') {
|
||||
dynamicLimit = 99999;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
dynamicUsed: dynamicQRCount,
|
||||
dynamicLimit,
|
||||
staticUsed: staticQRCount,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching user stats:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
import type { Metadata } from 'next';
|
||||
import OnboardingClient from './OnboardingClient';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Onboarding | QR Master',
|
||||
description: 'Set up your QR Master workspace and create your first QR code.',
|
||||
robots: {
|
||||
index: false,
|
||||
follow: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default function OnboardingPage() {
|
||||
return <OnboardingClient />;
|
||||
}
|
||||
import type { Metadata } from 'next';
|
||||
import OnboardingClient from './OnboardingClient';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Onboarding | QR Master',
|
||||
description: 'Set up your QR Master workspace and create your first QR code.',
|
||||
robots: {
|
||||
index: false,
|
||||
follow: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default function OnboardingPage() {
|
||||
return <OnboardingClient />;
|
||||
}
|
||||
|
||||
@@ -1,118 +1,118 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Free QR Code Tools | URL, vCard, WiFi & More | QR Master',
|
||||
description:
|
||||
'Access free QR code tools for URLs, WiFi, vCards, barcodes, and more. Use QR Master for static codes, dynamic QR codes, and QR code tracking.',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free QR Code Tools Collection',
|
||||
description:
|
||||
'All your QR code needs in one place. Free forever static codes.',
|
||||
url: 'https://www.qrmaster.net/tools',
|
||||
siteName: 'QR Master',
|
||||
type: 'website',
|
||||
},
|
||||
};
|
||||
|
||||
export default function ToolsHubPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 pt-20">
|
||||
<section className="bg-white py-20 px-4 border-b border-slate-200">
|
||||
<div className="container mx-auto text-center max-w-4xl">
|
||||
<span className="inline-block px-4 py-1.5 rounded-full bg-indigo-50 text-indigo-700 font-semibold text-sm mb-6">
|
||||
100% Free Forever
|
||||
</span>
|
||||
<h1 className="text-4xl md:text-6xl font-extrabold text-slate-900 mb-6 tracking-tight">
|
||||
Free QR Code Tools
|
||||
</h1>
|
||||
<p className="text-xl text-slate-600 mb-8 leading-relaxed max-w-2xl mx-auto">
|
||||
Generate static QR codes for any purpose. No credit card, no
|
||||
expiration, no hidden fees.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="py-12">
|
||||
<FreeToolsGrid />
|
||||
</div>
|
||||
|
||||
<section className="bg-white px-4 py-16">
|
||||
<div className="container mx-auto max-w-5xl">
|
||||
<div className="mb-8 text-center">
|
||||
<h2 className="text-2xl font-bold text-slate-900">
|
||||
Frequently used QR tools
|
||||
</h2>
|
||||
<p className="mx-auto mt-3 max-w-2xl text-slate-600">
|
||||
These tools cover common workplace, venue, and regional QR code
|
||||
workflows.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{[
|
||||
{
|
||||
href: '/tools/teams-qr-code',
|
||||
title: 'Teams QR Code Generator',
|
||||
description: 'Create QR codes for meeting rooms and invites.',
|
||||
},
|
||||
{
|
||||
href: '/tools/wifi-qr-code',
|
||||
title: 'WiFi QR Code Generator',
|
||||
description: 'Share guest WiFi without typing passwords.',
|
||||
},
|
||||
{
|
||||
href: '/qr-code-erstellen',
|
||||
title: 'QR Code Erstellen',
|
||||
description: 'German QR generator page for local users.',
|
||||
},
|
||||
].map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className="rounded-lg border border-slate-200 bg-slate-50 p-5 text-left transition-colors hover:border-indigo-200 hover:bg-indigo-50"
|
||||
>
|
||||
<h3 className="font-semibold text-slate-900">{item.title}</h3>
|
||||
<p className="mt-2 text-sm leading-6 text-slate-600">
|
||||
{item.description}
|
||||
</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-20 px-4">
|
||||
<div className="container mx-auto max-w-3xl text-center">
|
||||
<h2 className="text-2xl font-bold text-slate-900 mb-4">
|
||||
Why are these tools free?
|
||||
</h2>
|
||||
<p className="text-slate-600 mb-8">
|
||||
We believe basic QR codes should be accessible to everyone. Our
|
||||
static QR codes encode your data directly into the image, meaning we
|
||||
don't need to host tracking servers for them. That's why they are
|
||||
free forever. If you need tracking and editability, use our{' '}
|
||||
<a
|
||||
href="/dynamic-qr-code-generator"
|
||||
className="font-semibold text-indigo-700 underline"
|
||||
>
|
||||
dynamic QR code generator
|
||||
</a>{' '}
|
||||
or learn how{' '}
|
||||
<a
|
||||
href="/qr-code-tracking"
|
||||
className="font-semibold text-indigo-700 underline"
|
||||
>
|
||||
QR code tracking
|
||||
</a>{' '}
|
||||
works.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Free QR Code Tools | URL, vCard, WiFi & More | QR Master',
|
||||
description:
|
||||
'Access free QR code tools for URLs, WiFi, vCards, barcodes, and more. Use QR Master for static codes, dynamic QR codes, and QR code tracking.',
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Free QR Code Tools Collection',
|
||||
description:
|
||||
'All your QR code needs in one place. Free forever static codes.',
|
||||
url: 'https://www.qrmaster.net/tools',
|
||||
siteName: 'QR Master',
|
||||
type: 'website',
|
||||
},
|
||||
};
|
||||
|
||||
export default function ToolsHubPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 pt-20">
|
||||
<section className="bg-white py-20 px-4 border-b border-slate-200">
|
||||
<div className="container mx-auto text-center max-w-4xl">
|
||||
<span className="inline-block px-4 py-1.5 rounded-full bg-indigo-50 text-indigo-700 font-semibold text-sm mb-6">
|
||||
100% Free Forever
|
||||
</span>
|
||||
<h1 className="text-4xl md:text-6xl font-extrabold text-slate-900 mb-6 tracking-tight">
|
||||
Free QR Code Tools
|
||||
</h1>
|
||||
<p className="text-xl text-slate-600 mb-8 leading-relaxed max-w-2xl mx-auto">
|
||||
Generate static QR codes for any purpose. No credit card, no
|
||||
expiration, no hidden fees.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="py-12">
|
||||
<FreeToolsGrid />
|
||||
</div>
|
||||
|
||||
<section className="bg-white px-4 py-16">
|
||||
<div className="container mx-auto max-w-5xl">
|
||||
<div className="mb-8 text-center">
|
||||
<h2 className="text-2xl font-bold text-slate-900">
|
||||
Frequently used QR tools
|
||||
</h2>
|
||||
<p className="mx-auto mt-3 max-w-2xl text-slate-600">
|
||||
These tools cover common workplace, venue, and regional QR code
|
||||
workflows.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{[
|
||||
{
|
||||
href: '/tools/teams-qr-code',
|
||||
title: 'Teams QR Code Generator',
|
||||
description: 'Create QR codes for meeting rooms and invites.',
|
||||
},
|
||||
{
|
||||
href: '/tools/wifi-qr-code',
|
||||
title: 'WiFi QR Code Generator',
|
||||
description: 'Share guest WiFi without typing passwords.',
|
||||
},
|
||||
{
|
||||
href: '/qr-code-erstellen',
|
||||
title: 'QR Code Erstellen',
|
||||
description: 'German QR generator page for local users.',
|
||||
},
|
||||
].map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className="rounded-lg border border-slate-200 bg-slate-50 p-5 text-left transition-colors hover:border-indigo-200 hover:bg-indigo-50"
|
||||
>
|
||||
<h3 className="font-semibold text-slate-900">{item.title}</h3>
|
||||
<p className="mt-2 text-sm leading-6 text-slate-600">
|
||||
{item.description}
|
||||
</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-20 px-4">
|
||||
<div className="container mx-auto max-w-3xl text-center">
|
||||
<h2 className="text-2xl font-bold text-slate-900 mb-4">
|
||||
Why are these tools free?
|
||||
</h2>
|
||||
<p className="text-slate-600 mb-8">
|
||||
We believe basic QR codes should be accessible to everyone. Our
|
||||
static QR codes encode your data directly into the image, meaning we
|
||||
don't need to host tracking servers for them. That's why they are
|
||||
free forever. If you need tracking and editability, use our{' '}
|
||||
<a
|
||||
href="/dynamic-qr-code-generator"
|
||||
className="font-semibold text-indigo-700 underline"
|
||||
>
|
||||
dynamic QR code generator
|
||||
</a>{' '}
|
||||
or learn how{' '}
|
||||
<a
|
||||
href="/qr-code-tracking"
|
||||
className="font-semibold text-indigo-700 underline"
|
||||
>
|
||||
QR code tracking
|
||||
</a>{' '}
|
||||
works.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,452 +1,452 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||
import {
|
||||
generateFaqSchema,
|
||||
generateSoftwareAppSchema,
|
||||
} from '@/lib/schema-utils';
|
||||
import { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid';
|
||||
import de from '@/i18n/de.json';
|
||||
import { FAQ } from '@/components/marketing/FAQ';
|
||||
import { ScrollToTop } from '@/components/ui/ScrollToTop';
|
||||
import {
|
||||
Barcode as BarcodeIcon,
|
||||
Check,
|
||||
Shield,
|
||||
Zap,
|
||||
Printer,
|
||||
Download,
|
||||
} from 'lucide-react';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Barcode Generator Kostenlos – EAN, UPC, Code 128 Online',
|
||||
},
|
||||
description:
|
||||
'Erstellen Sie Barcodes kostenlos online: EAN-13, UPC-A und Code 128. Sofort druckfertig. Für Einzelhandel, Lager und Logistik. Keine Anmeldung erforderlich.',
|
||||
keywords: [
|
||||
'barcode generator',
|
||||
'barcode generator kostenlos',
|
||||
'ean-13 generator',
|
||||
'upc-a generator',
|
||||
'code 128 generator',
|
||||
'barcode erstellen',
|
||||
'barcode online erstellen',
|
||||
'strichcode generator',
|
||||
'barcode für amazon erstellen',
|
||||
'ean barcode generator',
|
||||
'produktbarcode erstellen',
|
||||
'lagerverwaltung barcode',
|
||||
'barcode drucken',
|
||||
],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/barcode-generator',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/tools/barcode-generator',
|
||||
en: 'https://www.qrmaster.net/tools/barcode-generator',
|
||||
de: 'https://www.qrmaster.net/tools/barcode-generator',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Barcode Generator – Kostenlos EAN, UPC & Code 128 erstellen',
|
||||
description:
|
||||
'Barcode Generator: Erstellen Sie professionelle Barcodes für Einzelhandel, Lager und Logistik. Kostenlos, ohne Anmeldung. Sofort druckfertig.',
|
||||
url: 'https://www.qrmaster.net/tools/barcode-generator',
|
||||
locale: 'de_DE',
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: 'https://www.qrmaster.net/barcode-generator-preview.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Barcode Generator – Kostenlos Online erstellen',
|
||||
description:
|
||||
'EAN-13, UPC-A und Code 128 Barcodes kostenlos erstellen. Sofort druckfertig.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
const faqQuestions = {
|
||||
'Was ist ein Barcode Generator?': {
|
||||
question: 'Was ist ein Barcode Generator?',
|
||||
answer:
|
||||
'Ein Barcode Generator ist ein Online-Tool, das Zahlen oder Texte in scannbare Barcode-Bilder umwandelt. Diese können für Produkte, Etiketten und Inventarsysteme verwendet werden.',
|
||||
},
|
||||
'Ist der Barcode Generator kostenlos?': {
|
||||
question: 'Ist der Barcode Generator kostenlos?',
|
||||
answer:
|
||||
'Ja, unser Barcode Generator ist vollständig kostenlos nutzbar. Sie können Barcodes ohne Anmeldung generieren, herunterladen und drucken.',
|
||||
},
|
||||
'Welches Barcode-Format brauche ich?': {
|
||||
question: 'Welches Barcode-Format brauche ich?',
|
||||
answer:
|
||||
'EAN-13 ist der Standard für Einzelhandel in Europa und weltweit. UPC-A wird hauptsächlich in den USA und Kanada verwendet. Code 128 eignet sich am besten für Logistik und interne Verfolgung, da er Buchstaben und Zahlen unterstützt.',
|
||||
},
|
||||
'Kann ich Barcodes als Vektorgrafik (SVG) herunterladen?': {
|
||||
question: 'Kann ich Barcodes als Vektorgrafik (SVG) herunterladen?',
|
||||
answer:
|
||||
'Ja! SVG-Dateien sind vektorbasiert und können ohne Qualitätsverlust auf jede Größe skaliert werden. Perfekt für professionelle Produktverpackungen und Etiketten.',
|
||||
},
|
||||
'Wie erstelle ich einen Barcode online?': {
|
||||
question: 'Wie erstelle ich einen Barcode online?',
|
||||
answer:
|
||||
'Geben Sie Ihre Produktnummer oder Ihren Text ein, wählen Sie das gewünschte Barcode-Format (z.B. EAN-13 oder Code 128) und der Barcode wird sofort erstellt. Dann können Sie ihn als PNG oder SVG herunterladen.',
|
||||
},
|
||||
'Sind die erstellten Barcodes scannbar?': {
|
||||
question: 'Sind die erstellten Barcodes scannbar?',
|
||||
answer:
|
||||
'Ja, wir generieren standardkonforme Barcodes, die von jedem handelsüblichen Barcode-Scanner gelesen werden können, einschließlich Smartphone-Kamera-Apps.',
|
||||
},
|
||||
'Kann ich diese Barcodes für Amazon verwenden?': {
|
||||
question: 'Kann ich diese Barcodes für Amazon verwenden?',
|
||||
answer:
|
||||
'Sie können hier das Barcode-Bild erstellen, wenn Sie bereits eine gültige EAN/UPC-Nummer haben. Offizielle GS1-Registrierungsnummern erhalten Sie jedoch nur direkt bei GS1, um Produkte auf Amazon oder in großen Einzelhandelssystemen zu listen.',
|
||||
},
|
||||
'Was ist der Unterschied zwischen Barcode und QR-Code?': {
|
||||
question: 'Was ist der Unterschied zwischen Barcode und QR-Code?',
|
||||
answer:
|
||||
'Ein Barcode speichert Daten horizontal (1D) und wird hauptsächlich für Produktkennungen verwendet. Ein QR-Code speichert Daten in 2D (Matrix) und kann viel mehr Informationen enthalten, wie URLs, Kontaktdaten oder WLAN-Zugangsdaten.',
|
||||
},
|
||||
'Kann ich mehrere Barcodes gleichzeitig erstellen?': {
|
||||
question: 'Kann ich mehrere Barcodes gleichzeitig erstellen?',
|
||||
answer:
|
||||
'Ja, mit einem PRO-Account können Sie mehrere Barcodes gleichzeitig generieren. Dies ist ideal für große Produktmengen oder Lagerverwaltung.',
|
||||
},
|
||||
'Was ist eine Prüfziffer bei Barcodes?': {
|
||||
question: 'Was ist eine Prüfziffer bei Barcodes?',
|
||||
answer:
|
||||
'Die Prüfziffer ist die letzte Ziffer in einem Barcode, die mathematisch aus den anderen Ziffern berechnet wird. Sie stellt sicher, dass der Barcode korrekt gescannt wird, selbst wenn er leicht beschädigt oder verkratzt ist.',
|
||||
},
|
||||
};
|
||||
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Barcode Generator',
|
||||
'Erstellen Sie druckfertige Barcodes für EAN, UPC, Code 128 und weitere Formate. Kostenlos und ohne Anmeldung.',
|
||||
'/barcode-generator-preview.png',
|
||||
'UtilitiesApplication'
|
||||
),
|
||||
generateFaqSchema(faqQuestions),
|
||||
],
|
||||
};
|
||||
|
||||
export default function BarcodeGeneratorDEPage() {
|
||||
const t = de;
|
||||
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
|
||||
{/* SEO Content für Suchmaschinen */}
|
||||
<div className="sr-only" aria-hidden="false">
|
||||
<p>
|
||||
QR Masters kostenloser Barcode Generator ermöglicht es Ihnen,
|
||||
professionelle Barcodes für Produkte, Etiketten und Inventarsysteme zu
|
||||
erstellen. Unterstützt EAN-13 für europäischen Einzelhandel, UPC-A für
|
||||
US-Markt und Code 128 für Logistik und interne Verwendung. Alle
|
||||
Barcodes sind standardkonform und sofort druckfertig.
|
||||
</p>
|
||||
<p>
|
||||
Unser Barcode Generator funktioniert vollständig im Browser. Keine
|
||||
Software-Installation erforderlich. Exportieren Sie Ihre Barcodes als
|
||||
PNG für den sofortigen Druck oder als SVG für professionelle
|
||||
Verpackungsdesigns. Perfekt für kleine Unternehmen, Online-Händler und
|
||||
Lagerverwaltung.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative bg-gradient-to-b from-slate-900 to-slate-800 text-white py-16 md:py-24">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" width="100%" height="100%">
|
||||
<defs>
|
||||
<pattern
|
||||
id="barcode_pattern_de"
|
||||
width="60"
|
||||
height="60"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path
|
||||
d="M5 0 V 60 M15 0 V 60 M20 0 V 60 M35 0 V 60 M40 0 V 60 M55 0 V 60"
|
||||
stroke="white"
|
||||
strokeWidth="2"
|
||||
strokeOpacity="0.5"
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#barcode_pattern_de)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl relative z-10">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-400"></span>
|
||||
</span>
|
||||
Kostenloses Tool – Professionell & Schnell
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold tracking-tight mb-6">
|
||||
Kostenloser{' '}
|
||||
<span className="text-blue-400">Barcode Generator</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-slate-300 mb-8 leading-relaxed">
|
||||
Erstellen Sie professionelle Barcodes in Sekunden. Für{' '}
|
||||
<strong className="text-white">
|
||||
Einzelhandel, Lager und Logistik
|
||||
</strong>
|
||||
. Unterstützt EAN-13, UPC-A und Code 128. Keine Anmeldung
|
||||
erforderlich.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-4 text-sm font-medium">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10">
|
||||
<Check className="w-4 h-4 text-blue-400" />
|
||||
Einzelhandel-fähig
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10">
|
||||
<Check className="w-4 h-4 text-blue-400" />
|
||||
SVG-Export
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10">
|
||||
<Check className="w-4 h-4 text-blue-400" />
|
||||
Ohne Anmeldung
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Generator Section */}
|
||||
<section className="py-12 px-4 bg-slate-100">
|
||||
<div className="container mx-auto max-w-7xl">
|
||||
<div className="text-center mb-6">
|
||||
<h2 className="text-2xl font-bold mb-2">Barcode jetzt erstellen</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Geben Sie Ihre Daten ein und laden Sie den Barcode herunter
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-3xl shadow-xl overflow-hidden border border-slate-200">
|
||||
<div className="bg-slate-900 px-6 py-3 flex items-center gap-2">
|
||||
<div className="flex gap-1.5">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500"></div>
|
||||
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
|
||||
<div className="w-3 h-3 rounded-full bg-green-500"></div>
|
||||
</div>
|
||||
<span className="text-white/60 text-sm ml-2">
|
||||
qrmaster.net/tools/barcode-generator
|
||||
</span>
|
||||
</div>
|
||||
<iframe
|
||||
src="https://www.qrmaster.net/tools/barcode-generator"
|
||||
className="w-full h-[650px] border-0"
|
||||
title="Barcode Generator"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center mt-6">
|
||||
<a
|
||||
href="https://www.qrmaster.net/tools/barcode-generator"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-slate-900 transition-colors"
|
||||
>
|
||||
<span>Vollbild öffnen</span>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Barcode Types Info */}
|
||||
<section className="py-16 px-4 bg-white">
|
||||
<div className="container mx-auto max-w-7xl">
|
||||
<h2 className="text-3xl font-bold text-center mb-12">
|
||||
Barcode-Formate im Überblick
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<div className="p-6 border rounded-xl">
|
||||
<div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center mb-4">
|
||||
<BarcodeIcon className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
<h3 className="font-bold text-lg mb-2">EAN-13</h3>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
Internationaler Standard für Einzelhandel. 13 Ziffern, weltweit
|
||||
anerkannt.
|
||||
</p>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<strong>Einsatz:</strong> Produkte in Europa & global
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 border rounded-xl">
|
||||
<div className="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center mb-4">
|
||||
<BarcodeIcon className="w-6 h-6 text-green-600" />
|
||||
</div>
|
||||
<h3 className="font-bold text-lg mb-2">UPC-A</h3>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
Amerikanischer Standard. 12 Ziffern, hauptsächlich in
|
||||
Nordamerika.
|
||||
</p>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<strong>Einsatz:</strong> USA & Kanada Einzelhandel
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 border rounded-xl">
|
||||
<div className="w-12 h-12 bg-purple-100 rounded-xl flex items-center justify-center mb-4">
|
||||
<BarcodeIcon className="w-6 h-6 text-purple-600" />
|
||||
</div>
|
||||
<h3 className="font-bold text-lg mb-2">Code 128</h3>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
Flexible Lösung für Logistik. Alphanumerisch, hohe Datendichte.
|
||||
</p>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<strong>Einsatz:</strong> Versand, Lager, Inventar
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* How It Works */}
|
||||
<section className="py-16 px-4 bg-slate-50">
|
||||
<div className="container mx-auto max-w-7xl">
|
||||
<h2 className="text-3xl font-bold text-center mb-12">
|
||||
So funktioniert unser Barcode Generator
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-5 gap-8">
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
|
||||
1
|
||||
</div>
|
||||
<h3 className="font-bold mb-2">Daten eingeben</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Geben Sie Ihre Produktnummer oder Text ein.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
|
||||
2
|
||||
</div>
|
||||
<h3 className="font-bold mb-2">Format wählen</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
EAN-13, UPC-A oder Code 128 auswählen.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
|
||||
3
|
||||
</div>
|
||||
<h3 className="font-bold mb-2">Vorschau</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Barcode wird in Echtzeit aktualisiert.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
|
||||
4
|
||||
</div>
|
||||
<h3 className="font-bold mb-2">Herunterladen</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Als PNG oder SVG speichern.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
|
||||
5
|
||||
</div>
|
||||
<h3 className="font-bold mb-2">Drucken</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Sofort auf Etiketten drucken.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Use Cases */}
|
||||
<section className="py-16 px-4 bg-white">
|
||||
<div className="container mx-auto max-w-7xl">
|
||||
<h2 className="text-3xl font-bold text-center mb-4">
|
||||
Für wen ist unser Barcode Generator geeignet?
|
||||
</h2>
|
||||
<p className="text-center text-muted-foreground mb-12 max-w-2xl mx-auto">
|
||||
Ob Kleinunternehmer, Online-Händler oder Lagerverwaltung – unser
|
||||
Tool unterstützt Sie bei allen Barcode-Bedarfen.
|
||||
</p>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="p-6 bg-slate-50 rounded-xl">
|
||||
<Printer className="w-8 h-8 text-blue-600 mb-3" />
|
||||
<h3 className="font-bold mb-2">Einzelhandel</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
EAN-13 Barcodes für Produktetiketten im stationären Handel.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-slate-50 rounded-xl">
|
||||
<Shield className="w-8 h-8 text-green-600 mb-3" />
|
||||
<h3 className="font-bold mb-2">E-Commerce</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
UPC/EAN für Amazon, eBay und andere Marktplätze.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-slate-50 rounded-xl">
|
||||
<Zap className="w-8 h-8 text-orange-600 mb-3" />
|
||||
<h3 className="font-bold mb-2">Lager & Logistik</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Code 128 für interne Bestandsverfolgung und Versand.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-slate-50 rounded-xl">
|
||||
<Download className="w-8 h-8 text-purple-600 mb-3" />
|
||||
<h3 className="font-bold mb-2">Druckerei</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
SVG-Export für professionelle Verpackungsdesigns.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<FAQ
|
||||
t={{
|
||||
faq: { title: 'Häufig gestellte Fragen', questions: faqQuestions },
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Free Tools Grid */}
|
||||
<FreeToolsGrid />
|
||||
|
||||
{/* Scroll to Top */}
|
||||
<ScrollToTop />
|
||||
</>
|
||||
);
|
||||
}
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||
import {
|
||||
generateFaqSchema,
|
||||
generateSoftwareAppSchema,
|
||||
} from '@/lib/schema-utils';
|
||||
import { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid';
|
||||
import de from '@/i18n/de.json';
|
||||
import { FAQ } from '@/components/marketing/FAQ';
|
||||
import { ScrollToTop } from '@/components/ui/ScrollToTop';
|
||||
import {
|
||||
Barcode as BarcodeIcon,
|
||||
Check,
|
||||
Shield,
|
||||
Zap,
|
||||
Printer,
|
||||
Download,
|
||||
} from 'lucide-react';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Barcode Generator Kostenlos – EAN, UPC, Code 128 Online',
|
||||
},
|
||||
description:
|
||||
'Erstellen Sie Barcodes kostenlos online: EAN-13, UPC-A und Code 128. Sofort druckfertig. Für Einzelhandel, Lager und Logistik. Keine Anmeldung erforderlich.',
|
||||
keywords: [
|
||||
'barcode generator',
|
||||
'barcode generator kostenlos',
|
||||
'ean-13 generator',
|
||||
'upc-a generator',
|
||||
'code 128 generator',
|
||||
'barcode erstellen',
|
||||
'barcode online erstellen',
|
||||
'strichcode generator',
|
||||
'barcode für amazon erstellen',
|
||||
'ean barcode generator',
|
||||
'produktbarcode erstellen',
|
||||
'lagerverwaltung barcode',
|
||||
'barcode drucken',
|
||||
],
|
||||
alternates: {
|
||||
canonical: 'https://www.qrmaster.net/tools/barcode-generator',
|
||||
languages: {
|
||||
'x-default': 'https://www.qrmaster.net/tools/barcode-generator',
|
||||
en: 'https://www.qrmaster.net/tools/barcode-generator',
|
||||
de: 'https://www.qrmaster.net/tools/barcode-generator',
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Barcode Generator – Kostenlos EAN, UPC & Code 128 erstellen',
|
||||
description:
|
||||
'Barcode Generator: Erstellen Sie professionelle Barcodes für Einzelhandel, Lager und Logistik. Kostenlos, ohne Anmeldung. Sofort druckfertig.',
|
||||
url: 'https://www.qrmaster.net/tools/barcode-generator',
|
||||
locale: 'de_DE',
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: 'https://www.qrmaster.net/barcode-generator-preview.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Barcode Generator – Kostenlos Online erstellen',
|
||||
description:
|
||||
'EAN-13, UPC-A und Code 128 Barcodes kostenlos erstellen. Sofort druckfertig.',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
const faqQuestions = {
|
||||
'Was ist ein Barcode Generator?': {
|
||||
question: 'Was ist ein Barcode Generator?',
|
||||
answer:
|
||||
'Ein Barcode Generator ist ein Online-Tool, das Zahlen oder Texte in scannbare Barcode-Bilder umwandelt. Diese können für Produkte, Etiketten und Inventarsysteme verwendet werden.',
|
||||
},
|
||||
'Ist der Barcode Generator kostenlos?': {
|
||||
question: 'Ist der Barcode Generator kostenlos?',
|
||||
answer:
|
||||
'Ja, unser Barcode Generator ist vollständig kostenlos nutzbar. Sie können Barcodes ohne Anmeldung generieren, herunterladen und drucken.',
|
||||
},
|
||||
'Welches Barcode-Format brauche ich?': {
|
||||
question: 'Welches Barcode-Format brauche ich?',
|
||||
answer:
|
||||
'EAN-13 ist der Standard für Einzelhandel in Europa und weltweit. UPC-A wird hauptsächlich in den USA und Kanada verwendet. Code 128 eignet sich am besten für Logistik und interne Verfolgung, da er Buchstaben und Zahlen unterstützt.',
|
||||
},
|
||||
'Kann ich Barcodes als Vektorgrafik (SVG) herunterladen?': {
|
||||
question: 'Kann ich Barcodes als Vektorgrafik (SVG) herunterladen?',
|
||||
answer:
|
||||
'Ja! SVG-Dateien sind vektorbasiert und können ohne Qualitätsverlust auf jede Größe skaliert werden. Perfekt für professionelle Produktverpackungen und Etiketten.',
|
||||
},
|
||||
'Wie erstelle ich einen Barcode online?': {
|
||||
question: 'Wie erstelle ich einen Barcode online?',
|
||||
answer:
|
||||
'Geben Sie Ihre Produktnummer oder Ihren Text ein, wählen Sie das gewünschte Barcode-Format (z.B. EAN-13 oder Code 128) und der Barcode wird sofort erstellt. Dann können Sie ihn als PNG oder SVG herunterladen.',
|
||||
},
|
||||
'Sind die erstellten Barcodes scannbar?': {
|
||||
question: 'Sind die erstellten Barcodes scannbar?',
|
||||
answer:
|
||||
'Ja, wir generieren standardkonforme Barcodes, die von jedem handelsüblichen Barcode-Scanner gelesen werden können, einschließlich Smartphone-Kamera-Apps.',
|
||||
},
|
||||
'Kann ich diese Barcodes für Amazon verwenden?': {
|
||||
question: 'Kann ich diese Barcodes für Amazon verwenden?',
|
||||
answer:
|
||||
'Sie können hier das Barcode-Bild erstellen, wenn Sie bereits eine gültige EAN/UPC-Nummer haben. Offizielle GS1-Registrierungsnummern erhalten Sie jedoch nur direkt bei GS1, um Produkte auf Amazon oder in großen Einzelhandelssystemen zu listen.',
|
||||
},
|
||||
'Was ist der Unterschied zwischen Barcode und QR-Code?': {
|
||||
question: 'Was ist der Unterschied zwischen Barcode und QR-Code?',
|
||||
answer:
|
||||
'Ein Barcode speichert Daten horizontal (1D) und wird hauptsächlich für Produktkennungen verwendet. Ein QR-Code speichert Daten in 2D (Matrix) und kann viel mehr Informationen enthalten, wie URLs, Kontaktdaten oder WLAN-Zugangsdaten.',
|
||||
},
|
||||
'Kann ich mehrere Barcodes gleichzeitig erstellen?': {
|
||||
question: 'Kann ich mehrere Barcodes gleichzeitig erstellen?',
|
||||
answer:
|
||||
'Ja, mit einem PRO-Account können Sie mehrere Barcodes gleichzeitig generieren. Dies ist ideal für große Produktmengen oder Lagerverwaltung.',
|
||||
},
|
||||
'Was ist eine Prüfziffer bei Barcodes?': {
|
||||
question: 'Was ist eine Prüfziffer bei Barcodes?',
|
||||
answer:
|
||||
'Die Prüfziffer ist die letzte Ziffer in einem Barcode, die mathematisch aus den anderen Ziffern berechnet wird. Sie stellt sicher, dass der Barcode korrekt gescannt wird, selbst wenn er leicht beschädigt oder verkratzt ist.',
|
||||
},
|
||||
};
|
||||
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
generateSoftwareAppSchema(
|
||||
'Barcode Generator',
|
||||
'Erstellen Sie druckfertige Barcodes für EAN, UPC, Code 128 und weitere Formate. Kostenlos und ohne Anmeldung.',
|
||||
'/barcode-generator-preview.png',
|
||||
'UtilitiesApplication'
|
||||
),
|
||||
generateFaqSchema(faqQuestions),
|
||||
],
|
||||
};
|
||||
|
||||
export default function BarcodeGeneratorDEPage() {
|
||||
const t = de;
|
||||
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
|
||||
{/* SEO Content für Suchmaschinen */}
|
||||
<div className="sr-only" aria-hidden="false">
|
||||
<p>
|
||||
QR Masters kostenloser Barcode Generator ermöglicht es Ihnen,
|
||||
professionelle Barcodes für Produkte, Etiketten und Inventarsysteme zu
|
||||
erstellen. Unterstützt EAN-13 für europäischen Einzelhandel, UPC-A für
|
||||
US-Markt und Code 128 für Logistik und interne Verwendung. Alle
|
||||
Barcodes sind standardkonform und sofort druckfertig.
|
||||
</p>
|
||||
<p>
|
||||
Unser Barcode Generator funktioniert vollständig im Browser. Keine
|
||||
Software-Installation erforderlich. Exportieren Sie Ihre Barcodes als
|
||||
PNG für den sofortigen Druck oder als SVG für professionelle
|
||||
Verpackungsdesigns. Perfekt für kleine Unternehmen, Online-Händler und
|
||||
Lagerverwaltung.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative bg-gradient-to-b from-slate-900 to-slate-800 text-white py-16 md:py-24">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" width="100%" height="100%">
|
||||
<defs>
|
||||
<pattern
|
||||
id="barcode_pattern_de"
|
||||
width="60"
|
||||
height="60"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path
|
||||
d="M5 0 V 60 M15 0 V 60 M20 0 V 60 M35 0 V 60 M40 0 V 60 M55 0 V 60"
|
||||
stroke="white"
|
||||
strokeWidth="2"
|
||||
strokeOpacity="0.5"
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#barcode_pattern_de)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl relative z-10">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10">
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-400"></span>
|
||||
</span>
|
||||
Kostenloses Tool – Professionell & Schnell
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold tracking-tight mb-6">
|
||||
Kostenloser{' '}
|
||||
<span className="text-blue-400">Barcode Generator</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-slate-300 mb-8 leading-relaxed">
|
||||
Erstellen Sie professionelle Barcodes in Sekunden. Für{' '}
|
||||
<strong className="text-white">
|
||||
Einzelhandel, Lager und Logistik
|
||||
</strong>
|
||||
. Unterstützt EAN-13, UPC-A und Code 128. Keine Anmeldung
|
||||
erforderlich.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-4 text-sm font-medium">
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10">
|
||||
<Check className="w-4 h-4 text-blue-400" />
|
||||
Einzelhandel-fähig
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10">
|
||||
<Check className="w-4 h-4 text-blue-400" />
|
||||
SVG-Export
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10">
|
||||
<Check className="w-4 h-4 text-blue-400" />
|
||||
Ohne Anmeldung
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Generator Section */}
|
||||
<section className="py-12 px-4 bg-slate-100">
|
||||
<div className="container mx-auto max-w-7xl">
|
||||
<div className="text-center mb-6">
|
||||
<h2 className="text-2xl font-bold mb-2">Barcode jetzt erstellen</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Geben Sie Ihre Daten ein und laden Sie den Barcode herunter
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-3xl shadow-xl overflow-hidden border border-slate-200">
|
||||
<div className="bg-slate-900 px-6 py-3 flex items-center gap-2">
|
||||
<div className="flex gap-1.5">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500"></div>
|
||||
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
|
||||
<div className="w-3 h-3 rounded-full bg-green-500"></div>
|
||||
</div>
|
||||
<span className="text-white/60 text-sm ml-2">
|
||||
qrmaster.net/tools/barcode-generator
|
||||
</span>
|
||||
</div>
|
||||
<iframe
|
||||
src="https://www.qrmaster.net/tools/barcode-generator"
|
||||
className="w-full h-[650px] border-0"
|
||||
title="Barcode Generator"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center mt-6">
|
||||
<a
|
||||
href="https://www.qrmaster.net/tools/barcode-generator"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-slate-900 transition-colors"
|
||||
>
|
||||
<span>Vollbild öffnen</span>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Barcode Types Info */}
|
||||
<section className="py-16 px-4 bg-white">
|
||||
<div className="container mx-auto max-w-7xl">
|
||||
<h2 className="text-3xl font-bold text-center mb-12">
|
||||
Barcode-Formate im Überblick
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<div className="p-6 border rounded-xl">
|
||||
<div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center mb-4">
|
||||
<BarcodeIcon className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
<h3 className="font-bold text-lg mb-2">EAN-13</h3>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
Internationaler Standard für Einzelhandel. 13 Ziffern, weltweit
|
||||
anerkannt.
|
||||
</p>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<strong>Einsatz:</strong> Produkte in Europa & global
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 border rounded-xl">
|
||||
<div className="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center mb-4">
|
||||
<BarcodeIcon className="w-6 h-6 text-green-600" />
|
||||
</div>
|
||||
<h3 className="font-bold text-lg mb-2">UPC-A</h3>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
Amerikanischer Standard. 12 Ziffern, hauptsächlich in
|
||||
Nordamerika.
|
||||
</p>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<strong>Einsatz:</strong> USA & Kanada Einzelhandel
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 border rounded-xl">
|
||||
<div className="w-12 h-12 bg-purple-100 rounded-xl flex items-center justify-center mb-4">
|
||||
<BarcodeIcon className="w-6 h-6 text-purple-600" />
|
||||
</div>
|
||||
<h3 className="font-bold text-lg mb-2">Code 128</h3>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
Flexible Lösung für Logistik. Alphanumerisch, hohe Datendichte.
|
||||
</p>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<strong>Einsatz:</strong> Versand, Lager, Inventar
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* How It Works */}
|
||||
<section className="py-16 px-4 bg-slate-50">
|
||||
<div className="container mx-auto max-w-7xl">
|
||||
<h2 className="text-3xl font-bold text-center mb-12">
|
||||
So funktioniert unser Barcode Generator
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-5 gap-8">
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
|
||||
1
|
||||
</div>
|
||||
<h3 className="font-bold mb-2">Daten eingeben</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Geben Sie Ihre Produktnummer oder Text ein.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
|
||||
2
|
||||
</div>
|
||||
<h3 className="font-bold mb-2">Format wählen</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
EAN-13, UPC-A oder Code 128 auswählen.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
|
||||
3
|
||||
</div>
|
||||
<h3 className="font-bold mb-2">Vorschau</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Barcode wird in Echtzeit aktualisiert.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
|
||||
4
|
||||
</div>
|
||||
<h3 className="font-bold mb-2">Herunterladen</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Als PNG oder SVG speichern.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
|
||||
5
|
||||
</div>
|
||||
<h3 className="font-bold mb-2">Drucken</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Sofort auf Etiketten drucken.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Use Cases */}
|
||||
<section className="py-16 px-4 bg-white">
|
||||
<div className="container mx-auto max-w-7xl">
|
||||
<h2 className="text-3xl font-bold text-center mb-4">
|
||||
Für wen ist unser Barcode Generator geeignet?
|
||||
</h2>
|
||||
<p className="text-center text-muted-foreground mb-12 max-w-2xl mx-auto">
|
||||
Ob Kleinunternehmer, Online-Händler oder Lagerverwaltung – unser
|
||||
Tool unterstützt Sie bei allen Barcode-Bedarfen.
|
||||
</p>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="p-6 bg-slate-50 rounded-xl">
|
||||
<Printer className="w-8 h-8 text-blue-600 mb-3" />
|
||||
<h3 className="font-bold mb-2">Einzelhandel</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
EAN-13 Barcodes für Produktetiketten im stationären Handel.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-slate-50 rounded-xl">
|
||||
<Shield className="w-8 h-8 text-green-600 mb-3" />
|
||||
<h3 className="font-bold mb-2">E-Commerce</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
UPC/EAN für Amazon, eBay und andere Marktplätze.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-slate-50 rounded-xl">
|
||||
<Zap className="w-8 h-8 text-orange-600 mb-3" />
|
||||
<h3 className="font-bold mb-2">Lager & Logistik</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Code 128 für interne Bestandsverfolgung und Versand.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-slate-50 rounded-xl">
|
||||
<Download className="w-8 h-8 text-purple-600 mb-3" />
|
||||
<h3 className="font-bold mb-2">Druckerei</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
SVG-Export für professionelle Verpackungsdesigns.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<FAQ
|
||||
t={{
|
||||
faq: { title: 'Häufig gestellte Fragen', questions: faqQuestions },
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Free Tools Grid */}
|
||||
<FreeToolsGrid />
|
||||
|
||||
{/* Scroll to Top */}
|
||||
<ScrollToTop />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,316 +1,316 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||
import { generateFaqSchema } from '@/lib/schema-utils';
|
||||
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 { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid';
|
||||
import de from '@/i18n/de.json';
|
||||
import { AnswerFirstBlock } from '@/components/marketing/AnswerFirstBlock';
|
||||
|
||||
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 = 'QR Code Erstellen – Kostenlos | QR Master';
|
||||
const description =
|
||||
'Erstellen Sie QR Codes kostenlos in Sekunden. Dynamische QR-Codes für Feedback, PDF und mehr. Mit Tracking, Branding und Massen-Erstellung.';
|
||||
|
||||
return {
|
||||
title: {
|
||||
absolute: 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',
|
||||
'feedback qr code',
|
||||
'pdf qr code',
|
||||
'coupon qr code',
|
||||
'app store qr code',
|
||||
'qr code generator online',
|
||||
'qr code erstellen ohne anmeldung',
|
||||
'qr code für speisekarte erstellen',
|
||||
'qr code erstellen free',
|
||||
'online qr code generator kostenlos',
|
||||
'qr code für restaurant erstellen',
|
||||
'qr code für flyer erstellen',
|
||||
'qr code für visitenkarte erstellen',
|
||||
],
|
||||
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',
|
||||
images: [
|
||||
{
|
||||
url: 'https://www.qrmaster.net/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'QR Code Erstellen - Kostenlos & Sofort',
|
||||
},
|
||||
],
|
||||
},
|
||||
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(),
|
||||
generateFaqSchema(t.faq.questions),
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Server-rendered SEO content for crawlers - GERMAN */}
|
||||
<div className="sr-only" aria-hidden="false">
|
||||
<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 für Feedback, PDF-Dateien, Coupons und App Stores
|
||||
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 aktiven dynamischen QR Codes (8 Typen
|
||||
verfügbar) 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} />
|
||||
|
||||
{/* SEO Content: QR-Code-Typen und Anwendungen */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl py-12">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">
|
||||
Beliebte QR-Code-Typen für deutsche Unternehmen
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="p-6 border rounded-lg">
|
||||
<h3 className="font-semibold text-lg mb-2">Speisekarten QR-Code</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Ideal für Restaurants, Cafés und Bars. Gäste scannen den Code und
|
||||
sehen Ihre Speisekarte digital auf dem Handy –无需接触实物菜单。
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 border rounded-lg">
|
||||
<h3 className="font-semibold text-lg mb-2">Feedback QR-Code</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Sammeln Sie Kundenfeedback direkt nach dem Besuch. Perfekt für
|
||||
Hotels, Zahnarztpraxen und Dienstleister.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 border rounded-lg">
|
||||
<h3 className="font-semibold text-lg mb-2">Coupon QR-Code</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Erstellen Sie einlösbare Coupons mit Zeitlimit. Steigern Sie die
|
||||
Kundentreue in Ihrem Einzelhandel oder Onlineshop.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 border rounded-lg">
|
||||
<h3 className="font-semibold text-lg mb-2">vCard QR-Code</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Digitale Visitenkarte: Kontaktdaten werden mit einem Scan ins
|
||||
Adressbuch übertragen. Für Vertrieb und Networking.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 border rounded-lg">
|
||||
<h3 className="font-semibold text-lg mb-2">PDF QR-Code</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Verteilen Sie PDFs wie Speisekarten, Preislisten oder Anleitungen.
|
||||
Ändern Sie den Inhalt jederzeit ohne Neudruck.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 border rounded-lg">
|
||||
<h3 className="font-semibold text-lg mb-2">WiFi QR-Code</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Gäste verbinden sich automatisch mit Ihrem WLAN. Für Hotels,
|
||||
Coworking Spaces und Gastronomie.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SEO Content: Branchen-Anwendungen */}
|
||||
<div className="bg-slate-50 py-12">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">
|
||||
QR-Codes nutzen: Für jede Branche
|
||||
</h2>
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-col md:flex-row gap-4 items-start">
|
||||
<div className="md:w-1/3 font-semibold">
|
||||
Restaurants & Gastronomie
|
||||
</div>
|
||||
<div className="md:w-2/3 text-sm text-muted-foreground">
|
||||
Digitale Speisekarten statt Papier, Bestellsysteme über QR-Code,
|
||||
Feedback-Schleifen nach dem Besuch. Spart Druckkosten und
|
||||
reduziert Kontaktpunkte.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-4 items-start">
|
||||
<div className="md:w-1/3 font-semibold">Einzelhandel</div>
|
||||
<div className="md:w-2/3 text-sm text-muted-foreground">
|
||||
Produktinformationen per QR-Code, Coupons und Aktionen,
|
||||
Cross-Selling zu Ihrem Onlineshop, Abfrage von
|
||||
Produktbewertungen.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-4 items-start">
|
||||
<div className="md:w-1/3 font-semibold">Events & Messen</div>
|
||||
<div className="md:w-2/3 text-sm text-muted-foreground">
|
||||
Digitale Visitenkarten, Event-Flyer mit Programm-QR-Code,
|
||||
Feedback nach Vorträgen, Social-Media-Verbindungen.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-4 items-start">
|
||||
<div className="md:w-1/3 font-semibold">
|
||||
Hotels & Ferienwohnungen
|
||||
</div>
|
||||
<div className="md:w-2/3 text-sm text-muted-foreground">
|
||||
Check-in über QR-Code, WLAN-Zugang ohne Passwort, digitale
|
||||
Hausordnung, lokale Empfehlungen und Concierge-Service.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-4 items-start">
|
||||
<div className="md:w-1/3 font-semibold">
|
||||
Dienstleister & Agenturen
|
||||
</div>
|
||||
<div className="md:w-2/3 text-sm text-muted-foreground">
|
||||
vCards für Vertriebsteams, Projekt-Updates per PDF-QR-Code,
|
||||
Bewertungsanfragen nach Projektabschluss.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SEO Content: Tipps für höhere Scan-Raten */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl py-12">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">
|
||||
Tipps für mehr QR-Code-Scans
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<h3 className="font-semibold mb-3">Design optimieren</h3>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
<li>• Hoher Kontrast: Dunkler Code auf hellem Hintergrund</li>
|
||||
<li>• Ausreichende Größe: Mindestens 2x2 cm, besser 3x3 cm</li>
|
||||
<li>
|
||||
• Eigenes Logo einbinden für Vertrauen und Wiedererkennung
|
||||
</li>
|
||||
<li>• Farben Ihrer Marke verwenden (Logo-Bereich)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-3">Placement verbessern</h3>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
<li>
|
||||
• Augenhöhe: QR-Codes auf Tischhöhe oder Wandhöhe platzieren
|
||||
</li>
|
||||
<li>• Ausreichend Weißraum um den Code herum</li>
|
||||
<li>• In der Nähe des Call-to-Action: "Scannen Sie hier"</li>
|
||||
<li>• Mehrfach platzieren: Eingang, Theke, Tische, Kasse</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-center text-sm text-muted-foreground mt-6">
|
||||
<strong>Tipp:</strong> Testen Sie Ihren QR-Code vor dem Druck mit
|
||||
mindestens 3 verschiedenen Smartphone-Modellen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Answer First Block (SEO/AEO) - German */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<AnswerFirstBlock
|
||||
whatIsIt="Ein QR-Code (Quick Response Code) ist ein zweidimensionaler Barcode, der von Smartphones gescannt werden kann, um sofort auf Informationen wie Webseiten, Kontaktdaten oder Speisekarten zuzugreifen. Mit QR Master erstellen Sie dynamische Codes, die Sie nachträglich ändern und statistisch auswerten können."
|
||||
whenToUse={[
|
||||
'Wenn Sie Printmaterialien (Flyer, Plakate) drucken und den Link später ändern möchten',
|
||||
'Wenn Sie wissen wollen, wie oft und wo Ihr Code gescannt wurde',
|
||||
'Wenn Sie professionelles Branding mit eigenem Logo benötigen',
|
||||
]}
|
||||
comparison={{
|
||||
leftTitle: 'Statisch',
|
||||
rightTitle: 'Dynamisch',
|
||||
items: [
|
||||
{ label: 'Inhalt änderbar', value: true, text: 'Nein' },
|
||||
{ label: 'Scan-Statistik', value: true, text: 'Nein' },
|
||||
{ label: 'Druckqualität', value: true, text: 'Hoch' },
|
||||
],
|
||||
}}
|
||||
howTo={{
|
||||
steps: [
|
||||
'Kostenlos bei QR Master anmelden',
|
||||
'Ziel-URL eingeben und QR-Code Design anpassen',
|
||||
'Code herunterladen, testen und drucken. Der Inhalt ist jederzeit änderbar.',
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main Interaction: Generator */}
|
||||
<InstantGenerator t={t} />
|
||||
|
||||
{/* Free Tools Grid */}
|
||||
<FreeToolsGrid />
|
||||
|
||||
<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 { generateFaqSchema } from '@/lib/schema-utils';
|
||||
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 { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid';
|
||||
import de from '@/i18n/de.json';
|
||||
import { AnswerFirstBlock } from '@/components/marketing/AnswerFirstBlock';
|
||||
|
||||
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 = 'QR Code Erstellen – Kostenlos | QR Master';
|
||||
const description =
|
||||
'Erstellen Sie QR Codes kostenlos in Sekunden. Dynamische QR-Codes für Feedback, PDF und mehr. Mit Tracking, Branding und Massen-Erstellung.';
|
||||
|
||||
return {
|
||||
title: {
|
||||
absolute: 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',
|
||||
'feedback qr code',
|
||||
'pdf qr code',
|
||||
'coupon qr code',
|
||||
'app store qr code',
|
||||
'qr code generator online',
|
||||
'qr code erstellen ohne anmeldung',
|
||||
'qr code für speisekarte erstellen',
|
||||
'qr code erstellen free',
|
||||
'online qr code generator kostenlos',
|
||||
'qr code für restaurant erstellen',
|
||||
'qr code für flyer erstellen',
|
||||
'qr code für visitenkarte erstellen',
|
||||
],
|
||||
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',
|
||||
images: [
|
||||
{
|
||||
url: 'https://www.qrmaster.net/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'QR Code Erstellen - Kostenlos & Sofort',
|
||||
},
|
||||
],
|
||||
},
|
||||
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(),
|
||||
generateFaqSchema(t.faq.questions),
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Server-rendered SEO content for crawlers - GERMAN */}
|
||||
<div className="sr-only" aria-hidden="false">
|
||||
<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 für Feedback, PDF-Dateien, Coupons und App Stores
|
||||
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 aktiven dynamischen QR Codes (8 Typen
|
||||
verfügbar) 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} />
|
||||
|
||||
{/* SEO Content: QR-Code-Typen und Anwendungen */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl py-12">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">
|
||||
Beliebte QR-Code-Typen für deutsche Unternehmen
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="p-6 border rounded-lg">
|
||||
<h3 className="font-semibold text-lg mb-2">Speisekarten QR-Code</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Ideal für Restaurants, Cafés und Bars. Gäste scannen den Code und
|
||||
sehen Ihre Speisekarte digital auf dem Handy –无需接触实物菜单。
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 border rounded-lg">
|
||||
<h3 className="font-semibold text-lg mb-2">Feedback QR-Code</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Sammeln Sie Kundenfeedback direkt nach dem Besuch. Perfekt für
|
||||
Hotels, Zahnarztpraxen und Dienstleister.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 border rounded-lg">
|
||||
<h3 className="font-semibold text-lg mb-2">Coupon QR-Code</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Erstellen Sie einlösbare Coupons mit Zeitlimit. Steigern Sie die
|
||||
Kundentreue in Ihrem Einzelhandel oder Onlineshop.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 border rounded-lg">
|
||||
<h3 className="font-semibold text-lg mb-2">vCard QR-Code</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Digitale Visitenkarte: Kontaktdaten werden mit einem Scan ins
|
||||
Adressbuch übertragen. Für Vertrieb und Networking.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 border rounded-lg">
|
||||
<h3 className="font-semibold text-lg mb-2">PDF QR-Code</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Verteilen Sie PDFs wie Speisekarten, Preislisten oder Anleitungen.
|
||||
Ändern Sie den Inhalt jederzeit ohne Neudruck.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 border rounded-lg">
|
||||
<h3 className="font-semibold text-lg mb-2">WiFi QR-Code</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Gäste verbinden sich automatisch mit Ihrem WLAN. Für Hotels,
|
||||
Coworking Spaces und Gastronomie.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SEO Content: Branchen-Anwendungen */}
|
||||
<div className="bg-slate-50 py-12">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">
|
||||
QR-Codes nutzen: Für jede Branche
|
||||
</h2>
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-col md:flex-row gap-4 items-start">
|
||||
<div className="md:w-1/3 font-semibold">
|
||||
Restaurants & Gastronomie
|
||||
</div>
|
||||
<div className="md:w-2/3 text-sm text-muted-foreground">
|
||||
Digitale Speisekarten statt Papier, Bestellsysteme über QR-Code,
|
||||
Feedback-Schleifen nach dem Besuch. Spart Druckkosten und
|
||||
reduziert Kontaktpunkte.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-4 items-start">
|
||||
<div className="md:w-1/3 font-semibold">Einzelhandel</div>
|
||||
<div className="md:w-2/3 text-sm text-muted-foreground">
|
||||
Produktinformationen per QR-Code, Coupons und Aktionen,
|
||||
Cross-Selling zu Ihrem Onlineshop, Abfrage von
|
||||
Produktbewertungen.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-4 items-start">
|
||||
<div className="md:w-1/3 font-semibold">Events & Messen</div>
|
||||
<div className="md:w-2/3 text-sm text-muted-foreground">
|
||||
Digitale Visitenkarten, Event-Flyer mit Programm-QR-Code,
|
||||
Feedback nach Vorträgen, Social-Media-Verbindungen.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-4 items-start">
|
||||
<div className="md:w-1/3 font-semibold">
|
||||
Hotels & Ferienwohnungen
|
||||
</div>
|
||||
<div className="md:w-2/3 text-sm text-muted-foreground">
|
||||
Check-in über QR-Code, WLAN-Zugang ohne Passwort, digitale
|
||||
Hausordnung, lokale Empfehlungen und Concierge-Service.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-4 items-start">
|
||||
<div className="md:w-1/3 font-semibold">
|
||||
Dienstleister & Agenturen
|
||||
</div>
|
||||
<div className="md:w-2/3 text-sm text-muted-foreground">
|
||||
vCards für Vertriebsteams, Projekt-Updates per PDF-QR-Code,
|
||||
Bewertungsanfragen nach Projektabschluss.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SEO Content: Tipps für höhere Scan-Raten */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl py-12">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">
|
||||
Tipps für mehr QR-Code-Scans
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<h3 className="font-semibold mb-3">Design optimieren</h3>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
<li>• Hoher Kontrast: Dunkler Code auf hellem Hintergrund</li>
|
||||
<li>• Ausreichende Größe: Mindestens 2x2 cm, besser 3x3 cm</li>
|
||||
<li>
|
||||
• Eigenes Logo einbinden für Vertrauen und Wiedererkennung
|
||||
</li>
|
||||
<li>• Farben Ihrer Marke verwenden (Logo-Bereich)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-3">Placement verbessern</h3>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
<li>
|
||||
• Augenhöhe: QR-Codes auf Tischhöhe oder Wandhöhe platzieren
|
||||
</li>
|
||||
<li>• Ausreichend Weißraum um den Code herum</li>
|
||||
<li>• In der Nähe des Call-to-Action: "Scannen Sie hier"</li>
|
||||
<li>• Mehrfach platzieren: Eingang, Theke, Tische, Kasse</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-center text-sm text-muted-foreground mt-6">
|
||||
<strong>Tipp:</strong> Testen Sie Ihren QR-Code vor dem Druck mit
|
||||
mindestens 3 verschiedenen Smartphone-Modellen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Answer First Block (SEO/AEO) - German */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<AnswerFirstBlock
|
||||
whatIsIt="Ein QR-Code (Quick Response Code) ist ein zweidimensionaler Barcode, der von Smartphones gescannt werden kann, um sofort auf Informationen wie Webseiten, Kontaktdaten oder Speisekarten zuzugreifen. Mit QR Master erstellen Sie dynamische Codes, die Sie nachträglich ändern und statistisch auswerten können."
|
||||
whenToUse={[
|
||||
'Wenn Sie Printmaterialien (Flyer, Plakate) drucken und den Link später ändern möchten',
|
||||
'Wenn Sie wissen wollen, wie oft und wo Ihr Code gescannt wurde',
|
||||
'Wenn Sie professionelles Branding mit eigenem Logo benötigen',
|
||||
]}
|
||||
comparison={{
|
||||
leftTitle: 'Statisch',
|
||||
rightTitle: 'Dynamisch',
|
||||
items: [
|
||||
{ label: 'Inhalt änderbar', value: true, text: 'Nein' },
|
||||
{ label: 'Scan-Statistik', value: true, text: 'Nein' },
|
||||
{ label: 'Druckqualität', value: true, text: 'Hoch' },
|
||||
],
|
||||
}}
|
||||
howTo={{
|
||||
steps: [
|
||||
'Kostenlos bei QR Master anmelden',
|
||||
'Ziel-URL eingeben und QR-Code Design anpassen',
|
||||
'Code herunterladen, testen und drucken. Der Inhalt ist jederzeit änderbar.',
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main Interaction: Generator */}
|
||||
<InstantGenerator t={t} />
|
||||
|
||||
{/* Free Tools Grid */}
|
||||
<FreeToolsGrid />
|
||||
|
||||
<StaticVsDynamic t={t} />
|
||||
<Features t={t} />
|
||||
|
||||
{/* Pricing Section */}
|
||||
<Pricing t={t} />
|
||||
|
||||
{/* FAQ Section */}
|
||||
<FAQ t={t} />
|
||||
|
||||
{/* Scroll to Top Button */}
|
||||
<ScrollToTop />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,35 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import type { FAQItem } from "@/lib/types";
|
||||
|
||||
type Props = { items: FAQItem[]; title?: string };
|
||||
|
||||
export function FAQSection({ items, title = "Frequently Asked Questions" }: Props) {
|
||||
if (!items?.length) return null;
|
||||
|
||||
return (
|
||||
<section className="rounded-xl border border-gray-100 bg-gray-50/50 p-6 my-8">
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-6">{title}</h2>
|
||||
<div className="space-y-4">
|
||||
{items.map((f) => {
|
||||
const cleanAnswer = sanitizeHtml(f.answer, {
|
||||
allowedTags: ['p', 'strong', 'em', 'ul', 'ol', 'li', 'a', 'br', 'code'],
|
||||
allowedAttributes: { 'a': ['href'] }
|
||||
});
|
||||
|
||||
return (
|
||||
<details key={f.question} className="group rounded-lg border border-gray-200 bg-white p-4 open:shadow-sm open:border-blue-200 transition-all">
|
||||
<summary className="cursor-pointer font-semibold text-gray-800 flex justify-between items-center group-open:text-blue-700">
|
||||
{f.question}
|
||||
<span className="text-gray-400 group-open:rotate-180 transition-transform">▼</span>
|
||||
</summary>
|
||||
<div className="prose max-w-none mt-3 text-gray-600 border-t border-gray-100 pt-3" dangerouslySetInnerHTML={{ __html: cleanAnswer }} />
|
||||
</details>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import type { FAQItem } from "@/lib/types";
|
||||
|
||||
type Props = { items: FAQItem[]; title?: string };
|
||||
|
||||
export function FAQSection({ items, title = "Frequently Asked Questions" }: Props) {
|
||||
if (!items?.length) return null;
|
||||
|
||||
return (
|
||||
<section className="rounded-2xl border border-slate-200/60 bg-white p-6 sm:p-8 my-12 shadow-sm">
|
||||
<h2 className="text-2xl font-semibold text-slate-900 mb-8 tracking-tight">{title}</h2>
|
||||
<div className="divide-y divide-slate-100">
|
||||
{items.map((f) => {
|
||||
const cleanAnswer = sanitizeHtml(f.answer, {
|
||||
allowedTags: ['p', 'strong', 'em', 'ul', 'ol', 'li', 'a', 'br', 'code'],
|
||||
allowedAttributes: { 'a': ['href'] }
|
||||
});
|
||||
|
||||
return (
|
||||
<details key={f.question} className="group py-5 first:pt-0 last:pb-0 transition-all">
|
||||
<summary className="list-none [&::-webkit-details-marker]:hidden cursor-pointer font-semibold text-slate-800 flex justify-between items-center group-open:text-indigo-600 hover:text-indigo-600 transition-colors">
|
||||
<span className="pr-4 leading-snug">{f.question}</span>
|
||||
<span className="relative flex h-5 w-5 shrink-0 items-center justify-center text-slate-400 group-open:text-indigo-650 transition-colors">
|
||||
<span className="absolute h-3.5 w-0.5 bg-current transition-transform duration-300 group-open:rotate-90" />
|
||||
<span className="absolute h-0.5 w-3.5 bg-current" />
|
||||
</span>
|
||||
</summary>
|
||||
<div
|
||||
className="prose max-w-none mt-3.5 text-slate-500 text-sm leading-relaxed border-t border-slate-50 pt-3.5"
|
||||
dangerouslySetInnerHTML={{ __html: cleanAnswer }}
|
||||
/>
|
||||
</details>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,171 +1,171 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card, CardContent } from '@/components/ui/Card';
|
||||
import { appendRedirectParam } from '@/lib/auth-flow';
|
||||
import {
|
||||
getChecklistItems,
|
||||
ONBOARDING_CHECKLIST_DISMISS_KEY,
|
||||
ONBOARDING_DOWNLOAD_COMPLETE_EVENT,
|
||||
ONBOARDING_DOWNLOAD_COMPLETE_KEY,
|
||||
} from '@/lib/revops';
|
||||
|
||||
type ChecklistState = {
|
||||
id?: string;
|
||||
signupSourceSelfReported?: string | null;
|
||||
primaryUseCase?: string | null;
|
||||
firstQrCreatedAt?: string | null;
|
||||
firstDynamicQrAt?: string | null;
|
||||
firstScanAt?: string | null;
|
||||
activationAt?: string | null;
|
||||
onboardingCompletedAt?: string | null;
|
||||
};
|
||||
|
||||
type OnboardingChecklistProps = {
|
||||
state: ChecklistState | null;
|
||||
};
|
||||
|
||||
function buildChecklistItems(state: ChecklistState, downloadDone: boolean) {
|
||||
return getChecklistItems(state).map((item) =>
|
||||
item.id === 'download'
|
||||
? {
|
||||
...item,
|
||||
done: downloadDone || Boolean(state.firstScanAt),
|
||||
}
|
||||
: item
|
||||
);
|
||||
}
|
||||
|
||||
export function OnboardingChecklist({ state }: OnboardingChecklistProps) {
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
const [downloadDone, setDownloadDone] = useState(false);
|
||||
const [storageReady, setStorageReady] = useState(false);
|
||||
|
||||
const stepMap: Record<string, number> = {
|
||||
source: 1,
|
||||
'use-case': 2,
|
||||
'first-qr': 7,
|
||||
'first-dynamic': 7,
|
||||
download: 8,
|
||||
scan: 8,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setDismissed(localStorage.getItem(ONBOARDING_CHECKLIST_DISMISS_KEY) === '1');
|
||||
setDownloadDone(localStorage.getItem(ONBOARDING_DOWNLOAD_COMPLETE_KEY) === '1');
|
||||
setStorageReady(true);
|
||||
|
||||
const syncDownloadState = () => {
|
||||
setDownloadDone(localStorage.getItem(ONBOARDING_DOWNLOAD_COMPLETE_KEY) === '1');
|
||||
};
|
||||
|
||||
window.addEventListener(ONBOARDING_DOWNLOAD_COMPLETE_EVENT, syncDownloadState);
|
||||
window.addEventListener('storage', syncDownloadState);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(ONBOARDING_DOWNLOAD_COMPLETE_EVENT, syncDownloadState);
|
||||
window.removeEventListener('storage', syncDownloadState);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const items = state ? buildChecklistItems(state, downloadDone) : [];
|
||||
const completed = items.filter((item) => item.done).length;
|
||||
const allDone = items.length > 0 && completed === items.length;
|
||||
|
||||
useEffect(() => {
|
||||
if (!storageReady || !state) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.onboardingCompletedAt || allDone) {
|
||||
localStorage.setItem(ONBOARDING_CHECKLIST_DISMISS_KEY, '1');
|
||||
setDismissed(true);
|
||||
}
|
||||
}, [allDone, state, storageReady]);
|
||||
|
||||
if (!state || !storageReady || dismissed || state.onboardingCompletedAt || allDone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const progress = Math.round((completed / items.length) * 100);
|
||||
|
||||
return (
|
||||
<Card className="overflow-hidden rounded-[28px] border border-slate-200 bg-white p-0 shadow-none">
|
||||
<CardContent className="space-y-5 p-5 sm:p-6">
|
||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div className="max-w-2xl">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">
|
||||
{allDone ? 'Onboarding complete' : 'Onboarding progress'}
|
||||
</p>
|
||||
<h3 className="mt-2 text-xl font-semibold tracking-tight text-slate-950">
|
||||
{allDone ? 'Your first setup is complete' : 'Finish the first-run checklist'}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-6 text-slate-600">
|
||||
{allDone
|
||||
? 'You created, downloaded, and tested your first code. You can keep this visible or dismiss it.'
|
||||
: 'Keep this minimal checklist visible until the first QR workflow is fully done.'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="rounded-full border border-primary-200 bg-primary-50 px-3 py-1 text-xs font-semibold text-primary-700">
|
||||
{completed}/{items.length}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Dismiss onboarding checklist"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-2xl border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-900"
|
||||
onClick={() => {
|
||||
localStorage.setItem(ONBOARDING_CHECKLIST_DISMISS_KEY, '1');
|
||||
setDismissed(true);
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="h-1.5 overflow-hidden rounded-full bg-slate-100">
|
||||
<div
|
||||
className="h-full rounded-full bg-primary-600 transition-all duration-300"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2 sm:grid-cols-6">
|
||||
{items.map((item, index) => (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={appendRedirectParam('/onboarding', '/dashboard', {
|
||||
step: String(stepMap[item.id] || index + 1),
|
||||
})}
|
||||
className={`rounded-full border px-3 py-2 text-center text-xs font-medium transition-colors ${
|
||||
item.done
|
||||
? 'border-primary-600 bg-primary-600 text-white'
|
||||
: 'border-slate-200 bg-slate-50 text-slate-500 hover:border-primary-200 hover:bg-primary-50 hover:text-primary-700'
|
||||
}`}
|
||||
>
|
||||
{index + 1}. {item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-11 rounded-2xl border-slate-300 px-5 text-sm font-semibold text-slate-700"
|
||||
onClick={() => {
|
||||
localStorage.setItem(ONBOARDING_CHECKLIST_DISMISS_KEY, '1');
|
||||
setDismissed(true);
|
||||
}}
|
||||
>
|
||||
{allDone ? 'Hide checklist' : 'Dismiss'}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card, CardContent } from '@/components/ui/Card';
|
||||
import { appendRedirectParam } from '@/lib/auth-flow';
|
||||
import {
|
||||
getChecklistItems,
|
||||
ONBOARDING_CHECKLIST_DISMISS_KEY,
|
||||
ONBOARDING_DOWNLOAD_COMPLETE_EVENT,
|
||||
ONBOARDING_DOWNLOAD_COMPLETE_KEY,
|
||||
} from '@/lib/revops';
|
||||
|
||||
type ChecklistState = {
|
||||
id?: string;
|
||||
signupSourceSelfReported?: string | null;
|
||||
primaryUseCase?: string | null;
|
||||
firstQrCreatedAt?: string | null;
|
||||
firstDynamicQrAt?: string | null;
|
||||
firstScanAt?: string | null;
|
||||
activationAt?: string | null;
|
||||
onboardingCompletedAt?: string | null;
|
||||
};
|
||||
|
||||
type OnboardingChecklistProps = {
|
||||
state: ChecklistState | null;
|
||||
};
|
||||
|
||||
function buildChecklistItems(state: ChecklistState, downloadDone: boolean) {
|
||||
return getChecklistItems(state).map((item) =>
|
||||
item.id === 'download'
|
||||
? {
|
||||
...item,
|
||||
done: downloadDone || Boolean(state.firstScanAt),
|
||||
}
|
||||
: item
|
||||
);
|
||||
}
|
||||
|
||||
export function OnboardingChecklist({ state }: OnboardingChecklistProps) {
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
const [downloadDone, setDownloadDone] = useState(false);
|
||||
const [storageReady, setStorageReady] = useState(false);
|
||||
|
||||
const stepMap: Record<string, number> = {
|
||||
source: 1,
|
||||
'use-case': 2,
|
||||
'first-qr': 7,
|
||||
'first-dynamic': 7,
|
||||
download: 8,
|
||||
scan: 8,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setDismissed(localStorage.getItem(ONBOARDING_CHECKLIST_DISMISS_KEY) === '1');
|
||||
setDownloadDone(localStorage.getItem(ONBOARDING_DOWNLOAD_COMPLETE_KEY) === '1');
|
||||
setStorageReady(true);
|
||||
|
||||
const syncDownloadState = () => {
|
||||
setDownloadDone(localStorage.getItem(ONBOARDING_DOWNLOAD_COMPLETE_KEY) === '1');
|
||||
};
|
||||
|
||||
window.addEventListener(ONBOARDING_DOWNLOAD_COMPLETE_EVENT, syncDownloadState);
|
||||
window.addEventListener('storage', syncDownloadState);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(ONBOARDING_DOWNLOAD_COMPLETE_EVENT, syncDownloadState);
|
||||
window.removeEventListener('storage', syncDownloadState);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const items = state ? buildChecklistItems(state, downloadDone) : [];
|
||||
const completed = items.filter((item) => item.done).length;
|
||||
const allDone = items.length > 0 && completed === items.length;
|
||||
|
||||
useEffect(() => {
|
||||
if (!storageReady || !state) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.onboardingCompletedAt || allDone) {
|
||||
localStorage.setItem(ONBOARDING_CHECKLIST_DISMISS_KEY, '1');
|
||||
setDismissed(true);
|
||||
}
|
||||
}, [allDone, state, storageReady]);
|
||||
|
||||
if (!state || !storageReady || dismissed || state.onboardingCompletedAt || allDone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const progress = Math.round((completed / items.length) * 100);
|
||||
|
||||
return (
|
||||
<Card className="overflow-hidden rounded-[28px] border border-slate-200 bg-white p-0 shadow-none">
|
||||
<CardContent className="space-y-5 p-5 sm:p-6">
|
||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div className="max-w-2xl">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">
|
||||
{allDone ? 'Onboarding complete' : 'Onboarding progress'}
|
||||
</p>
|
||||
<h3 className="mt-2 text-xl font-semibold tracking-tight text-slate-950">
|
||||
{allDone ? 'Your first setup is complete' : 'Finish the first-run checklist'}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-6 text-slate-600">
|
||||
{allDone
|
||||
? 'You created, downloaded, and tested your first code. You can keep this visible or dismiss it.'
|
||||
: 'Keep this minimal checklist visible until the first QR workflow is fully done.'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="rounded-full border border-primary-200 bg-primary-50 px-3 py-1 text-xs font-semibold text-primary-700">
|
||||
{completed}/{items.length}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Dismiss onboarding checklist"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-2xl border border-slate-200 bg-white text-slate-500 transition hover:border-slate-300 hover:text-slate-900"
|
||||
onClick={() => {
|
||||
localStorage.setItem(ONBOARDING_CHECKLIST_DISMISS_KEY, '1');
|
||||
setDismissed(true);
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="h-1.5 overflow-hidden rounded-full bg-slate-100">
|
||||
<div
|
||||
className="h-full rounded-full bg-primary-600 transition-all duration-300"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2 sm:grid-cols-6">
|
||||
{items.map((item, index) => (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={appendRedirectParam('/onboarding', '/dashboard', {
|
||||
step: String(stepMap[item.id] || index + 1),
|
||||
})}
|
||||
className={`rounded-full border px-3 py-2 text-center text-xs font-medium transition-colors ${
|
||||
item.done
|
||||
? 'border-primary-600 bg-primary-600 text-white'
|
||||
: 'border-slate-200 bg-slate-50 text-slate-500 hover:border-primary-200 hover:bg-primary-50 hover:text-primary-700'
|
||||
}`}
|
||||
>
|
||||
{index + 1}. {item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-11 rounded-2xl border-slate-300 px-5 text-sm font-semibold text-slate-700"
|
||||
onClick={() => {
|
||||
localStorage.setItem(ONBOARDING_CHECKLIST_DISMISS_KEY, '1');
|
||||
setDismissed(true);
|
||||
}}
|
||||
>
|
||||
{allDone ? 'Hide checklist' : 'Dismiss'}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import React from 'react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Check, X } from 'lucide-react';
|
||||
|
||||
interface ComparisonItem {
|
||||
label: string;
|
||||
value: boolean;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
interface AnswerFirstBlockProps {
|
||||
whatIsIt: string; // 2 sentences: "Was ist das?"
|
||||
whenToUse: string[]; // 3 Bulletpoints: "Wann brauchst du's?"
|
||||
comparison: {
|
||||
leftTitle: string;
|
||||
rightTitle: string;
|
||||
items: ComparisonItem[];
|
||||
};
|
||||
howTo: {
|
||||
steps: string[]; // 3 Steps: "So funktioniert's"
|
||||
};
|
||||
className?: string; // Add className prop
|
||||
}
|
||||
|
||||
import { Check, X, HelpCircle, ArrowRight, Play } from 'lucide-react';
|
||||
|
||||
interface ComparisonItem {
|
||||
label: string;
|
||||
value: boolean;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
interface AnswerFirstBlockProps {
|
||||
whatIsIt: string; // 2 sentences: "Was ist das?"
|
||||
whenToUse: string[]; // 3 Bulletpoints: "Wann brauchst du's?"
|
||||
comparison: {
|
||||
leftTitle: string;
|
||||
rightTitle: string;
|
||||
items: ComparisonItem[];
|
||||
};
|
||||
howTo: {
|
||||
steps: string[]; // 3 Steps: "So funktioniert's"
|
||||
};
|
||||
className?: string; // Add className prop
|
||||
}
|
||||
|
||||
export const AnswerFirstBlock: React.FC<AnswerFirstBlockProps> = ({
|
||||
whatIsIt,
|
||||
whenToUse,
|
||||
@@ -32,54 +32,62 @@ export const AnswerFirstBlock: React.FC<AnswerFirstBlockProps> = ({
|
||||
const leftValueFor = (item: ComparisonItem) => item.text ?? 'No';
|
||||
|
||||
return (
|
||||
<section className={`my-8 space-y-8 ${className || ''}`} aria-label="Quick answer">
|
||||
<div className="prose max-w-none">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">Quick Summary</h2>
|
||||
<p className="text-lg text-gray-700 leading-relaxed font-medium">
|
||||
<section className={`my-12 space-y-10 ${className || ''}`} aria-label="Quick answer">
|
||||
<div className="max-w-4xl space-y-4">
|
||||
<h2 className="text-2xl font-semibold text-slate-900 tracking-tight -tracking-[0.02em]">Quick Summary</h2>
|
||||
<p className="text-lg text-slate-600 leading-relaxed font-light">
|
||||
{whatIsIt}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<Card className="p-6 bg-blue-50 border-blue-100">
|
||||
<h3 className="font-semibold text-lg text-blue-900 mb-4">When to use this?</h3>
|
||||
<ul className="space-y-3">
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{/* When to use card */}
|
||||
<Card className="p-6 bg-white border-slate-200/60 shadow-sm rounded-xl flex flex-col h-full">
|
||||
<h3 className="font-semibold text-lg text-slate-900 mb-5 flex items-center gap-2">
|
||||
<HelpCircle className="w-5 h-5 text-slate-400 shrink-0" />
|
||||
When to use this?
|
||||
</h3>
|
||||
<ul className="space-y-4 flex-1">
|
||||
{whenToUse.map((item, idx) => (
|
||||
<li key={idx} className="flex items-start gap-3 text-blue-800">
|
||||
<span className="mt-1.5 w-1.5 h-1.5 rounded-full bg-blue-500 shrink-0" />
|
||||
{item}
|
||||
</li>
|
||||
<li key={idx} className="flex items-start gap-3.5 text-slate-600 text-sm leading-relaxed">
|
||||
<span className="mt-2 w-1.5 h-1.5 rounded-full bg-slate-350 shrink-0" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-white border-slate-200 shadow-sm flex flex-col">
|
||||
<h3 className="font-semibold text-lg text-slate-900 mb-6 font-sans">Comparison</h3>
|
||||
<div className="space-y-4 flex-1">
|
||||
{/* Comparison card (Grayed-out static column, highlighted dynamic column) */}
|
||||
<Card className="p-6 bg-white border-slate-200/60 shadow-sm rounded-xl flex flex-col h-full">
|
||||
<h3 className="font-semibold text-lg text-slate-900 mb-5 flex items-center gap-2">
|
||||
<ArrowRight className="w-5 h-5 text-slate-400 shrink-0 rotate-[-45deg]" />
|
||||
Comparison
|
||||
</h3>
|
||||
<div className="space-y-3.5 flex-1">
|
||||
{comparison.items.map((item, idx) => (
|
||||
<div key={idx} className="flex flex-col gap-2 rounded-xl border border-slate-100 bg-slate-50 p-4">
|
||||
<div className="font-semibold text-sm text-slate-900">
|
||||
<div key={idx} className="flex flex-col gap-2 rounded-lg border border-slate-100 bg-slate-50/50 p-3">
|
||||
<div className="font-bold text-[9px] text-slate-450 uppercase tracking-wider">
|
||||
{item.label}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm mt-1">
|
||||
{/* Left Side (e.g., Static Pages) */}
|
||||
<div>
|
||||
<div className="text-xs text-slate-500 mb-1">{comparison.leftTitle}</div>
|
||||
<div className="text-slate-600 font-medium">{leftValueFor(item)}</div>
|
||||
<div className="grid grid-cols-2 gap-4 text-xs mt-0.5 items-center">
|
||||
{/* Left Side (Static QR - Grayed out) */}
|
||||
<div className="opacity-45 text-slate-400">
|
||||
<div className="text-[9px] font-medium mb-0.5 uppercase tracking-wide">{comparison.leftTitle}</div>
|
||||
<div className="text-xs font-light truncate">{leftValueFor(item)}</div>
|
||||
</div>
|
||||
{/* Right Side (e.g., QR Master) */}
|
||||
<div>
|
||||
<div className="text-xs text-blue-600 mb-1 font-medium">{comparison.rightTitle}</div>
|
||||
<div className="flex items-center gap-1.5 font-semibold text-slate-900">
|
||||
{/* Right Side (Dynamic QR - Highlighted in color) */}
|
||||
<div className="bg-indigo-500/[0.04] border border-indigo-500/10 rounded-lg p-2 flex flex-col">
|
||||
<div className="text-[9px] text-indigo-500 font-bold mb-0.5 uppercase tracking-wide">{comparison.rightTitle}</div>
|
||||
<div className="flex items-center gap-1 text-xs font-bold text-slate-800">
|
||||
{item.value ? (
|
||||
<>
|
||||
<Check className="w-4 h-4 text-emerald-500" aria-hidden="true" />
|
||||
<span>Included</span>
|
||||
<Check className="w-3.5 h-3.5 text-emerald-600 shrink-0" aria-hidden="true" />
|
||||
<span className="text-indigo-650">Supported</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<X className="w-4 h-4 text-red-500" aria-hidden="true" />
|
||||
<span className="text-slate-500">Not Included</span>
|
||||
<X className="w-3.5 h-3.5 text-red-400 shrink-0" aria-hidden="true" />
|
||||
<span className="text-slate-500">No</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -90,17 +98,21 @@ export const AnswerFirstBlock: React.FC<AnswerFirstBlockProps> = ({
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-green-50 border-green-100">
|
||||
<h3 className="font-semibold text-lg text-green-900 mb-4">How it works</h3>
|
||||
<ol className="space-y-4">
|
||||
{/* How it works card (Spacious clean index numbers) */}
|
||||
<Card className="p-6 bg-white border-slate-200/60 shadow-sm rounded-xl flex flex-col h-full">
|
||||
<h3 className="font-semibold text-lg text-slate-900 mb-5 flex items-center gap-2">
|
||||
<Play className="w-5 h-5 text-slate-400 shrink-0" />
|
||||
How it works
|
||||
</h3>
|
||||
<ol className="space-y-6 flex-1">
|
||||
{howTo.steps.map((step, idx) => (
|
||||
<li key={idx} className="flex gap-3 text-green-800">
|
||||
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-green-200 text-green-700 flex items-center justify-center text-sm font-bold">
|
||||
{idx + 1}
|
||||
</span>
|
||||
<span>{step}</span>
|
||||
</li>
|
||||
))}
|
||||
<li key={idx} className="flex items-start gap-4">
|
||||
<span className="text-sm font-bold text-slate-400 select-none mt-0.5">
|
||||
0{idx + 1}
|
||||
</span>
|
||||
<span className="text-sm text-slate-600 leading-relaxed">{step}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -1,115 +1,115 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
|
||||
interface FAQProps {
|
||||
t: any;
|
||||
}
|
||||
|
||||
export const FAQ: React.FC<FAQProps> = ({ t }) => {
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
||||
|
||||
const defaultQuestions = [
|
||||
'account',
|
||||
'static_vs_dynamic',
|
||||
'forever',
|
||||
'file_type',
|
||||
'analytics',
|
||||
];
|
||||
|
||||
const questions = t?.faq?.questions
|
||||
? Array.isArray(t.faq.questions)
|
||||
? t.faq.questions
|
||||
: Object.keys(t.faq.questions).length > 5
|
||||
? Object.keys(t.faq.questions).slice(0, 12)
|
||||
: defaultQuestions
|
||||
: defaultQuestions;
|
||||
|
||||
return (
|
||||
<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">
|
||||
<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">
|
||||
{t.faq.title}
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="max-w-3xl mx-auto space-y-4">
|
||||
{questions.map((key: string, index: number) => (
|
||||
<motion.div
|
||||
key={key}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
>
|
||||
<Card
|
||||
className="cursor-pointer border-gray-200 hover:border-gray-300 transition-colors"
|
||||
onClick={() => setOpenIndex(openIndex === index ? null : index)}
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
{t.faq.questions[key]?.question ||
|
||||
t.faq.questions[key]?.question ||
|
||||
key}
|
||||
</h3>
|
||||
<svg
|
||||
className={`w-5 h-5 text-gray-500 transition-transform duration-300 ${openIndex === index ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{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 ||
|
||||
t.faq.questions[key]?.answer ||
|
||||
''}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-8">
|
||||
<a
|
||||
href="/faq"
|
||||
className="text-primary-600 hover:text-primary-700 font-medium"
|
||||
>
|
||||
View All Questions →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
|
||||
interface FAQProps {
|
||||
t: any;
|
||||
}
|
||||
|
||||
export const FAQ: React.FC<FAQProps> = ({ t }) => {
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
||||
|
||||
const defaultQuestions = [
|
||||
'account',
|
||||
'static_vs_dynamic',
|
||||
'forever',
|
||||
'file_type',
|
||||
'analytics',
|
||||
];
|
||||
|
||||
const questions = t?.faq?.questions
|
||||
? Array.isArray(t.faq.questions)
|
||||
? t.faq.questions
|
||||
: Object.keys(t.faq.questions).length > 5
|
||||
? Object.keys(t.faq.questions).slice(0, 12)
|
||||
: defaultQuestions
|
||||
: defaultQuestions;
|
||||
|
||||
return (
|
||||
<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">
|
||||
<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">
|
||||
{t.faq.title}
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="max-w-3xl mx-auto space-y-4">
|
||||
{questions.map((key: string, index: number) => (
|
||||
<motion.div
|
||||
key={key}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
>
|
||||
<Card
|
||||
className="cursor-pointer border-gray-200 hover:border-gray-300 transition-colors"
|
||||
onClick={() => setOpenIndex(openIndex === index ? null : index)}
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
{t.faq.questions[key]?.question ||
|
||||
t.faq.questions[key]?.question ||
|
||||
key}
|
||||
</h3>
|
||||
<svg
|
||||
className={`w-5 h-5 text-gray-500 transition-transform duration-300 ${openIndex === index ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{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 ||
|
||||
t.faq.questions[key]?.answer ||
|
||||
''}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-8">
|
||||
<a
|
||||
href="/faq"
|
||||
className="text-primary-600 hover:text-primary-700 font-medium"
|
||||
>
|
||||
View All Questions →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,125 +1,125 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { motion, Variants } from 'framer-motion';
|
||||
import {
|
||||
Link as LinkIcon,
|
||||
User,
|
||||
Mail,
|
||||
Calendar,
|
||||
Facebook,
|
||||
Instagram,
|
||||
Phone,
|
||||
MessageSquare,
|
||||
Type,
|
||||
Music,
|
||||
Twitter,
|
||||
MessageCircle,
|
||||
Wifi,
|
||||
Youtube,
|
||||
Bitcoin,
|
||||
MapPin,
|
||||
CreditCard,
|
||||
Video,
|
||||
Users,
|
||||
Barcode,
|
||||
Star
|
||||
} from 'lucide-react';
|
||||
|
||||
const TOOLS = [
|
||||
{ icon: LinkIcon, name: 'URL', description: 'Open any website', href: '/tools/url-qr-code', color: 'text-blue-500', bg: 'bg-blue-50' },
|
||||
{ icon: User, name: 'vCard', description: 'Share contact details', href: '/tools/vcard-qr-code', color: 'text-rose-500', bg: 'bg-rose-50' },
|
||||
{ icon: Type, name: 'Text', description: 'Display plain text', href: '/tools/text-qr-code', color: 'text-slate-600', bg: 'bg-slate-50' },
|
||||
{ icon: Mail, name: 'Email', description: 'Send an email', href: '/tools/email-qr-code', color: 'text-red-500', bg: 'bg-red-50' },
|
||||
{ icon: MessageSquare, name: 'SMS', description: 'Send a text message', href: '/tools/sms-qr-code', color: 'text-green-500', bg: 'bg-green-50' },
|
||||
{ icon: Wifi, name: 'WiFi', description: 'Connect to WiFi', href: '/tools/wifi-qr-code', color: 'text-indigo-500', bg: 'bg-indigo-50' },
|
||||
{ icon: Bitcoin, name: 'Crypto', description: 'Receive payments', href: '/tools/crypto-qr-code', color: 'text-orange-500', bg: 'bg-orange-50' },
|
||||
{ icon: Calendar, name: 'Event', description: 'Save calendar event', href: '/tools/event-qr-code', color: 'text-violet-500', bg: 'bg-violet-50' },
|
||||
{ icon: Facebook, name: 'Facebook', description: 'Open Facebook page', href: '/tools/facebook-qr-code', color: 'text-blue-600', bg: 'bg-blue-50' },
|
||||
{ icon: Instagram, name: 'Instagram', description: 'Open Instagram profile', href: '/tools/instagram-qr-code', color: 'text-pink-500', bg: 'bg-pink-50' },
|
||||
{ icon: Twitter, name: 'Twitter', description: 'Open Twitter profile', href: '/tools/twitter-qr-code', color: 'text-sky-500', bg: 'bg-sky-50' },
|
||||
{ icon: Youtube, name: 'YouTube', description: 'Open YouTube video', href: '/tools/youtube-qr-code', color: 'text-red-600', bg: 'bg-red-50' },
|
||||
{ icon: MessageCircle, name: 'WhatsApp', description: 'Send WhatsApp message', href: '/tools/whatsapp-qr-code', color: 'text-green-600', bg: 'bg-green-50' },
|
||||
{ icon: Music, name: 'TikTok', description: 'Open TikTok profile', href: '/tools/tiktok-qr-code', color: 'text-slate-900', bg: 'bg-slate-100' },
|
||||
{ icon: MapPin, name: 'Location', description: 'Share GPS coordinates', href: '/tools/location-qr-code', color: 'text-emerald-500', bg: 'bg-emerald-50' },
|
||||
{ icon: Phone, name: 'Call', description: 'Start a phone call', href: '/tools/phone-qr-code', color: 'text-teal-500', bg: 'bg-teal-50' },
|
||||
{ icon: Barcode, name: 'Barcode', description: 'Free online barcode generator', href: '/tools/barcode-generator', color: 'text-amber-600', bg: 'bg-amber-50' },
|
||||
];
|
||||
|
||||
const containerVariants: Variants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: { staggerChildren: 0.05 }
|
||||
}
|
||||
};
|
||||
|
||||
const itemVariants: Variants = {
|
||||
hidden: { opacity: 0, y: 16 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { duration: 0.4 }
|
||||
}
|
||||
};
|
||||
|
||||
export function FreeToolsGrid() {
|
||||
return (
|
||||
<section id="tools" className="py-24 bg-slate-50/50 border-t border-slate-100">
|
||||
<style dangerouslySetInnerHTML={{__html:`
|
||||
.ftg-card { transition: transform 0.22s ease-out, box-shadow 0.22s ease-out, border-color 0.22s ease-out; }
|
||||
.ftg-card:hover { transform: translateY(-8px); box-shadow: rgba(83,58,253,0.2) 0px 20px 40px -12px, rgba(0,0,0,0.08) 0px 8px 16px -8px; border-color: #533afd; }
|
||||
.ftg-icon { transition: transform 0.25s ease-out; }
|
||||
.ftg-card:hover .ftg-icon { transform: rotate(12deg) scale(1.1); }
|
||||
@media (prefers-reduced-motion: reduce) { .ftg-card, .ftg-icon { transition: none !important; } }
|
||||
`}}/>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<div className="flex flex-col md:flex-row items-center justify-center gap-3 mb-4">
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-slate-900">More Free QR Code Tools</h2>
|
||||
<div className="bg-gradient-to-r from-emerald-500 to-green-500 text-white px-3 py-1 rounded-full text-xs md:text-sm font-semibold shadow-lg shadow-emerald-500/20 flex items-center gap-2">
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
|
||||
</span>
|
||||
Free Forever
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-lg text-slate-600 max-w-2xl mx-auto">
|
||||
Create specialized QR codes for every need. Completely free and no signup required.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
className="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6"
|
||||
>
|
||||
{TOOLS.map((tool) => (
|
||||
<motion.div key={tool.name} variants={itemVariants}>
|
||||
<Link
|
||||
href={tool.href}
|
||||
className="ftg-card group flex flex-col items-center p-5 md:p-6 rounded-2xl border border-[#e5edf5] bg-white"
|
||||
>
|
||||
<div className={`ftg-icon w-12 h-12 md:w-14 md:h-14 rounded-xl ${tool.bg} flex items-center justify-center mb-3 md:mb-4`}>
|
||||
<tool.icon className={`w-6 h-6 md:w-7 md:h-7 ${tool.color}`} aria-hidden="true" />
|
||||
</div>
|
||||
<h3 className="text-base md:text-lg font-semibold text-[#061b31] mb-0.5">{tool.name}</h3>
|
||||
<p className="text-xs md:text-sm text-[#64748d] text-center">{tool.description}</p>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { motion, Variants } from 'framer-motion';
|
||||
import {
|
||||
Link as LinkIcon,
|
||||
User,
|
||||
Mail,
|
||||
Calendar,
|
||||
Facebook,
|
||||
Instagram,
|
||||
Phone,
|
||||
MessageSquare,
|
||||
Type,
|
||||
Music,
|
||||
Twitter,
|
||||
MessageCircle,
|
||||
Wifi,
|
||||
Youtube,
|
||||
Bitcoin,
|
||||
MapPin,
|
||||
CreditCard,
|
||||
Video,
|
||||
Users,
|
||||
Barcode,
|
||||
Star
|
||||
} from 'lucide-react';
|
||||
|
||||
const TOOLS = [
|
||||
{ icon: LinkIcon, name: 'URL', description: 'Open any website', href: '/tools/url-qr-code', color: 'text-blue-500', bg: 'bg-blue-50' },
|
||||
{ icon: User, name: 'vCard', description: 'Share contact details', href: '/tools/vcard-qr-code', color: 'text-rose-500', bg: 'bg-rose-50' },
|
||||
{ icon: Type, name: 'Text', description: 'Display plain text', href: '/tools/text-qr-code', color: 'text-slate-600', bg: 'bg-slate-50' },
|
||||
{ icon: Mail, name: 'Email', description: 'Send an email', href: '/tools/email-qr-code', color: 'text-red-500', bg: 'bg-red-50' },
|
||||
{ icon: MessageSquare, name: 'SMS', description: 'Send a text message', href: '/tools/sms-qr-code', color: 'text-green-500', bg: 'bg-green-50' },
|
||||
{ icon: Wifi, name: 'WiFi', description: 'Connect to WiFi', href: '/tools/wifi-qr-code', color: 'text-indigo-500', bg: 'bg-indigo-50' },
|
||||
{ icon: Bitcoin, name: 'Crypto', description: 'Receive payments', href: '/tools/crypto-qr-code', color: 'text-orange-500', bg: 'bg-orange-50' },
|
||||
{ icon: Calendar, name: 'Event', description: 'Save calendar event', href: '/tools/event-qr-code', color: 'text-violet-500', bg: 'bg-violet-50' },
|
||||
{ icon: Facebook, name: 'Facebook', description: 'Open Facebook page', href: '/tools/facebook-qr-code', color: 'text-blue-600', bg: 'bg-blue-50' },
|
||||
{ icon: Instagram, name: 'Instagram', description: 'Open Instagram profile', href: '/tools/instagram-qr-code', color: 'text-pink-500', bg: 'bg-pink-50' },
|
||||
{ icon: Twitter, name: 'Twitter', description: 'Open Twitter profile', href: '/tools/twitter-qr-code', color: 'text-sky-500', bg: 'bg-sky-50' },
|
||||
{ icon: Youtube, name: 'YouTube', description: 'Open YouTube video', href: '/tools/youtube-qr-code', color: 'text-red-600', bg: 'bg-red-50' },
|
||||
{ icon: MessageCircle, name: 'WhatsApp', description: 'Send WhatsApp message', href: '/tools/whatsapp-qr-code', color: 'text-green-600', bg: 'bg-green-50' },
|
||||
{ icon: Music, name: 'TikTok', description: 'Open TikTok profile', href: '/tools/tiktok-qr-code', color: 'text-slate-900', bg: 'bg-slate-100' },
|
||||
{ icon: MapPin, name: 'Location', description: 'Share GPS coordinates', href: '/tools/location-qr-code', color: 'text-emerald-500', bg: 'bg-emerald-50' },
|
||||
{ icon: Phone, name: 'Call', description: 'Start a phone call', href: '/tools/phone-qr-code', color: 'text-teal-500', bg: 'bg-teal-50' },
|
||||
{ icon: Barcode, name: 'Barcode', description: 'Free online barcode generator', href: '/tools/barcode-generator', color: 'text-amber-600', bg: 'bg-amber-50' },
|
||||
];
|
||||
|
||||
const containerVariants: Variants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: { staggerChildren: 0.05 }
|
||||
}
|
||||
};
|
||||
|
||||
const itemVariants: Variants = {
|
||||
hidden: { opacity: 0, y: 16 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { duration: 0.4 }
|
||||
}
|
||||
};
|
||||
|
||||
export function FreeToolsGrid() {
|
||||
return (
|
||||
<section id="tools" className="py-24 bg-slate-50/50 border-t border-slate-100">
|
||||
<style dangerouslySetInnerHTML={{__html:`
|
||||
.ftg-card { transition: transform 0.22s ease-out, box-shadow 0.22s ease-out, border-color 0.22s ease-out; }
|
||||
.ftg-card:hover { transform: translateY(-8px); box-shadow: rgba(83,58,253,0.2) 0px 20px 40px -12px, rgba(0,0,0,0.08) 0px 8px 16px -8px; border-color: #533afd; }
|
||||
.ftg-icon { transition: transform 0.25s ease-out; }
|
||||
.ftg-card:hover .ftg-icon { transform: rotate(12deg) scale(1.1); }
|
||||
@media (prefers-reduced-motion: reduce) { .ftg-card, .ftg-icon { transition: none !important; } }
|
||||
`}}/>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<div className="flex flex-col md:flex-row items-center justify-center gap-3 mb-4">
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-slate-900">More Free QR Code Tools</h2>
|
||||
<div className="bg-gradient-to-r from-emerald-500 to-green-500 text-white px-3 py-1 rounded-full text-xs md:text-sm font-semibold shadow-lg shadow-emerald-500/20 flex items-center gap-2">
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
|
||||
</span>
|
||||
Free Forever
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-lg text-slate-600 max-w-2xl mx-auto">
|
||||
Create specialized QR codes for every need. Completely free and no signup required.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
className="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6"
|
||||
>
|
||||
{TOOLS.map((tool) => (
|
||||
<motion.div key={tool.name} variants={itemVariants}>
|
||||
<Link
|
||||
href={tool.href}
|
||||
className="ftg-card group flex flex-col items-center p-5 md:p-6 rounded-2xl border border-[#e5edf5] bg-white"
|
||||
>
|
||||
<div className={`ftg-icon w-12 h-12 md:w-14 md:h-14 rounded-xl ${tool.bg} flex items-center justify-center mb-3 md:mb-4`}>
|
||||
<tool.icon className={`w-6 h-6 md:w-7 md:h-7 ${tool.color}`} aria-hidden="true" />
|
||||
</div>
|
||||
<h3 className="text-base md:text-lg font-semibold text-[#061b31] mb-0.5">{tool.name}</h3>
|
||||
<p className="text-xs md:text-sm text-[#64748d] text-center">{tool.description}</p>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,227 +1,227 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Globe, User, MapPin, Phone, FileText, Ticket, Smartphone, Star } from 'lucide-react';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
const PRODUCT_HUNT_URL =
|
||||
'https://www.producthunt.com/products/qr-master-2?launch=qr-master-3';
|
||||
const PRODUCT_HUNT_BADGE_URL =
|
||||
'/producthunt-featured.svg';
|
||||
|
||||
const FlippingCard = ({ front, back, delay }: { front: any, back: any, delay: number }) => {
|
||||
const [isFlipped, setIsFlipped] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const initialTimeout = setTimeout(() => {
|
||||
setIsFlipped(true);
|
||||
const interval = setInterval(() => {
|
||||
setIsFlipped(prev => !prev);
|
||||
}, 8000);
|
||||
return () => clearInterval(interval);
|
||||
}, delay * 1000);
|
||||
return () => clearTimeout(initialTimeout);
|
||||
}, [delay]);
|
||||
|
||||
return (
|
||||
<div className="relative h-32 w-full perspective-[1000px] group cursor-pointer">
|
||||
<motion.div
|
||||
animate={{ rotateY: isFlipped ? 180 : 0 }}
|
||||
transition={{ duration: 0.6, type: "spring", stiffness: 260, damping: 20 }}
|
||||
className="relative w-full h-full preserve-3d"
|
||||
style={{ transformStyle: 'preserve-3d' }}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 backface-hidden"
|
||||
style={{ backfaceVisibility: 'hidden', WebkitBackfaceVisibility: 'hidden' }}
|
||||
>
|
||||
<Card className="w-full h-full backdrop-blur-xl bg-white/70 border-white/50 shadow-xl shadow-gray-200/50 p-4 flex flex-col items-center justify-center hover:scale-105 transition-all duration-300">
|
||||
<div className={`w-10 h-10 mb-3 rounded-xl ${front.color} flex items-center justify-center`}>
|
||||
<front.icon className="w-5 h-5" />
|
||||
</div>
|
||||
<p className="font-semibold text-gray-800 text-sm">{front.title}</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute inset-0 backface-hidden"
|
||||
style={{
|
||||
backfaceVisibility: 'hidden',
|
||||
WebkitBackfaceVisibility: 'hidden',
|
||||
transform: 'rotateY(180deg)'
|
||||
}}
|
||||
>
|
||||
<Card className="w-full h-full backdrop-blur-xl bg-white/80 border-white/60 shadow-xl shadow-blue-200/50 p-4 flex flex-col items-center justify-center hover:scale-105 transition-all duration-300">
|
||||
<div className={`w-10 h-10 mb-3 rounded-xl ${back.color} flex items-center justify-center`}>
|
||||
<back.icon className="w-5 h-5" />
|
||||
</div>
|
||||
<p className="font-semibold text-gray-900 text-sm">{back.title}</p>
|
||||
</Card>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface HeroProps {
|
||||
t: any;
|
||||
headingAs?: 'h1' | 'div';
|
||||
}
|
||||
|
||||
export const Hero: React.FC<HeroProps> = ({ t, headingAs = 'h1' }) => {
|
||||
const HeadingTag = headingAs;
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 pt-12 pb-20">
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<style dangerouslySetInnerHTML={{__html:`
|
||||
@keyframes dotFloat{0%{transform:translateY(0) translateX(0);opacity:0}20%{opacity:1}80%{opacity:1}100%{transform:translateY(calc(var(--dy)*1px)) translateX(calc(var(--dx)*1px));opacity:0}}
|
||||
.hero-dot{position:absolute;border-radius:50%;animation:dotFloat linear infinite;}
|
||||
@media(prefers-reduced-motion:reduce){.hero-dot{animation:none}}
|
||||
`}}/>
|
||||
{([
|
||||
{size:4,x:15,y:80,dx:-30,dy:-120,dur:12,delay:0,color:'rgba(96,165,250,0.5)'},
|
||||
{size:3,x:35,y:90,dx:20,dy:-100,dur:15,delay:-3,color:'rgba(167,139,250,0.4)'},
|
||||
{size:5,x:55,y:85,dx:-10,dy:-130,dur:10,delay:-6,color:'rgba(96,165,250,0.3)'},
|
||||
{size:2,x:70,y:95,dx:30,dy:-110,dur:18,delay:-2,color:'rgba(192,132,252,0.5)'},
|
||||
{size:4,x:85,y:80,dx:-20,dy:-90,dur:14,delay:-8,color:'rgba(147,197,253,0.4)'},
|
||||
{size:3,x:25,y:70,dx:15,dy:-140,dur:11,delay:-5,color:'rgba(216,180,254,0.4)'},
|
||||
{size:6,x:60,y:75,dx:-25,dy:-100,dur:16,delay:-1,color:'rgba(96,165,250,0.25)'},
|
||||
{size:2,x:45,y:88,dx:10,dy:-120,dur:13,delay:-9,color:'rgba(167,139,250,0.5)'},
|
||||
{size:5,x:80,y:60,dx:-15,dy:-80,dur:20,delay:-4,color:'rgba(99,102,241,0.3)'},
|
||||
{size:3,x:10,y:50,dx:25,dy:-110,dur:17,delay:-7,color:'rgba(192,132,252,0.35)'},
|
||||
] as {size:number,x:number,y:number,dx:number,dy:number,dur:number,delay:number,color:string}[]).map((p,i)=>(
|
||||
<div key={i} className="hero-dot" style={{
|
||||
width:p.size,height:p.size,
|
||||
left:`${p.x}%`,top:`${p.y}%`,
|
||||
background:p.color,
|
||||
'--dx':p.dx,'--dy':p.dy,
|
||||
animationDuration:`${p.dur}s`,
|
||||
animationDelay:`${p.delay}s`,
|
||||
} as React.CSSProperties}/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl relative z-10">
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
{/* Left Content */}
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="w-6 h-0.5 bg-[#533afd] shrink-0" />
|
||||
<span className="text-[10px] font-semibold tracking-[0.1em] uppercase text-[#533afd]">{t.hero.badge}</span>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="space-y-4"
|
||||
>
|
||||
<HeadingTag
|
||||
className="font-extrabold text-[#061b31] leading-[0.92]"
|
||||
style={{ fontSize: 'clamp(2.75rem, 6vw, 5rem)' }}
|
||||
>
|
||||
{t.hero.title}
|
||||
</HeadingTag>
|
||||
|
||||
<p className="text-[0.9375rem] text-[#64748d] leading-[1.55] max-w-[44ch]">
|
||||
{t.hero.subtitle}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-1.5 pt-1">
|
||||
{t.hero.features.map((feature: string, index: number) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.2 + (index * 0.1) }}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<div className="w-1 h-1 rounded-full bg-[#533afd] shrink-0" />
|
||||
<span className="text-[13px] text-[#273951] font-medium">{feature}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="flex gap-4 items-center flex-wrap pt-1"
|
||||
>
|
||||
<Link href="/signup">
|
||||
<Button size="lg" style={{ padding: '14px 36px', fontSize: '1.0625rem', boxShadow: 'rgba(83,58,253,0.3) 0px 10px 28px -8px' }}>
|
||||
{t.hero.cta_primary}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link
|
||||
href="/#pricing"
|
||||
style={{ fontSize: '0.875rem', fontWeight: 500, color: '#533afd', textDecoration: 'underline', textUnderlineOffset: '3px' }}
|
||||
>
|
||||
{t.hero.cta_secondary}
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
<motion.a
|
||||
href={PRODUCT_HUNT_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="View QR Master on Product Hunt"
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.65 }}
|
||||
className="inline-flex rounded-[10px] transition-transform duration-200 hover:-translate-y-0.5 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#533afd]"
|
||||
>
|
||||
<img
|
||||
src={PRODUCT_HUNT_BADGE_URL}
|
||||
alt="QR Master - Dynamic QR codes with analytics and editable links | Product Hunt"
|
||||
width="250"
|
||||
height="54"
|
||||
className="h-[54px] w-[250px]"
|
||||
/>
|
||||
</motion.a>
|
||||
</div>
|
||||
|
||||
{/* Right Preview Widget */}
|
||||
<div className="relative">
|
||||
<div className="relative perspective-[1000px]">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{[
|
||||
{
|
||||
front: { title: 'URL/Website', color: 'bg-blue-500/10 text-blue-600', icon: Globe },
|
||||
back: { title: 'PDF / Menu', color: 'bg-orange-500/10 text-orange-600', icon: FileText },
|
||||
delay: 3
|
||||
},
|
||||
{
|
||||
front: { title: 'Contact Card', color: 'bg-purple-500/10 text-purple-600', icon: User },
|
||||
back: { title: 'Coupon / Deals', color: 'bg-red-500/10 text-red-600', icon: Ticket },
|
||||
delay: 5
|
||||
},
|
||||
{
|
||||
front: { title: 'Location', color: 'bg-green-500/10 text-green-600', icon: MapPin },
|
||||
back: { title: 'App Store', color: 'bg-sky-500/10 text-sky-600', icon: Smartphone },
|
||||
delay: 7
|
||||
},
|
||||
{
|
||||
front: { title: 'Phone Number', color: 'bg-pink-500/10 text-pink-600', icon: Phone },
|
||||
back: { title: 'Feedback', color: 'bg-yellow-500/10 text-yellow-600', icon: Star },
|
||||
delay: 9
|
||||
},
|
||||
].map((card, index) => (
|
||||
<FlippingCard key={index} {...card} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-0 left-0 w-full h-32 bg-gradient-to-b from-transparent to-gray-50 pointer-events-none" />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Globe, User, MapPin, Phone, FileText, Ticket, Smartphone, Star } from 'lucide-react';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
const PRODUCT_HUNT_URL =
|
||||
'https://www.producthunt.com/products/qr-master-2?launch=qr-master-3';
|
||||
const PRODUCT_HUNT_BADGE_URL =
|
||||
'/producthunt-featured.svg';
|
||||
|
||||
const FlippingCard = ({ front, back, delay }: { front: any, back: any, delay: number }) => {
|
||||
const [isFlipped, setIsFlipped] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const initialTimeout = setTimeout(() => {
|
||||
setIsFlipped(true);
|
||||
const interval = setInterval(() => {
|
||||
setIsFlipped(prev => !prev);
|
||||
}, 8000);
|
||||
return () => clearInterval(interval);
|
||||
}, delay * 1000);
|
||||
return () => clearTimeout(initialTimeout);
|
||||
}, [delay]);
|
||||
|
||||
return (
|
||||
<div className="relative h-32 w-full perspective-[1000px] group cursor-pointer">
|
||||
<motion.div
|
||||
animate={{ rotateY: isFlipped ? 180 : 0 }}
|
||||
transition={{ duration: 0.6, type: "spring", stiffness: 260, damping: 20 }}
|
||||
className="relative w-full h-full preserve-3d"
|
||||
style={{ transformStyle: 'preserve-3d' }}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 backface-hidden"
|
||||
style={{ backfaceVisibility: 'hidden', WebkitBackfaceVisibility: 'hidden' }}
|
||||
>
|
||||
<Card className="w-full h-full backdrop-blur-xl bg-white/70 border-white/50 shadow-xl shadow-gray-200/50 p-4 flex flex-col items-center justify-center hover:scale-105 transition-all duration-300">
|
||||
<div className={`w-10 h-10 mb-3 rounded-xl ${front.color} flex items-center justify-center`}>
|
||||
<front.icon className="w-5 h-5" />
|
||||
</div>
|
||||
<p className="font-semibold text-gray-800 text-sm">{front.title}</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute inset-0 backface-hidden"
|
||||
style={{
|
||||
backfaceVisibility: 'hidden',
|
||||
WebkitBackfaceVisibility: 'hidden',
|
||||
transform: 'rotateY(180deg)'
|
||||
}}
|
||||
>
|
||||
<Card className="w-full h-full backdrop-blur-xl bg-white/80 border-white/60 shadow-xl shadow-blue-200/50 p-4 flex flex-col items-center justify-center hover:scale-105 transition-all duration-300">
|
||||
<div className={`w-10 h-10 mb-3 rounded-xl ${back.color} flex items-center justify-center`}>
|
||||
<back.icon className="w-5 h-5" />
|
||||
</div>
|
||||
<p className="font-semibold text-gray-900 text-sm">{back.title}</p>
|
||||
</Card>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface HeroProps {
|
||||
t: any;
|
||||
headingAs?: 'h1' | 'div';
|
||||
}
|
||||
|
||||
export const Hero: React.FC<HeroProps> = ({ t, headingAs = 'h1' }) => {
|
||||
const HeadingTag = headingAs;
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 pt-12 pb-20">
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<style dangerouslySetInnerHTML={{__html:`
|
||||
@keyframes dotFloat{0%{transform:translateY(0) translateX(0);opacity:0}20%{opacity:1}80%{opacity:1}100%{transform:translateY(calc(var(--dy)*1px)) translateX(calc(var(--dx)*1px));opacity:0}}
|
||||
.hero-dot{position:absolute;border-radius:50%;animation:dotFloat linear infinite;}
|
||||
@media(prefers-reduced-motion:reduce){.hero-dot{animation:none}}
|
||||
`}}/>
|
||||
{([
|
||||
{size:4,x:15,y:80,dx:-30,dy:-120,dur:12,delay:0,color:'rgba(96,165,250,0.5)'},
|
||||
{size:3,x:35,y:90,dx:20,dy:-100,dur:15,delay:-3,color:'rgba(167,139,250,0.4)'},
|
||||
{size:5,x:55,y:85,dx:-10,dy:-130,dur:10,delay:-6,color:'rgba(96,165,250,0.3)'},
|
||||
{size:2,x:70,y:95,dx:30,dy:-110,dur:18,delay:-2,color:'rgba(192,132,252,0.5)'},
|
||||
{size:4,x:85,y:80,dx:-20,dy:-90,dur:14,delay:-8,color:'rgba(147,197,253,0.4)'},
|
||||
{size:3,x:25,y:70,dx:15,dy:-140,dur:11,delay:-5,color:'rgba(216,180,254,0.4)'},
|
||||
{size:6,x:60,y:75,dx:-25,dy:-100,dur:16,delay:-1,color:'rgba(96,165,250,0.25)'},
|
||||
{size:2,x:45,y:88,dx:10,dy:-120,dur:13,delay:-9,color:'rgba(167,139,250,0.5)'},
|
||||
{size:5,x:80,y:60,dx:-15,dy:-80,dur:20,delay:-4,color:'rgba(99,102,241,0.3)'},
|
||||
{size:3,x:10,y:50,dx:25,dy:-110,dur:17,delay:-7,color:'rgba(192,132,252,0.35)'},
|
||||
] as {size:number,x:number,y:number,dx:number,dy:number,dur:number,delay:number,color:string}[]).map((p,i)=>(
|
||||
<div key={i} className="hero-dot" style={{
|
||||
width:p.size,height:p.size,
|
||||
left:`${p.x}%`,top:`${p.y}%`,
|
||||
background:p.color,
|
||||
'--dx':p.dx,'--dy':p.dy,
|
||||
animationDuration:`${p.dur}s`,
|
||||
animationDelay:`${p.delay}s`,
|
||||
} as React.CSSProperties}/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl relative z-10">
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
{/* Left Content */}
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="w-6 h-0.5 bg-[#533afd] shrink-0" />
|
||||
<span className="text-[10px] font-semibold tracking-[0.1em] uppercase text-[#533afd]">{t.hero.badge}</span>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="space-y-4"
|
||||
>
|
||||
<HeadingTag
|
||||
className="font-extrabold text-[#061b31] leading-[0.92]"
|
||||
style={{ fontSize: 'clamp(2.75rem, 6vw, 5rem)' }}
|
||||
>
|
||||
{t.hero.title}
|
||||
</HeadingTag>
|
||||
|
||||
<p className="text-[0.9375rem] text-[#64748d] leading-[1.55] max-w-[44ch]">
|
||||
{t.hero.subtitle}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-1.5 pt-1">
|
||||
{t.hero.features.map((feature: string, index: number) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.2 + (index * 0.1) }}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<div className="w-1 h-1 rounded-full bg-[#533afd] shrink-0" />
|
||||
<span className="text-[13px] text-[#273951] font-medium">{feature}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="flex gap-4 items-center flex-wrap pt-1"
|
||||
>
|
||||
<Link href="/signup">
|
||||
<Button size="lg" style={{ padding: '14px 36px', fontSize: '1.0625rem', boxShadow: 'rgba(83,58,253,0.3) 0px 10px 28px -8px' }}>
|
||||
{t.hero.cta_primary}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link
|
||||
href="/#pricing"
|
||||
style={{ fontSize: '0.875rem', fontWeight: 500, color: '#533afd', textDecoration: 'underline', textUnderlineOffset: '3px' }}
|
||||
>
|
||||
{t.hero.cta_secondary}
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
<motion.a
|
||||
href={PRODUCT_HUNT_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="View QR Master on Product Hunt"
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.65 }}
|
||||
className="inline-flex rounded-[10px] transition-transform duration-200 hover:-translate-y-0.5 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#533afd]"
|
||||
>
|
||||
<img
|
||||
src={PRODUCT_HUNT_BADGE_URL}
|
||||
alt="QR Master - Dynamic QR codes with analytics and editable links | Product Hunt"
|
||||
width="250"
|
||||
height="54"
|
||||
className="h-[54px] w-[250px]"
|
||||
/>
|
||||
</motion.a>
|
||||
</div>
|
||||
|
||||
{/* Right Preview Widget */}
|
||||
<div className="relative">
|
||||
<div className="relative perspective-[1000px]">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{[
|
||||
{
|
||||
front: { title: 'URL/Website', color: 'bg-blue-500/10 text-blue-600', icon: Globe },
|
||||
back: { title: 'PDF / Menu', color: 'bg-orange-500/10 text-orange-600', icon: FileText },
|
||||
delay: 3
|
||||
},
|
||||
{
|
||||
front: { title: 'Contact Card', color: 'bg-purple-500/10 text-purple-600', icon: User },
|
||||
back: { title: 'Coupon / Deals', color: 'bg-red-500/10 text-red-600', icon: Ticket },
|
||||
delay: 5
|
||||
},
|
||||
{
|
||||
front: { title: 'Location', color: 'bg-green-500/10 text-green-600', icon: MapPin },
|
||||
back: { title: 'App Store', color: 'bg-sky-500/10 text-sky-600', icon: Smartphone },
|
||||
delay: 7
|
||||
},
|
||||
{
|
||||
front: { title: 'Phone Number', color: 'bg-pink-500/10 text-pink-600', icon: Phone },
|
||||
back: { title: 'Feedback', color: 'bg-yellow-500/10 text-yellow-600', icon: Star },
|
||||
delay: 9
|
||||
},
|
||||
].map((card, index) => (
|
||||
<FlippingCard key={index} {...card} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-0 left-0 w-full h-32 bg-gradient-to-b from-transparent to-gray-50 pointer-events-none" />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,476 +1,476 @@
|
||||
import type { FAQItem } from "@/lib/types";
|
||||
import type { Metadata } from "next";
|
||||
import Image from "next/image";
|
||||
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ArrowRight,
|
||||
CheckCircle2,
|
||||
Building2,
|
||||
Settings2,
|
||||
Smartphone,
|
||||
BarChart3,
|
||||
Link2,
|
||||
} from "lucide-react";
|
||||
|
||||
import Breadcrumbs, { BreadcrumbItem } from "@/components/Breadcrumbs";
|
||||
import SeoJsonLd from "@/components/SeoJsonLd";
|
||||
import { FAQSection } from "@/components/aeo/FAQSection";
|
||||
import {
|
||||
MarketingPageTracker,
|
||||
TrackedCtaLink,
|
||||
} from "@/components/marketing/MarketingAnalytics";
|
||||
import { AnswerFirstBlock } from "@/components/marketing/AnswerFirstBlock";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { breadcrumbSchema, faqPageSchema } from "@/lib/schema";
|
||||
|
||||
type LinkCard = {
|
||||
href: string;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
type IndustryPageTemplateProps = {
|
||||
title: string;
|
||||
description: string;
|
||||
eyebrow: string;
|
||||
intro: string;
|
||||
pageType: "commercial" | "use_case";
|
||||
cluster: string;
|
||||
useCase?: string;
|
||||
breadcrumbs: BreadcrumbItem[];
|
||||
answer: string;
|
||||
whenToUse: string[];
|
||||
comparisonItems: {
|
||||
label: string;
|
||||
value: boolean;
|
||||
text?: string;
|
||||
}[];
|
||||
howToSteps: string[];
|
||||
primaryCta: {
|
||||
href: string;
|
||||
label: string;
|
||||
};
|
||||
secondaryCta: {
|
||||
href: string;
|
||||
label: string;
|
||||
};
|
||||
workflowTitle: string;
|
||||
workflowIntro: string;
|
||||
workflowCards: {
|
||||
title: string;
|
||||
description: string;
|
||||
}[];
|
||||
checklistTitle: string;
|
||||
checklist: string[];
|
||||
supportLinks: LinkCard[];
|
||||
faq: FAQItem[];
|
||||
schemaData?: Record<string, unknown>[];
|
||||
heroImage?: string;
|
||||
heroImageAlt?: string;
|
||||
statistics?: { value: string; label: string }[];
|
||||
benefits?: { title: string; description: string }[];
|
||||
};
|
||||
|
||||
export function buildIndustryMetadata({
|
||||
title,
|
||||
fallbackTitle,
|
||||
description,
|
||||
canonicalPath,
|
||||
}: {
|
||||
title: string;
|
||||
fallbackTitle?: string;
|
||||
description: string;
|
||||
canonicalPath: string;
|
||||
}): Metadata {
|
||||
const canonical = `https://www.qrmaster.net${canonicalPath}`;
|
||||
const brandSuffix = " | QR Master";
|
||||
const maxTitleLength = 60;
|
||||
const maxBaseTitleLength = maxTitleLength - brandSuffix.length;
|
||||
const normalizedTitle = title.replace(/\s+\|\s+QR Master$/i, "").trim();
|
||||
const fallback = fallbackTitle?.replace(/\s+\|\s+QR Master$/i, "").trim();
|
||||
const candidates = [
|
||||
normalizedTitle,
|
||||
normalizedTitle.split(":")[0]?.trim(),
|
||||
fallback,
|
||||
].filter((candidate): candidate is string => Boolean(candidate));
|
||||
const seoTitle =
|
||||
candidates.find((candidate) => candidate.length <= maxBaseTitleLength) ??
|
||||
normalizedTitle.slice(0, maxBaseTitleLength).replace(/\s+\S*$/, "").trim();
|
||||
const fullTitle = `${seoTitle} | QR Master`;
|
||||
|
||||
return {
|
||||
title: {
|
||||
absolute: fullTitle,
|
||||
},
|
||||
description,
|
||||
alternates: {
|
||||
canonical,
|
||||
languages: {
|
||||
"x-default": canonical,
|
||||
en: canonical,
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: fullTitle,
|
||||
description,
|
||||
url: canonical,
|
||||
type: "website",
|
||||
images: ["/og-image.png"],
|
||||
},
|
||||
twitter: {
|
||||
title: fullTitle,
|
||||
description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Map a consistent icon for workflow cards depending on index
|
||||
const WORKFLOW_ICONS = [Smartphone, Settings2, BarChart3, Building2];
|
||||
|
||||
export function IndustryPageTemplate({
|
||||
title,
|
||||
description,
|
||||
eyebrow,
|
||||
intro,
|
||||
pageType,
|
||||
cluster,
|
||||
useCase,
|
||||
breadcrumbs,
|
||||
answer,
|
||||
whenToUse,
|
||||
comparisonItems,
|
||||
howToSteps,
|
||||
primaryCta,
|
||||
secondaryCta,
|
||||
workflowTitle,
|
||||
workflowIntro,
|
||||
workflowCards,
|
||||
checklistTitle,
|
||||
checklist,
|
||||
supportLinks,
|
||||
faq,
|
||||
schemaData = [],
|
||||
heroImage = "/hero-qr-scan-mockup.png",
|
||||
heroImageAlt = "Industry example showing a QR code workflow in action",
|
||||
statistics = [],
|
||||
benefits = [],
|
||||
}: IndustryPageTemplateProps) {
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd
|
||||
data={[...schemaData, breadcrumbSchema(breadcrumbs), faqPageSchema(faq)]}
|
||||
/>
|
||||
<MarketingPageTracker
|
||||
pageType={pageType}
|
||||
cluster={cluster}
|
||||
useCase={useCase}
|
||||
/>
|
||||
|
||||
<div className="min-h-screen bg-white text-slate-900 font-sans">
|
||||
|
||||
{/* --- HERO SECTION --- */}
|
||||
<section className="relative pt-24 pb-20 lg:pt-36 lg:pb-32 overflow-hidden border-b border-slate-100 bg-slate-50/50">
|
||||
{/* Decorative Backgrounds */}
|
||||
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_right,#cbd5e1_1px,transparent_1px),linear-gradient(to_bottom,#cbd5e1_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_80%_80%_at_50%_0%,#000_80%,transparent_110%)] opacity-30" />
|
||||
<div className="absolute top-0 right-0 -mr-[20rem] w-[60rem] h-[60rem] bg-blue-200/40 rounded-full blur-[120px] pointer-events-none" />
|
||||
|
||||
<div className="relative container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-16 lg:grid-cols-2 lg:gap-8 items-center">
|
||||
|
||||
{/* Left Column: Text & CTAs */}
|
||||
<div className="flex flex-col justify-center text-center lg:text-left pt-10 lg:pt-0">
|
||||
<Breadcrumbs
|
||||
items={breadcrumbs}
|
||||
className="mb-8 justify-center lg:justify-start [&_a]:text-slate-500 [&_a:hover]:text-blue-600 [&_span]:text-slate-400 [&_[aria-current=page]]:text-slate-900"
|
||||
/>
|
||||
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-blue-200 bg-white/60 backdrop-blur-sm px-4 py-2 text-sm font-semibold text-blue-700 shadow-sm mb-8 mx-auto lg:mx-0 w-max">
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-500"></span>
|
||||
</span>
|
||||
Industry Solutions
|
||||
</div>
|
||||
|
||||
<h1 className="max-w-3xl text-5xl font-extrabold tracking-tight text-slate-900 sm:text-6xl lg:text-7xl mb-8 leading-[1.1]">
|
||||
{title.replace("QR Codes for", "")}{" "}
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-indigo-600 block sm:inline">
|
||||
QR Codes
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p className="max-w-2xl text-xl leading-relaxed text-slate-600 mb-12 font-medium mx-auto lg:mx-0">
|
||||
{intro}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center lg:justify-start gap-4 w-full">
|
||||
<TrackedCtaLink
|
||||
href={primaryCta.href}
|
||||
ctaLabel={primaryCta.label}
|
||||
ctaLocation="hero_primary"
|
||||
pageType={pageType}
|
||||
cluster={cluster}
|
||||
useCase={useCase}
|
||||
>
|
||||
<Button
|
||||
size="lg"
|
||||
className="w-full sm:w-auto bg-blue-600 hover:bg-blue-700 text-white rounded-full px-8 py-7 text-lg font-bold shadow-xl shadow-blue-600/30 transition-all hover:-translate-y-1"
|
||||
>
|
||||
{primaryCta.label}
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
|
||||
<TrackedCtaLink
|
||||
href={secondaryCta.href}
|
||||
ctaLabel={secondaryCta.label}
|
||||
ctaLocation="hero_secondary"
|
||||
pageType={pageType}
|
||||
cluster={cluster}
|
||||
useCase={useCase}
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="w-full sm:w-auto overflow-hidden bg-white border-slate-200 text-slate-700 hover:border-slate-300 hover:bg-slate-50 hover:text-slate-900 rounded-full px-8 py-7 text-lg font-bold shadow-sm transition-all hover:-translate-y-1"
|
||||
>
|
||||
{secondaryCta.label}
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column: Abstract App Interface Mockup */}
|
||||
<div className="relative mx-auto w-full max-w-lg lg:max-w-none flex justify-center lg:justify-end mt-12 lg:mt-0 pb-16 lg:pb-0">
|
||||
|
||||
<div className="relative w-full max-w-[440px] aspect-[4/5] sm:aspect-[4/5]">
|
||||
{/* Decorative glowing blobs behind the image */}
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[120%] h-[120%] bg-blue-500/20 blur-[90px] rounded-full pointer-events-none" />
|
||||
<div className="absolute -top-10 -right-10 w-64 h-64 bg-indigo-500/20 blur-[70px] rounded-full pointer-events-none" />
|
||||
|
||||
{/* Floating abstract element 1: Notification */}
|
||||
<div className="absolute z-30 top-1/4 -left-6 sm:-left-12 bg-white/90 backdrop-blur-md p-4 rounded-2xl shadow-2xl shadow-slate-200/50 border border-slate-100/50 flex items-center gap-4 transition-transform hover:scale-105 duration-300 scale-90 sm:scale-100">
|
||||
<div className="w-12 h-12 rounded-full bg-emerald-100 flex items-center justify-center shrink-0">
|
||||
<CheckCircle2 className="w-6 h-6 text-emerald-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-black text-slate-900 tracking-tight">Active Scan!</div>
|
||||
<div className="text-xs text-slate-500 font-medium">Just now</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Floating abstract element 2: Stats */}
|
||||
<div className="absolute z-30 bottom-1/4 -right-4 sm:-right-8 bg-white/90 backdrop-blur-md p-4 rounded-2xl shadow-2xl shadow-slate-200/50 border border-slate-100/50 flex items-center gap-4 transition-transform hover:scale-105 duration-300 scale-90 sm:scale-100 delay-100">
|
||||
<div className="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center shrink-0">
|
||||
<BarChart3 className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-black text-slate-900 tracking-tight">+148 Views</div>
|
||||
<div className="text-xs text-slate-500 font-medium">This week</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* AI Generated Photorealistic Image */}
|
||||
<div className="relative z-20 w-full h-full rounded-[2rem] sm:rounded-[2.5rem] shadow-2xl shadow-blue-900/30 border-8 border-white overflow-hidden rotate-0 sm:rotate-2 transition-transform duration-700 hover:rotate-0">
|
||||
<Image
|
||||
src={heroImage}
|
||||
alt={heroImageAlt}
|
||||
fill
|
||||
priority
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* --- BENEFITS SECTION --- */}
|
||||
{benefits.length > 0 && (
|
||||
<section className="py-20 bg-slate-50 border-t border-slate-100 relative z-10">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl max-w-2xl mx-auto">
|
||||
Why Leading {title.replace("QR Codes for ", "").replace("QR Codes for", "")} Businesses Use QR Master
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{benefits.map((benefit, index) => (
|
||||
<div key={benefit.title} className="bg-white rounded-3xl p-8 shadow-sm border border-slate-200 hover:border-blue-200 hover:shadow-lg transition-all">
|
||||
<div className="w-12 h-12 rounded-full bg-indigo-50 flex items-center justify-center text-indigo-600 font-bold text-xl mb-6">
|
||||
{index + 1}
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-slate-900 mb-4">{benefit.title}</h3>
|
||||
<p className="text-slate-600 leading-relaxed">{benefit.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* --- WORKFLOW CARDS SECTION (USE CASES) --- */}
|
||||
<section className="py-12 bg-white relative z-10">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{workflowCards.map((card, index) => {
|
||||
const Icon = WORKFLOW_ICONS[index % WORKFLOW_ICONS.length];
|
||||
return (
|
||||
<div
|
||||
key={card.title}
|
||||
className="flex flex-col group rounded-[2rem] border border-slate-200 bg-white p-8 shadow-sm hover:shadow-xl transition-all duration-300 hover:-translate-y-1"
|
||||
>
|
||||
<div className="mb-6 flex h-14 w-14 items-center justify-center rounded-2xl bg-blue-50 text-blue-600 group-hover:scale-110 group-hover:bg-blue-600 group-hover:text-white transition-all duration-300">
|
||||
<Icon className="h-7 w-7" />
|
||||
</div>
|
||||
<h3 className="mb-3 text-2xl font-bold text-slate-900 leading-tight">
|
||||
{card.title}
|
||||
</h3>
|
||||
<p className="text-slate-600 leading-relaxed text-lg">
|
||||
{card.description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* --- QUICK SUMMARY (ANSWER FIRST) --- */}
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-8 text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
|
||||
Quick Summary
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-slate-600 max-w-2xl mx-auto">
|
||||
{answer}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-[2rem] border border-slate-100 bg-white p-2">
|
||||
<AnswerFirstBlock
|
||||
whatIsIt={answer}
|
||||
whenToUse={whenToUse}
|
||||
comparison={{
|
||||
leftTitle: "Static Pages",
|
||||
rightTitle: "QR Master Pages",
|
||||
items: comparisonItems,
|
||||
}}
|
||||
howTo={{
|
||||
steps: howToSteps,
|
||||
}}
|
||||
className="mt-0 shadow-none border-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* --- IMPLEMENTATION CHECKLIST --- */}
|
||||
<section className="py-20 bg-slate-50">
|
||||
<div className="container mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-2xl text-center mb-12">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
|
||||
{checklistTitle}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid gap-x-8 gap-y-4 sm:grid-cols-2">
|
||||
{checklist.map((item) => (
|
||||
<div key={item} className="flex items-start gap-4 p-4 rounded-xl bg-white border border-slate-200 shadow-sm">
|
||||
<CheckCircle2 className="h-6 w-6 shrink-0 text-blue-600 mt-0.5" />
|
||||
<span className="text-lg font-medium text-slate-700">{item}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* --- RECOMMENDED TOOLS --- */}
|
||||
<section className="py-24">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="relative overflow-hidden rounded-[2.5rem] bg-slate-900 px-6 py-16 sm:px-12 sm:py-20 lg:px-16 text-center text-white shadow-2xl shadow-indigo-900/20 border border-slate-800">
|
||||
<div className="absolute -top-24 -right-24 w-96 h-96 bg-blue-600/20 blur-[100px] rounded-full pointer-events-none" />
|
||||
<div className="absolute -bottom-24 -left-24 w-96 h-96 bg-indigo-600/20 blur-[100px] rounded-full pointer-events-none" />
|
||||
<div className="relative z-10">
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl mb-12">
|
||||
Recommended Tools
|
||||
</h2>
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3 max-w-5xl mx-auto">
|
||||
{supportLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="group flex flex-col items-center rounded-3xl bg-slate-800/50 p-8 border border-slate-700 transition-all hover:bg-white hover:border-white hover:scale-105 backdrop-blur-md shadow-xl"
|
||||
>
|
||||
<div className="mb-4 text-indigo-400 group-hover:text-blue-600 transition-colors">
|
||||
<Link2 className="h-10 w-10" />
|
||||
</div>
|
||||
<div className="text-xl font-bold text-white group-hover:text-slate-900 mb-2 transition-colors">
|
||||
{link.title}
|
||||
</div>
|
||||
<p className="mb-4 text-sm leading-relaxed text-slate-400 group-hover:text-slate-600 transition-colors">
|
||||
{link.description}
|
||||
</p>
|
||||
<div className="text-sm font-medium text-slate-300 group-hover:text-blue-600 transition-colors">
|
||||
Use Tool →
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* --- FAQ SECTION --- */}
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-10">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
|
||||
FAQ
|
||||
</h2>
|
||||
</div>
|
||||
<div className="rounded-3xl bg-white p-6 sm:p-10 shadow-sm border border-slate-200">
|
||||
<FAQSection items={faq} title="" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* --- FINAL CTA --- */}
|
||||
<section className="relative overflow-hidden bg-slate-900 border-t border-slate-800 pt-24 pb-44 text-center -mb-20">
|
||||
<div className="absolute top-0 right-1/4 w-[40rem] h-[40rem] bg-blue-600/20 blur-[120px] rounded-full pointer-events-none" />
|
||||
<div className="absolute bottom-0 left-1/4 w-[40rem] h-[40rem] bg-indigo-600/20 blur-[120px] rounded-full pointer-events-none" />
|
||||
|
||||
<div className="relative z-10 container mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<h2 className="mb-6 text-4xl font-extrabold tracking-tight text-white sm:text-5xl">
|
||||
Ready to modernize your operations?
|
||||
</h2>
|
||||
<p className="mx-auto mb-10 text-xl text-slate-300 max-w-2xl font-medium">
|
||||
Elevate your {title.replace("QR Codes for", "").trim().toLowerCase()} experience: seamless operations and enhanced engagement.
|
||||
</p>
|
||||
<div className="flex flex-col justify-center gap-4 sm:flex-row">
|
||||
<TrackedCtaLink
|
||||
href={primaryCta.href}
|
||||
ctaLabel={primaryCta.label}
|
||||
ctaLocation="footer_primary"
|
||||
pageType={pageType}
|
||||
cluster={cluster}
|
||||
useCase={useCase}
|
||||
>
|
||||
<Button
|
||||
size="lg"
|
||||
className="w-full bg-blue-600 px-10 py-7 text-lg font-bold text-white hover:bg-blue-500 hover:shadow-blue-500/25 sm:w-auto rounded-full shadow-xl shadow-blue-600/20 transition-all hover:-translate-y-1 border border-blue-500"
|
||||
>
|
||||
{primaryCta.label}
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
import type { FAQItem } from "@/lib/types";
|
||||
import type { Metadata } from "next";
|
||||
import Image from "next/image";
|
||||
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ArrowRight,
|
||||
CheckCircle2,
|
||||
Building2,
|
||||
Settings2,
|
||||
Smartphone,
|
||||
BarChart3,
|
||||
Link2,
|
||||
} from "lucide-react";
|
||||
|
||||
import Breadcrumbs, { BreadcrumbItem } from "@/components/Breadcrumbs";
|
||||
import SeoJsonLd from "@/components/SeoJsonLd";
|
||||
import { FAQSection } from "@/components/aeo/FAQSection";
|
||||
import {
|
||||
MarketingPageTracker,
|
||||
TrackedCtaLink,
|
||||
} from "@/components/marketing/MarketingAnalytics";
|
||||
import { AnswerFirstBlock } from "@/components/marketing/AnswerFirstBlock";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { breadcrumbSchema, faqPageSchema } from "@/lib/schema";
|
||||
|
||||
type LinkCard = {
|
||||
href: string;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
type IndustryPageTemplateProps = {
|
||||
title: string;
|
||||
description: string;
|
||||
eyebrow: string;
|
||||
intro: string;
|
||||
pageType: "commercial" | "use_case";
|
||||
cluster: string;
|
||||
useCase?: string;
|
||||
breadcrumbs: BreadcrumbItem[];
|
||||
answer: string;
|
||||
whenToUse: string[];
|
||||
comparisonItems: {
|
||||
label: string;
|
||||
value: boolean;
|
||||
text?: string;
|
||||
}[];
|
||||
howToSteps: string[];
|
||||
primaryCta: {
|
||||
href: string;
|
||||
label: string;
|
||||
};
|
||||
secondaryCta: {
|
||||
href: string;
|
||||
label: string;
|
||||
};
|
||||
workflowTitle: string;
|
||||
workflowIntro: string;
|
||||
workflowCards: {
|
||||
title: string;
|
||||
description: string;
|
||||
}[];
|
||||
checklistTitle: string;
|
||||
checklist: string[];
|
||||
supportLinks: LinkCard[];
|
||||
faq: FAQItem[];
|
||||
schemaData?: Record<string, unknown>[];
|
||||
heroImage?: string;
|
||||
heroImageAlt?: string;
|
||||
statistics?: { value: string; label: string }[];
|
||||
benefits?: { title: string; description: string }[];
|
||||
};
|
||||
|
||||
export function buildIndustryMetadata({
|
||||
title,
|
||||
fallbackTitle,
|
||||
description,
|
||||
canonicalPath,
|
||||
}: {
|
||||
title: string;
|
||||
fallbackTitle?: string;
|
||||
description: string;
|
||||
canonicalPath: string;
|
||||
}): Metadata {
|
||||
const canonical = `https://www.qrmaster.net${canonicalPath}`;
|
||||
const brandSuffix = " | QR Master";
|
||||
const maxTitleLength = 60;
|
||||
const maxBaseTitleLength = maxTitleLength - brandSuffix.length;
|
||||
const normalizedTitle = title.replace(/\s+\|\s+QR Master$/i, "").trim();
|
||||
const fallback = fallbackTitle?.replace(/\s+\|\s+QR Master$/i, "").trim();
|
||||
const candidates = [
|
||||
normalizedTitle,
|
||||
normalizedTitle.split(":")[0]?.trim(),
|
||||
fallback,
|
||||
].filter((candidate): candidate is string => Boolean(candidate));
|
||||
const seoTitle =
|
||||
candidates.find((candidate) => candidate.length <= maxBaseTitleLength) ??
|
||||
normalizedTitle.slice(0, maxBaseTitleLength).replace(/\s+\S*$/, "").trim();
|
||||
const fullTitle = `${seoTitle} | QR Master`;
|
||||
|
||||
return {
|
||||
title: {
|
||||
absolute: fullTitle,
|
||||
},
|
||||
description,
|
||||
alternates: {
|
||||
canonical,
|
||||
languages: {
|
||||
"x-default": canonical,
|
||||
en: canonical,
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: fullTitle,
|
||||
description,
|
||||
url: canonical,
|
||||
type: "website",
|
||||
images: ["/og-image.png"],
|
||||
},
|
||||
twitter: {
|
||||
title: fullTitle,
|
||||
description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Map a consistent icon for workflow cards depending on index
|
||||
const WORKFLOW_ICONS = [Smartphone, Settings2, BarChart3, Building2];
|
||||
|
||||
export function IndustryPageTemplate({
|
||||
title,
|
||||
description,
|
||||
eyebrow,
|
||||
intro,
|
||||
pageType,
|
||||
cluster,
|
||||
useCase,
|
||||
breadcrumbs,
|
||||
answer,
|
||||
whenToUse,
|
||||
comparisonItems,
|
||||
howToSteps,
|
||||
primaryCta,
|
||||
secondaryCta,
|
||||
workflowTitle,
|
||||
workflowIntro,
|
||||
workflowCards,
|
||||
checklistTitle,
|
||||
checklist,
|
||||
supportLinks,
|
||||
faq,
|
||||
schemaData = [],
|
||||
heroImage = "/hero-qr-scan-mockup.png",
|
||||
heroImageAlt = "Industry example showing a QR code workflow in action",
|
||||
statistics = [],
|
||||
benefits = [],
|
||||
}: IndustryPageTemplateProps) {
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd
|
||||
data={[...schemaData, breadcrumbSchema(breadcrumbs), faqPageSchema(faq)]}
|
||||
/>
|
||||
<MarketingPageTracker
|
||||
pageType={pageType}
|
||||
cluster={cluster}
|
||||
useCase={useCase}
|
||||
/>
|
||||
|
||||
<div className="min-h-screen bg-white text-slate-900 font-sans">
|
||||
|
||||
{/* --- HERO SECTION --- */}
|
||||
<section className="relative pt-24 pb-20 lg:pt-36 lg:pb-32 overflow-hidden border-b border-slate-100 bg-slate-50/50">
|
||||
{/* Decorative Backgrounds */}
|
||||
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_right,#cbd5e1_1px,transparent_1px),linear-gradient(to_bottom,#cbd5e1_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_80%_80%_at_50%_0%,#000_80%,transparent_110%)] opacity-30" />
|
||||
<div className="absolute top-0 right-0 -mr-[20rem] w-[60rem] h-[60rem] bg-blue-200/40 rounded-full blur-[120px] pointer-events-none" />
|
||||
|
||||
<div className="relative container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-16 lg:grid-cols-2 lg:gap-8 items-center">
|
||||
|
||||
{/* Left Column: Text & CTAs */}
|
||||
<div className="flex flex-col justify-center text-center lg:text-left pt-10 lg:pt-0">
|
||||
<Breadcrumbs
|
||||
items={breadcrumbs}
|
||||
className="mb-8 justify-center lg:justify-start [&_a]:text-slate-500 [&_a:hover]:text-blue-600 [&_span]:text-slate-400 [&_[aria-current=page]]:text-slate-900"
|
||||
/>
|
||||
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-blue-200 bg-white/60 backdrop-blur-sm px-4 py-2 text-sm font-semibold text-blue-700 shadow-sm mb-8 mx-auto lg:mx-0 w-max">
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-500"></span>
|
||||
</span>
|
||||
Industry Solutions
|
||||
</div>
|
||||
|
||||
<h1 className="max-w-3xl text-5xl font-extrabold tracking-tight text-slate-900 sm:text-6xl lg:text-7xl mb-8 leading-[1.1]">
|
||||
{title.replace("QR Codes for", "")}{" "}
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-indigo-600 block sm:inline">
|
||||
QR Codes
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p className="max-w-2xl text-xl leading-relaxed text-slate-600 mb-12 font-medium mx-auto lg:mx-0">
|
||||
{intro}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center lg:justify-start gap-4 w-full">
|
||||
<TrackedCtaLink
|
||||
href={primaryCta.href}
|
||||
ctaLabel={primaryCta.label}
|
||||
ctaLocation="hero_primary"
|
||||
pageType={pageType}
|
||||
cluster={cluster}
|
||||
useCase={useCase}
|
||||
>
|
||||
<Button
|
||||
size="lg"
|
||||
className="w-full sm:w-auto bg-blue-600 hover:bg-blue-700 text-white rounded-full px-8 py-7 text-lg font-bold shadow-xl shadow-blue-600/30 transition-all hover:-translate-y-1"
|
||||
>
|
||||
{primaryCta.label}
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
|
||||
<TrackedCtaLink
|
||||
href={secondaryCta.href}
|
||||
ctaLabel={secondaryCta.label}
|
||||
ctaLocation="hero_secondary"
|
||||
pageType={pageType}
|
||||
cluster={cluster}
|
||||
useCase={useCase}
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="w-full sm:w-auto overflow-hidden bg-white border-slate-200 text-slate-700 hover:border-slate-300 hover:bg-slate-50 hover:text-slate-900 rounded-full px-8 py-7 text-lg font-bold shadow-sm transition-all hover:-translate-y-1"
|
||||
>
|
||||
{secondaryCta.label}
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column: Abstract App Interface Mockup */}
|
||||
<div className="relative mx-auto w-full max-w-lg lg:max-w-none flex justify-center lg:justify-end mt-12 lg:mt-0 pb-16 lg:pb-0">
|
||||
|
||||
<div className="relative w-full max-w-[440px] aspect-[4/5] sm:aspect-[4/5]">
|
||||
{/* Decorative glowing blobs behind the image */}
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[120%] h-[120%] bg-blue-500/20 blur-[90px] rounded-full pointer-events-none" />
|
||||
<div className="absolute -top-10 -right-10 w-64 h-64 bg-indigo-500/20 blur-[70px] rounded-full pointer-events-none" />
|
||||
|
||||
{/* Floating abstract element 1: Notification */}
|
||||
<div className="absolute z-30 top-1/4 -left-6 sm:-left-12 bg-white/90 backdrop-blur-md p-4 rounded-2xl shadow-2xl shadow-slate-200/50 border border-slate-100/50 flex items-center gap-4 transition-transform hover:scale-105 duration-300 scale-90 sm:scale-100">
|
||||
<div className="w-12 h-12 rounded-full bg-emerald-100 flex items-center justify-center shrink-0">
|
||||
<CheckCircle2 className="w-6 h-6 text-emerald-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-black text-slate-900 tracking-tight">Active Scan!</div>
|
||||
<div className="text-xs text-slate-500 font-medium">Just now</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Floating abstract element 2: Stats */}
|
||||
<div className="absolute z-30 bottom-1/4 -right-4 sm:-right-8 bg-white/90 backdrop-blur-md p-4 rounded-2xl shadow-2xl shadow-slate-200/50 border border-slate-100/50 flex items-center gap-4 transition-transform hover:scale-105 duration-300 scale-90 sm:scale-100 delay-100">
|
||||
<div className="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center shrink-0">
|
||||
<BarChart3 className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-black text-slate-900 tracking-tight">+148 Views</div>
|
||||
<div className="text-xs text-slate-500 font-medium">This week</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* AI Generated Photorealistic Image */}
|
||||
<div className="relative z-20 w-full h-full rounded-[2rem] sm:rounded-[2.5rem] shadow-2xl shadow-blue-900/30 border-8 border-white overflow-hidden rotate-0 sm:rotate-2 transition-transform duration-700 hover:rotate-0">
|
||||
<Image
|
||||
src={heroImage}
|
||||
alt={heroImageAlt}
|
||||
fill
|
||||
priority
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* --- BENEFITS SECTION --- */}
|
||||
{benefits.length > 0 && (
|
||||
<section className="py-20 bg-slate-50 border-t border-slate-100 relative z-10">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl max-w-2xl mx-auto">
|
||||
Why Leading {title.replace("QR Codes for ", "").replace("QR Codes for", "")} Businesses Use QR Master
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{benefits.map((benefit, index) => (
|
||||
<div key={benefit.title} className="bg-white rounded-3xl p-8 shadow-sm border border-slate-200 hover:border-blue-200 hover:shadow-lg transition-all">
|
||||
<div className="w-12 h-12 rounded-full bg-indigo-50 flex items-center justify-center text-indigo-600 font-bold text-xl mb-6">
|
||||
{index + 1}
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-slate-900 mb-4">{benefit.title}</h3>
|
||||
<p className="text-slate-600 leading-relaxed">{benefit.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* --- WORKFLOW CARDS SECTION (USE CASES) --- */}
|
||||
<section className="py-12 bg-white relative z-10">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{workflowCards.map((card, index) => {
|
||||
const Icon = WORKFLOW_ICONS[index % WORKFLOW_ICONS.length];
|
||||
return (
|
||||
<div
|
||||
key={card.title}
|
||||
className="flex flex-col group rounded-[2rem] border border-slate-200 bg-white p-8 shadow-sm hover:shadow-xl transition-all duration-300 hover:-translate-y-1"
|
||||
>
|
||||
<div className="mb-6 flex h-14 w-14 items-center justify-center rounded-2xl bg-blue-50 text-blue-600 group-hover:scale-110 group-hover:bg-blue-600 group-hover:text-white transition-all duration-300">
|
||||
<Icon className="h-7 w-7" />
|
||||
</div>
|
||||
<h3 className="mb-3 text-2xl font-bold text-slate-900 leading-tight">
|
||||
{card.title}
|
||||
</h3>
|
||||
<p className="text-slate-600 leading-relaxed text-lg">
|
||||
{card.description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* --- QUICK SUMMARY (ANSWER FIRST) --- */}
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-8 text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
|
||||
Quick Summary
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-slate-600 max-w-2xl mx-auto">
|
||||
{answer}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-[2rem] border border-slate-100 bg-white p-2">
|
||||
<AnswerFirstBlock
|
||||
whatIsIt={answer}
|
||||
whenToUse={whenToUse}
|
||||
comparison={{
|
||||
leftTitle: "Static Pages",
|
||||
rightTitle: "QR Master Pages",
|
||||
items: comparisonItems,
|
||||
}}
|
||||
howTo={{
|
||||
steps: howToSteps,
|
||||
}}
|
||||
className="mt-0 shadow-none border-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* --- IMPLEMENTATION CHECKLIST --- */}
|
||||
<section className="py-20 bg-slate-50">
|
||||
<div className="container mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-2xl text-center mb-12">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
|
||||
{checklistTitle}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid gap-x-8 gap-y-4 sm:grid-cols-2">
|
||||
{checklist.map((item) => (
|
||||
<div key={item} className="flex items-start gap-4 p-4 rounded-xl bg-white border border-slate-200 shadow-sm">
|
||||
<CheckCircle2 className="h-6 w-6 shrink-0 text-blue-600 mt-0.5" />
|
||||
<span className="text-lg font-medium text-slate-700">{item}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* --- RECOMMENDED TOOLS --- */}
|
||||
<section className="py-24">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="relative overflow-hidden rounded-[2.5rem] bg-slate-900 px-6 py-16 sm:px-12 sm:py-20 lg:px-16 text-center text-white shadow-2xl shadow-indigo-900/20 border border-slate-800">
|
||||
<div className="absolute -top-24 -right-24 w-96 h-96 bg-blue-600/20 blur-[100px] rounded-full pointer-events-none" />
|
||||
<div className="absolute -bottom-24 -left-24 w-96 h-96 bg-indigo-600/20 blur-[100px] rounded-full pointer-events-none" />
|
||||
<div className="relative z-10">
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl mb-12">
|
||||
Recommended Tools
|
||||
</h2>
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3 max-w-5xl mx-auto">
|
||||
{supportLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="group flex flex-col items-center rounded-3xl bg-slate-800/50 p-8 border border-slate-700 transition-all hover:bg-white hover:border-white hover:scale-105 backdrop-blur-md shadow-xl"
|
||||
>
|
||||
<div className="mb-4 text-indigo-400 group-hover:text-blue-600 transition-colors">
|
||||
<Link2 className="h-10 w-10" />
|
||||
</div>
|
||||
<div className="text-xl font-bold text-white group-hover:text-slate-900 mb-2 transition-colors">
|
||||
{link.title}
|
||||
</div>
|
||||
<p className="mb-4 text-sm leading-relaxed text-slate-400 group-hover:text-slate-600 transition-colors">
|
||||
{link.description}
|
||||
</p>
|
||||
<div className="text-sm font-medium text-slate-300 group-hover:text-blue-600 transition-colors">
|
||||
Use Tool →
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* --- FAQ SECTION --- */}
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-10">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
|
||||
FAQ
|
||||
</h2>
|
||||
</div>
|
||||
<div className="rounded-3xl bg-white p-6 sm:p-10 shadow-sm border border-slate-200">
|
||||
<FAQSection items={faq} title="" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* --- FINAL CTA --- */}
|
||||
<section className="relative overflow-hidden bg-slate-900 border-t border-slate-800 pt-24 pb-44 text-center -mb-20">
|
||||
<div className="absolute top-0 right-1/4 w-[40rem] h-[40rem] bg-blue-600/20 blur-[120px] rounded-full pointer-events-none" />
|
||||
<div className="absolute bottom-0 left-1/4 w-[40rem] h-[40rem] bg-indigo-600/20 blur-[120px] rounded-full pointer-events-none" />
|
||||
|
||||
<div className="relative z-10 container mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<h2 className="mb-6 text-4xl font-extrabold tracking-tight text-white sm:text-5xl">
|
||||
Ready to modernize your operations?
|
||||
</h2>
|
||||
<p className="mx-auto mb-10 text-xl text-slate-300 max-w-2xl font-medium">
|
||||
Elevate your {title.replace("QR Codes for", "").trim().toLowerCase()} experience: seamless operations and enhanced engagement.
|
||||
</p>
|
||||
<div className="flex flex-col justify-center gap-4 sm:flex-row">
|
||||
<TrackedCtaLink
|
||||
href={primaryCta.href}
|
||||
ctaLabel={primaryCta.label}
|
||||
ctaLocation="footer_primary"
|
||||
pageType={pageType}
|
||||
cluster={cluster}
|
||||
useCase={useCase}
|
||||
>
|
||||
<Button
|
||||
size="lg"
|
||||
className="w-full bg-blue-600 px-10 py-7 text-lg font-bold text-white hover:bg-blue-500 hover:shadow-blue-500/25 sm:w-auto rounded-full shadow-xl shadow-blue-600/20 transition-all hover:-translate-y-1 border border-blue-500"
|
||||
>
|
||||
{primaryCta.label}
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,99 +1,99 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { X, Zap, BarChart2, RefreshCw, Palette } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
interface PostDownloadPopupProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const BENEFITS = [
|
||||
{ icon: RefreshCw, text: 'Edit the link anytime — QR stays the same' },
|
||||
{ icon: BarChart2, text: 'See who scans, when & where' },
|
||||
{ icon: Palette, text: 'Custom colors, logo & frames' },
|
||||
{ icon: Zap, text: 'Free plan included — upgrade anytime for more' },
|
||||
];
|
||||
|
||||
const LS_KEY = 'qrm_download_popup_seen';
|
||||
|
||||
export function shouldShowDownloadPopup(): boolean {
|
||||
try { return !localStorage.getItem(LS_KEY); } catch { return false; }
|
||||
}
|
||||
|
||||
export function markDownloadPopupSeen(): void {
|
||||
try { localStorage.setItem(LS_KEY, '1'); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
export default function PostDownloadPopup({ open, onClose }: PostDownloadPopupProps) {
|
||||
const overlayRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
markDownloadPopupSeen();
|
||||
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
|
||||
document.addEventListener('keydown', onKey);
|
||||
return () => document.removeEventListener('keydown', onKey);
|
||||
}, [open, onClose]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={overlayRef}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||
style={{ backgroundColor: 'rgba(15, 23, 42, 0.6)', backdropFilter: 'blur(4px)' }}
|
||||
onClick={(e) => { if (e.target === overlayRef.current) onClose(); }}
|
||||
>
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-md overflow-hidden animate-in fade-in zoom-in-95 duration-200">
|
||||
|
||||
{/* Header */}
|
||||
<div className="relative bg-gradient-to-br from-[#4F46E5] to-[#7C3AED] p-6 text-white text-center">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 text-white/70 hover:text-white transition-colors"
|
||||
aria-label="Close"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="w-12 h-12 bg-white/20 rounded-2xl flex items-center justify-center mx-auto mb-3">
|
||||
<Zap className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold">Your QR code is downloading!</h2>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Want to make it smarter — for free?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Benefits */}
|
||||
<div className="p-6 space-y-3">
|
||||
{BENEFITS.map(({ icon: Icon, text }) => (
|
||||
<div key={text} className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-xl bg-indigo-50 flex items-center justify-center shrink-0">
|
||||
<Icon className="w-4 h-4 text-[#4F46E5]" />
|
||||
</div>
|
||||
<span className="text-sm text-slate-700">{text}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* CTAs */}
|
||||
<div className="px-6 pb-6 space-y-3">
|
||||
<Link href="/signup" onClick={onClose} className="block">
|
||||
<Button className="w-full bg-[#4F46E5] hover:bg-[#4338CA] text-white h-12 text-base font-semibold shadow-lg">
|
||||
Create Free Account
|
||||
</Button>
|
||||
</Link>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-full text-sm text-slate-400 hover:text-slate-600 transition-colors py-1"
|
||||
>
|
||||
No thanks, keep it static
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { X, Zap, BarChart2, RefreshCw, Palette } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
interface PostDownloadPopupProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const BENEFITS = [
|
||||
{ icon: RefreshCw, text: 'Edit the link anytime — QR stays the same' },
|
||||
{ icon: BarChart2, text: 'See who scans, when & where' },
|
||||
{ icon: Palette, text: 'Custom colors, logo & frames' },
|
||||
{ icon: Zap, text: 'Free plan included — upgrade anytime for more' },
|
||||
];
|
||||
|
||||
const LS_KEY = 'qrm_download_popup_seen';
|
||||
|
||||
export function shouldShowDownloadPopup(): boolean {
|
||||
try { return !localStorage.getItem(LS_KEY); } catch { return false; }
|
||||
}
|
||||
|
||||
export function markDownloadPopupSeen(): void {
|
||||
try { localStorage.setItem(LS_KEY, '1'); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
export default function PostDownloadPopup({ open, onClose }: PostDownloadPopupProps) {
|
||||
const overlayRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
markDownloadPopupSeen();
|
||||
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
|
||||
document.addEventListener('keydown', onKey);
|
||||
return () => document.removeEventListener('keydown', onKey);
|
||||
}, [open, onClose]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={overlayRef}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||
style={{ backgroundColor: 'rgba(15, 23, 42, 0.6)', backdropFilter: 'blur(4px)' }}
|
||||
onClick={(e) => { if (e.target === overlayRef.current) onClose(); }}
|
||||
>
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-md overflow-hidden animate-in fade-in zoom-in-95 duration-200">
|
||||
|
||||
{/* Header */}
|
||||
<div className="relative bg-gradient-to-br from-[#4F46E5] to-[#7C3AED] p-6 text-white text-center">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 text-white/70 hover:text-white transition-colors"
|
||||
aria-label="Close"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="w-12 h-12 bg-white/20 rounded-2xl flex items-center justify-center mx-auto mb-3">
|
||||
<Zap className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold">Your QR code is downloading!</h2>
|
||||
<p className="text-white/80 text-sm mt-1">
|
||||
Want to make it smarter — for free?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Benefits */}
|
||||
<div className="p-6 space-y-3">
|
||||
{BENEFITS.map(({ icon: Icon, text }) => (
|
||||
<div key={text} className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-xl bg-indigo-50 flex items-center justify-center shrink-0">
|
||||
<Icon className="w-4 h-4 text-[#4F46E5]" />
|
||||
</div>
|
||||
<span className="text-sm text-slate-700">{text}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* CTAs */}
|
||||
<div className="px-6 pb-6 space-y-3">
|
||||
<Link href="/signup" onClick={onClose} className="block">
|
||||
<Button className="w-full bg-[#4F46E5] hover:bg-[#4338CA] text-white h-12 text-base font-semibold shadow-lg">
|
||||
Create Free Account
|
||||
</Button>
|
||||
</Link>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-full text-sm text-slate-400 hover:text-slate-600 transition-colors py-1"
|
||||
>
|
||||
No thanks, keep it static
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,164 +1,164 @@
|
||||
'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';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { BillingToggle } from '@/components/ui/BillingToggle';
|
||||
|
||||
interface PricingProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const Pricing: React.FC<PricingProps> = ({ t }) => {
|
||||
const [billingPeriod, setBillingPeriod] = useState<'month' | 'year'>('month');
|
||||
|
||||
const plans = [
|
||||
{
|
||||
key: 'free',
|
||||
popular: false,
|
||||
},
|
||||
{
|
||||
key: 'pro',
|
||||
popular: true,
|
||||
},
|
||||
{
|
||||
key: 'business',
|
||||
popular: false,
|
||||
},
|
||||
{
|
||||
key: 'enterprise',
|
||||
popular: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="pricing" className="py-16">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
|
||||
{t.pricing.title}
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600">{t.pricing.subtitle}</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="flex justify-center mb-8">
|
||||
<BillingToggle value={billingPeriod} onChange={setBillingPeriod} />
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8 max-w-7xl mx-auto">
|
||||
{plans.map((plan, index) => (
|
||||
<motion.div
|
||||
key={plan.key}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="h-full"
|
||||
>
|
||||
<Card
|
||||
className={`h-full flex flex-col ${
|
||||
plan.popular
|
||||
? 'border-primary-500 shadow-xl relative scale-105 z-10'
|
||||
: 'border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all'
|
||||
}`}
|
||||
>
|
||||
{plan.popular && (
|
||||
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2 w-full text-center">
|
||||
<Badge variant="info" className="px-4 py-1.5 shadow-sm">
|
||||
{t.pricing[plan.key].badge}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CardHeader className="text-center pb-8">
|
||||
<CardTitle className="text-2xl mb-4">
|
||||
{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' || plan.key === 'enterprise'
|
||||
? 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' || plan.key === 'enterprise'
|
||||
? t.pricing[plan.key].period
|
||||
: billingPeriod === 'month'
|
||||
? t.pricing[plan.key].period
|
||||
: 'per year'}
|
||||
</span>
|
||||
</div>
|
||||
{billingPeriod === 'year' &&
|
||||
plan.key !== 'free' &&
|
||||
plan.key !== 'enterprise' && (
|
||||
<Badge variant="success" className="mt-2">
|
||||
Save 16%
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-8 flex-1 flex flex-col">
|
||||
<ul className="space-y-3 flex-1">
|
||||
{t.pricing[plan.key].features.map(
|
||||
(feature: string, index: number) => (
|
||||
<li key={index} className="flex items-start space-x-3">
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-gray-700">{feature}</span>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
|
||||
<div className="mt-8 pt-8 border-t border-gray-100">
|
||||
{plan.key === 'enterprise' ? (
|
||||
<Link href="mailto:timo@qrmaster.net">
|
||||
<Button variant="outline" className="w-full" size="lg">
|
||||
{t.pricing[plan.key].contact || 'Contact Us'}
|
||||
</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<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>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
'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';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { BillingToggle } from '@/components/ui/BillingToggle';
|
||||
|
||||
interface PricingProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const Pricing: React.FC<PricingProps> = ({ t }) => {
|
||||
const [billingPeriod, setBillingPeriod] = useState<'month' | 'year'>('month');
|
||||
|
||||
const plans = [
|
||||
{
|
||||
key: 'free',
|
||||
popular: false,
|
||||
},
|
||||
{
|
||||
key: 'pro',
|
||||
popular: true,
|
||||
},
|
||||
{
|
||||
key: 'business',
|
||||
popular: false,
|
||||
},
|
||||
{
|
||||
key: 'enterprise',
|
||||
popular: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="pricing" className="py-16">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
|
||||
{t.pricing.title}
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600">{t.pricing.subtitle}</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="flex justify-center mb-8">
|
||||
<BillingToggle value={billingPeriod} onChange={setBillingPeriod} />
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8 max-w-7xl mx-auto">
|
||||
{plans.map((plan, index) => (
|
||||
<motion.div
|
||||
key={plan.key}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="h-full"
|
||||
>
|
||||
<Card
|
||||
className={`h-full flex flex-col ${
|
||||
plan.popular
|
||||
? 'border-primary-500 shadow-xl relative scale-105 z-10'
|
||||
: 'border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all'
|
||||
}`}
|
||||
>
|
||||
{plan.popular && (
|
||||
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2 w-full text-center">
|
||||
<Badge variant="info" className="px-4 py-1.5 shadow-sm">
|
||||
{t.pricing[plan.key].badge}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CardHeader className="text-center pb-8">
|
||||
<CardTitle className="text-2xl mb-4">
|
||||
{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' || plan.key === 'enterprise'
|
||||
? 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' || plan.key === 'enterprise'
|
||||
? t.pricing[plan.key].period
|
||||
: billingPeriod === 'month'
|
||||
? t.pricing[plan.key].period
|
||||
: 'per year'}
|
||||
</span>
|
||||
</div>
|
||||
{billingPeriod === 'year' &&
|
||||
plan.key !== 'free' &&
|
||||
plan.key !== 'enterprise' && (
|
||||
<Badge variant="success" className="mt-2">
|
||||
Save 16%
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-8 flex-1 flex flex-col">
|
||||
<ul className="space-y-3 flex-1">
|
||||
{t.pricing[plan.key].features.map(
|
||||
(feature: string, index: number) => (
|
||||
<li key={index} className="flex items-start space-x-3">
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-gray-700">{feature}</span>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
|
||||
<div className="mt-8 pt-8 border-t border-gray-100">
|
||||
{plan.key === 'enterprise' ? (
|
||||
<Link href="mailto:timo@qrmaster.net">
|
||||
<Button variant="outline" className="w-full" size="lg">
|
||||
{t.pricing[plan.key].contact || 'Contact Us'}
|
||||
</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<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>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,111 +1,111 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Star, CheckCircle, ChevronRight } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import type { Testimonial } from '@/lib/types';
|
||||
|
||||
interface Props {
|
||||
testimonials: Testimonial[];
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
function StarRating({ rating }: { rating: number }) {
|
||||
return (
|
||||
<div className="flex gap-0.5" role="img" aria-label={`${rating} out of 5 stars`}>
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<Star
|
||||
key={i}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
className={`w-4 h-4 ${i < rating ? 'fill-yellow-400 text-yellow-400' : 'fill-gray-200 text-gray-200'}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TestimonialCard({ testimonial }: { testimonial: Testimonial }) {
|
||||
return (
|
||||
<div className="w-[340px] sm:w-[380px] bg-white rounded-2xl shadow-sm border border-gray-100 p-6 flex flex-col h-full">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<StarRating rating={testimonial.rating} />
|
||||
{testimonial.verified && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-green-50 text-green-700 text-xs font-medium rounded-full border border-green-200">
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
Verified
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">"{testimonial.title}"</h3>
|
||||
<p className="text-gray-600 text-sm leading-relaxed flex-grow">{testimonial.content}</p>
|
||||
<div className="border-t border-gray-100 pt-4 mt-4">
|
||||
<span className="font-semibold text-gray-900 text-sm block">{testimonial.author.name}</span>
|
||||
{(testimonial.author.role || testimonial.author.company) && (
|
||||
<span className="text-xs text-gray-600 block">
|
||||
{[testimonial.author.role, testimonial.author.company].filter(Boolean).join(', ')}
|
||||
</span>
|
||||
)}
|
||||
{testimonial.author.location && (
|
||||
<span className="text-xs text-gray-600 block">{testimonial.author.location}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const TestimonialsCarousel: React.FC<Props> = ({
|
||||
testimonials,
|
||||
title = 'What Our Customers Say',
|
||||
subtitle = 'Real experiences from businesses using QR Master',
|
||||
}) => {
|
||||
// Duplicate for seamless loop: when first set exits left, second set is identical → no visible reset
|
||||
const doubled = [...testimonials, ...testimonials];
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-gray-50 overflow-hidden">
|
||||
<style>{`
|
||||
@keyframes marquee {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
.marquee-track {
|
||||
animation: marquee 90s linear infinite;
|
||||
}
|
||||
.marquee-track:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl mb-10">
|
||||
<div className="text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">{title}</h2>
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">{subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Full-width overflow mask */}
|
||||
<div className="w-full overflow-hidden">
|
||||
<div
|
||||
className="marquee-track flex gap-6"
|
||||
style={{ width: 'max-content' }}
|
||||
>
|
||||
{doubled.map((t, idx) => (
|
||||
<TestimonialCard key={`${t.id}-${idx}`} testimonial={t} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-center">
|
||||
<Link
|
||||
href="/testimonials"
|
||||
className="inline-flex items-center text-blue-600 font-semibold hover:text-blue-700 transition-colors text-sm"
|
||||
>
|
||||
See all {testimonials.length} reviews
|
||||
<ChevronRight className="w-4 h-4 ml-1" />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Star, CheckCircle, ChevronRight } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import type { Testimonial } from '@/lib/types';
|
||||
|
||||
interface Props {
|
||||
testimonials: Testimonial[];
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
function StarRating({ rating }: { rating: number }) {
|
||||
return (
|
||||
<div className="flex gap-0.5" role="img" aria-label={`${rating} out of 5 stars`}>
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<Star
|
||||
key={i}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
className={`w-4 h-4 ${i < rating ? 'fill-yellow-400 text-yellow-400' : 'fill-gray-200 text-gray-200'}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TestimonialCard({ testimonial }: { testimonial: Testimonial }) {
|
||||
return (
|
||||
<div className="w-[340px] sm:w-[380px] bg-white rounded-2xl shadow-sm border border-gray-100 p-6 flex flex-col h-full">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<StarRating rating={testimonial.rating} />
|
||||
{testimonial.verified && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-green-50 text-green-700 text-xs font-medium rounded-full border border-green-200">
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
Verified
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">"{testimonial.title}"</h3>
|
||||
<p className="text-gray-600 text-sm leading-relaxed flex-grow">{testimonial.content}</p>
|
||||
<div className="border-t border-gray-100 pt-4 mt-4">
|
||||
<span className="font-semibold text-gray-900 text-sm block">{testimonial.author.name}</span>
|
||||
{(testimonial.author.role || testimonial.author.company) && (
|
||||
<span className="text-xs text-gray-600 block">
|
||||
{[testimonial.author.role, testimonial.author.company].filter(Boolean).join(', ')}
|
||||
</span>
|
||||
)}
|
||||
{testimonial.author.location && (
|
||||
<span className="text-xs text-gray-600 block">{testimonial.author.location}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const TestimonialsCarousel: React.FC<Props> = ({
|
||||
testimonials,
|
||||
title = 'What Our Customers Say',
|
||||
subtitle = 'Real experiences from businesses using QR Master',
|
||||
}) => {
|
||||
// Duplicate for seamless loop: when first set exits left, second set is identical → no visible reset
|
||||
const doubled = [...testimonials, ...testimonials];
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-gray-50 overflow-hidden">
|
||||
<style>{`
|
||||
@keyframes marquee {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
.marquee-track {
|
||||
animation: marquee 90s linear infinite;
|
||||
}
|
||||
.marquee-track:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl mb-10">
|
||||
<div className="text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">{title}</h2>
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">{subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Full-width overflow mask */}
|
||||
<div className="w-full overflow-hidden">
|
||||
<div
|
||||
className="marquee-track flex gap-6"
|
||||
style={{ width: 'max-content' }}
|
||||
>
|
||||
{doubled.map((t, idx) => (
|
||||
<TestimonialCard key={`${t.id}-${idx}`} testimonial={t} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-center">
|
||||
<Link
|
||||
href="/testimonials"
|
||||
className="inline-flex items-center text-blue-600 font-semibold hover:text-blue-700 transition-colors text-sm"
|
||||
>
|
||||
See all {testimonials.length} reviews
|
||||
<ChevronRight className="w-4 h-4 ml-1" />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,47 +1,47 @@
|
||||
export const GOOGLE_OAUTH_STATE_COOKIE_NAME = 'qrmaster_google_oauth_state';
|
||||
export const POST_AUTH_REDIRECT_COOKIE_NAME = 'qrmaster_post_auth_redirect';
|
||||
|
||||
export function sanitizeRedirectPath(value?: string | null): string | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
|
||||
if (!trimmed.startsWith('/') || trimmed.startsWith('//')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (trimmed.startsWith('/api/') || trimmed.startsWith('/login') || trimmed.startsWith('/signup')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export function appendRedirectParam(
|
||||
path: string,
|
||||
redirectPath?: string | null,
|
||||
extraParams?: Record<string, string | null | undefined>
|
||||
): string {
|
||||
const [pathname, existingQuery = ''] = path.split('?');
|
||||
const searchParams = new URLSearchParams(existingQuery);
|
||||
const safeRedirect = sanitizeRedirectPath(redirectPath);
|
||||
|
||||
if (safeRedirect) {
|
||||
searchParams.set('redirect', safeRedirect);
|
||||
}
|
||||
|
||||
Object.entries(extraParams || {}).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
searchParams.set(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
const query = searchParams.toString();
|
||||
return query ? `${pathname}?${query}` : pathname;
|
||||
}
|
||||
|
||||
export function getPostOnboardingDestination(redirectPath?: string | null): string {
|
||||
return sanitizeRedirectPath(redirectPath) || '/dashboard';
|
||||
}
|
||||
export const GOOGLE_OAUTH_STATE_COOKIE_NAME = 'qrmaster_google_oauth_state';
|
||||
export const POST_AUTH_REDIRECT_COOKIE_NAME = 'qrmaster_post_auth_redirect';
|
||||
|
||||
export function sanitizeRedirectPath(value?: string | null): string | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
|
||||
if (!trimmed.startsWith('/') || trimmed.startsWith('//')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (trimmed.startsWith('/api/') || trimmed.startsWith('/login') || trimmed.startsWith('/signup')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export function appendRedirectParam(
|
||||
path: string,
|
||||
redirectPath?: string | null,
|
||||
extraParams?: Record<string, string | null | undefined>
|
||||
): string {
|
||||
const [pathname, existingQuery = ''] = path.split('?');
|
||||
const searchParams = new URLSearchParams(existingQuery);
|
||||
const safeRedirect = sanitizeRedirectPath(redirectPath);
|
||||
|
||||
if (safeRedirect) {
|
||||
searchParams.set('redirect', safeRedirect);
|
||||
}
|
||||
|
||||
Object.entries(extraParams || {}).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
searchParams.set(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
const query = searchParams.toString();
|
||||
return query ? `${pathname}?${query}` : pathname;
|
||||
}
|
||||
|
||||
export function getPostOnboardingDestination(redirectPath?: string | null): string {
|
||||
return sanitizeRedirectPath(redirectPath) || '/dashboard';
|
||||
}
|
||||
|
||||
@@ -1,305 +1,305 @@
|
||||
// Centralized competitor data — single source of truth for all comparison pages.
|
||||
// Update pricing/features here and it propagates to all competitor pages.
|
||||
|
||||
export type FeatureComparison = {
|
||||
feature: string;
|
||||
qrMaster: string | boolean;
|
||||
competitor: string | boolean;
|
||||
note?: string;
|
||||
};
|
||||
|
||||
export type CompetitorProfile = {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
tagline: string;
|
||||
targetAudience: string;
|
||||
positioning: string;
|
||||
pricing: {
|
||||
free: string;
|
||||
starter: string;
|
||||
pro: string;
|
||||
enterprise?: string;
|
||||
};
|
||||
strengths: string[];
|
||||
weaknesses: string[];
|
||||
bestFor: string[];
|
||||
notIdealFor: string[];
|
||||
commonComplaints: string[];
|
||||
migrationNotes: string;
|
||||
features: FeatureComparison[];
|
||||
};
|
||||
|
||||
export const qrMasterPricing = {
|
||||
free: '€0 — 3 active dynamic QR codes, unlimited static, basic tracking',
|
||||
pro: '€9/month (€90/year) — 50 dynamic QR codes, advanced analytics, custom branding',
|
||||
business: '€29/month (€290/year) — 500 dynamic QR codes, bulk creation up to 1,000, priority support',
|
||||
};
|
||||
|
||||
export const competitors: Record<string, CompetitorProfile> = {
|
||||
'qr-code-generator': {
|
||||
id: 'qr-code-generator',
|
||||
name: 'QR Code Generator (qr-code-generator.com)',
|
||||
url: 'https://www.qr-code-generator.com',
|
||||
tagline: 'Popular generator, deceptive free tier for dynamic codes',
|
||||
targetAudience: 'General public, small businesses, first-time QR users',
|
||||
positioning: 'High-volume consumer QR generator marketed as easy and free',
|
||||
pricing: {
|
||||
free: 'Static QR only — dynamic QR codes deactivated after ~14-day trial',
|
||||
starter: '~€8.99/month (billed annually) for basic dynamic QR',
|
||||
pro: '~€24.99/month (billed annually) for analytics + more codes',
|
||||
},
|
||||
strengths: [
|
||||
'High brand recognition — many people land here first',
|
||||
'Simple interface for first-time users',
|
||||
'Wide variety of QR types supported',
|
||||
'Large template library for QR styling',
|
||||
],
|
||||
weaknesses: [
|
||||
'Dynamic QR codes are free only for ~14 days, then deactivated without warning',
|
||||
'Users who already printed materials are forced into expensive annual contracts',
|
||||
'Pricing is unclear upfront — trial period is not prominently disclosed',
|
||||
'Analytics require paid plan even for basic scan counts',
|
||||
'No bulk creation capability',
|
||||
'US-based servers — GDPR compliance not a priority',
|
||||
],
|
||||
bestFor: [
|
||||
'One-off static QR code generation with no analytics needed',
|
||||
'Users who need a quick, styled QR for a design project',
|
||||
],
|
||||
notIdealFor: [
|
||||
'Any use case involving dynamic QR codes that will go on printed materials',
|
||||
'Businesses that need GDPR-compliant scan tracking',
|
||||
'Teams that need bulk QR creation',
|
||||
],
|
||||
commonComplaints: [
|
||||
'"I printed 500 flyers and my QR code stopped working after 2 weeks"',
|
||||
'"Felt forced into an annual subscription I didn\'t understand I was signing up for"',
|
||||
'"The free dynamic QR is a trap — it gets deactivated right when you\'ve already distributed materials"',
|
||||
'"No way to export or migrate codes if you want to leave"',
|
||||
],
|
||||
migrationNotes:
|
||||
'Static QR codes cannot be migrated — the destination is baked into the image. Dynamic QR codes need to be re-created. In QR Master, create new dynamic QR codes pointing to your same destinations. Then update your printed materials or digital placements to use the new codes. Static codes from QR Code Generator remain permanently valid even after you stop paying.',
|
||||
features: [
|
||||
{ feature: 'Free static QR codes', qrMaster: true, competitor: true },
|
||||
{
|
||||
feature: 'Free dynamic QR codes (permanent)',
|
||||
qrMaster: '3 always-free',
|
||||
competitor: '~14-day trial then deactivated',
|
||||
note: 'QR Code Generator deactivates dynamic codes after trial — even if already printed',
|
||||
},
|
||||
{ feature: 'Transparent pricing upfront', qrMaster: true, competitor: false },
|
||||
{ feature: 'Scan analytics', qrMaster: 'All plans', competitor: 'Paid only' },
|
||||
{ feature: 'GDPR-compliant analytics (hashed IPs)', qrMaster: true, competitor: false },
|
||||
{ feature: 'Bulk QR creation', qrMaster: 'Business plan (up to 1,000)', competitor: false },
|
||||
{ feature: 'Custom branding / logo in QR', qrMaster: 'Pro+', competitor: 'Paid plans' },
|
||||
{ feature: 'No forced annual subscription', qrMaster: true, competitor: false },
|
||||
{
|
||||
feature: 'Pro pricing',
|
||||
qrMaster: '€9/month',
|
||||
competitor: '~€24.99/month for comparable features',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
flowcode: {
|
||||
id: 'flowcode',
|
||||
name: 'Flowcode',
|
||||
url: 'https://www.flowcode.com',
|
||||
tagline: 'Design-forward QR tool with forced branding and scan-hijacking interstitials',
|
||||
targetAudience: 'Brands, marketers, enterprise design teams',
|
||||
positioning: 'Premium-branded QR code platform emphasizing aesthetics and design',
|
||||
pricing: {
|
||||
free: 'Free — limited codes, Flowcode branding on every QR, interstitial pages on scans',
|
||||
starter: '~$15/month for basic white-label',
|
||||
pro: '~$49/month for team features and full branding removal',
|
||||
enterprise: 'Custom pricing for large teams',
|
||||
},
|
||||
strengths: [
|
||||
'Exceptional QR design options — rounded, branded styles that stand out',
|
||||
'Strong brand recognition in the US market',
|
||||
'Good enterprise team management features',
|
||||
'Analytics dashboard is well-designed',
|
||||
],
|
||||
weaknesses: [
|
||||
'Free tier forces Flowcode logo onto every QR code — your brand takes second place',
|
||||
'Interstitial pages on free tier send scanners through a Flowcode-branded page, stealing attention and analytics',
|
||||
'White-label and clean QR design requires jumping to expensive paid plans',
|
||||
'No bulk QR creation even on paid plans',
|
||||
'US company — GDPR compliance not built in',
|
||||
'Pricing escalates quickly for teams',
|
||||
],
|
||||
bestFor: [
|
||||
'Enterprise brands with budget for premium QR aesthetics and full white-label',
|
||||
'US-based companies prioritizing design over data privacy compliance',
|
||||
'Large teams managing QR codes across multiple campaigns',
|
||||
],
|
||||
notIdealFor: [
|
||||
'Anyone who wants clean QR codes without Flowcode branding on a budget',
|
||||
'EU businesses that need GDPR-compliant tracking',
|
||||
'Marketing teams that need bulk code creation',
|
||||
'SMBs and freelancers who need professional QR without enterprise pricing',
|
||||
],
|
||||
commonComplaints: [
|
||||
'"The free QR codes have Flowcode\'s logo on them, not mine"',
|
||||
'"When customers scan my QR, they see a Flowcode page first — I lose control of the experience"',
|
||||
'"Had to upgrade to $49/month just to get a clean QR code without their branding"',
|
||||
'"No way to create QR codes in bulk even on paid plans"',
|
||||
],
|
||||
migrationNotes:
|
||||
'Flowcode does not provide a bulk export tool. Each dynamic QR code needs to be re-created manually in QR Master. Since Flowcode uses its own redirect infrastructure, your printed QR codes pointing to Flowcode domains will stop working if you cancel — plan migration before canceling. In QR Master, new dynamic QR codes are generated immediately and redirect infrastructure is included in all plans.',
|
||||
features: [
|
||||
{ feature: 'Free dynamic QR codes', qrMaster: '3 always-free', competitor: 'Limited, with branding' },
|
||||
{
|
||||
feature: 'No Flowcode/third-party branding on QR',
|
||||
qrMaster: true,
|
||||
competitor: 'Paid plans only',
|
||||
note: 'Free Flowcode QR codes include Flowcode logo and forced design elements',
|
||||
},
|
||||
{
|
||||
feature: 'No branded interstitial page on scan',
|
||||
qrMaster: 'Fast pass-through — no Flowcode-style marketing page',
|
||||
competitor: 'Paid plans only',
|
||||
note: 'Flowcode free tier shows a branded splash page; QR Master redirects through qrmaster.net without any branded interstitial',
|
||||
},
|
||||
{ feature: 'GDPR-compliant analytics (hashed IPs)', qrMaster: true, competitor: false },
|
||||
{ feature: 'Bulk QR creation', qrMaster: 'Business plan (up to 1,000)', competitor: false },
|
||||
{ feature: 'Custom branding (your logo/colors)', qrMaster: 'Pro+', competitor: 'Paid plans' },
|
||||
{ feature: 'Scan analytics', qrMaster: 'All plans', competitor: 'Paid plans' },
|
||||
{
|
||||
feature: 'Pro pricing',
|
||||
qrMaster: '€9/month',
|
||||
competitor: '~$49/month for comparable white-label',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
beaconstac: {
|
||||
id: 'beaconstac',
|
||||
name: 'Uniqode (formerly Beaconstac)',
|
||||
url: 'https://www.uniqode.com',
|
||||
tagline: 'Enterprise QR platform — excellent for large orgs, overkill for SMBs',
|
||||
targetAudience: 'Enterprise companies, large agencies, compliance-heavy industries',
|
||||
positioning: 'Enterprise-grade QR code platform with SOC2 compliance, SSO, and deep API access',
|
||||
pricing: {
|
||||
free: 'No free plan — paid-only from the start',
|
||||
starter: '~$5/month (very limited features)',
|
||||
pro: '~$49–99/month for meaningful team and analytics features',
|
||||
enterprise: 'Custom pricing for SOC2, SSO, advanced API',
|
||||
},
|
||||
strengths: [
|
||||
'SOC2 Type II certified — serious enterprise security credentials',
|
||||
'Deep API access for custom integrations',
|
||||
'SSO / SAML support for large organizations',
|
||||
'White-label solution for agencies',
|
||||
'Advanced team management and permissions',
|
||||
'Long track record and brand trust',
|
||||
],
|
||||
weaknesses: [
|
||||
'No free plan — must pay from day one even for basic use',
|
||||
'Meaningful features only unlock at $49–99/month+',
|
||||
'Interface is complex for users who just need to manage 50 dynamic QR codes',
|
||||
'US company — GDPR compliance requires additional configuration',
|
||||
'Bulk creation and API access locked behind higher enterprise tiers',
|
||||
'Setup and onboarding takes longer than simpler tools',
|
||||
],
|
||||
bestFor: [
|
||||
'Enterprises with compliance requirements (SOC2, SAML/SSO)',
|
||||
'Large agencies managing QR codes at scale across multiple clients',
|
||||
'Companies with dedicated IT teams who can leverage the API',
|
||||
'Organizations where a QR vendor going through formal procurement/security review',
|
||||
],
|
||||
notIdealFor: [
|
||||
'SMBs and freelancers who just need reliable dynamic QR + analytics',
|
||||
'EU businesses needing out-of-the-box GDPR-compliant tracking',
|
||||
'Marketing teams who want to start for free and upgrade later',
|
||||
'Anyone who finds the interface overwhelming for a straightforward use case',
|
||||
],
|
||||
commonComplaints: [
|
||||
'"The $5 plan is basically useless — you need to spend $49+ to get anything meaningful"',
|
||||
'"Way too complex for what I need — I just want to manage my 30 QR codes"',
|
||||
'"GDPR compliance required a lot of manual configuration"',
|
||||
'"Expensive and hard to justify for a small marketing team"',
|
||||
],
|
||||
migrationNotes:
|
||||
'Uniqode/Beaconstac allows CSV export of your QR code data. You can download your QR code list and re-create dynamic codes in QR Master. Static QR codes are permanently tied to their destination — if you generated them as static codes, they remain valid forever. For dynamic codes, update your redirect destinations in QR Master and replace any printed materials that point to Uniqode redirect URLs.',
|
||||
features: [
|
||||
{ feature: 'Free plan available', qrMaster: true, competitor: false },
|
||||
{ feature: 'Pricing entry point', qrMaster: '€0 (free tier)', competitor: '~$5/month (limited)' },
|
||||
{
|
||||
feature: 'Meaningful features at base paid plan',
|
||||
qrMaster: '€9/month — analytics, branding, 50 dynamic QRs',
|
||||
competitor: '~$49/month for real team + analytics features',
|
||||
},
|
||||
{ feature: 'GDPR-compliant analytics (hashed IPs, out of box)', qrMaster: true, competitor: false },
|
||||
{ feature: 'Bulk QR creation', qrMaster: 'Business plan (up to 1,000)', competitor: 'Enterprise tier' },
|
||||
{ feature: 'SOC2 certification', qrMaster: false, competitor: true, note: 'Enterprise requirement — most SMBs do not need this' },
|
||||
{ feature: 'SSO / SAML', qrMaster: false, competitor: true, note: 'Enterprise requirement' },
|
||||
{ feature: 'Simple onboarding for small teams', qrMaster: true, competitor: false },
|
||||
],
|
||||
},
|
||||
|
||||
bitly: {
|
||||
id: 'bitly',
|
||||
name: 'Bitly',
|
||||
url: 'https://bitly.com',
|
||||
tagline: 'URL shortener with QR codes as a side feature — only 2 QR codes at $10/month',
|
||||
targetAudience: 'Link management users, marketing teams using Bitly for short links',
|
||||
positioning: 'Link management platform that added QR codes as a secondary feature',
|
||||
pricing: {
|
||||
free: 'Free — 1 QR code only',
|
||||
starter: '~$10/month (Core) — "unlimited scans" but only 2 QR codes total',
|
||||
pro: '~$29–35/month — more QR codes but still tightly capped; price jumps fast once you need more',
|
||||
enterprise: 'Custom — needed for meaningful QR code volume',
|
||||
},
|
||||
strengths: [
|
||||
'Very strong brand recognition — many people already use Bitly for link shortening',
|
||||
'Combines link shortening and QR codes in one tool if you need both',
|
||||
'Clean analytics dashboard for link tracking',
|
||||
'Reliable redirect infrastructure',
|
||||
'Good integrations with marketing tools',
|
||||
],
|
||||
weaknesses: [
|
||||
'QR codes are a secondary feature — core product is link shortening',
|
||||
'Core plan (~$10/month) allows "unlimited scans" but only 2 QR codes total — useless for real campaigns',
|
||||
'Need more than 2 QR codes? Pricing jumps disproportionately fast',
|
||||
'Free plan allows only 1 QR code',
|
||||
'No bulk QR creation at any plan level',
|
||||
'No GDPR-specific privacy controls for analytics',
|
||||
'Not built for QR-specific workflows (restaurant menus, product packaging, etc.)',
|
||||
],
|
||||
bestFor: [
|
||||
'Teams already using Bitly for link management who need 1–2 QR codes with no bulk requirements',
|
||||
'Use cases where a QR code is a minor side channel, not a primary campaign element',
|
||||
'Link tracking that happens to include a QR code as one of many channels',
|
||||
],
|
||||
notIdealFor: [
|
||||
'Anyone who needs more than 2 QR codes without paying steep per-step plan upgrades',
|
||||
'Teams needing more than a handful of QR codes',
|
||||
'EU businesses needing GDPR-compliant scan tracking',
|
||||
'Anyone who needs bulk QR creation',
|
||||
'QR code-first workflows like restaurant menus, product packaging, or event materials',
|
||||
],
|
||||
commonComplaints: [
|
||||
'"$10/month and I only get 2 QR codes — that\'s basically unusable for anything real"',
|
||||
'"QR features feel like an afterthought — the UI is built around links, not QR workflows"',
|
||||
'"The free plan gives you exactly 1 QR code — useless for real use"',
|
||||
'"No bulk creation — had to manually create each code one by one"',
|
||||
],
|
||||
migrationNotes:
|
||||
'Bitly short links and QR codes are tied to Bitly\'s redirect infrastructure. If you cancel Bitly, your QR codes that point to bit.ly URLs will stop working. Export your link data from Bitly\'s dashboard before canceling. In QR Master, create new dynamic QR codes pointing directly to your destination URLs — no Bitly redirect layer in between. Replace printed or distributed QR codes with the new QR Master codes.',
|
||||
features: [
|
||||
{ feature: 'Free QR codes', qrMaster: '3 dynamic + unlimited static', competitor: '1 QR code only' },
|
||||
{ feature: 'QR codes on base paid plan (~$10/month)', qrMaster: '50 dynamic QRs (Pro €9/month)', competitor: '2 QR codes total', note: 'Bitly Core plan markets "unlimited scans" but caps you at 2 QR codes' },
|
||||
{ feature: 'Purpose-built for QR management', qrMaster: true, competitor: false },
|
||||
{ feature: 'Bulk QR creation', qrMaster: 'Business plan (up to 1,000)', competitor: false },
|
||||
{ feature: 'GDPR-compliant analytics', qrMaster: true, competitor: false },
|
||||
{ feature: 'QR codes for restaurant menus, packaging, events', qrMaster: true, competitor: 'Possible but not designed for it' },
|
||||
{
|
||||
feature: 'Pro pricing',
|
||||
qrMaster: '€9/month — 50 dynamic QRs, full analytics',
|
||||
competitor: '~$10/month — 2 QR codes only',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
// Centralized competitor data — single source of truth for all comparison pages.
|
||||
// Update pricing/features here and it propagates to all competitor pages.
|
||||
|
||||
export type FeatureComparison = {
|
||||
feature: string;
|
||||
qrMaster: string | boolean;
|
||||
competitor: string | boolean;
|
||||
note?: string;
|
||||
};
|
||||
|
||||
export type CompetitorProfile = {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
tagline: string;
|
||||
targetAudience: string;
|
||||
positioning: string;
|
||||
pricing: {
|
||||
free: string;
|
||||
starter: string;
|
||||
pro: string;
|
||||
enterprise?: string;
|
||||
};
|
||||
strengths: string[];
|
||||
weaknesses: string[];
|
||||
bestFor: string[];
|
||||
notIdealFor: string[];
|
||||
commonComplaints: string[];
|
||||
migrationNotes: string;
|
||||
features: FeatureComparison[];
|
||||
};
|
||||
|
||||
export const qrMasterPricing = {
|
||||
free: '€0 — 3 active dynamic QR codes, unlimited static, basic tracking',
|
||||
pro: '€9/month (€90/year) — 50 dynamic QR codes, advanced analytics, custom branding',
|
||||
business: '€29/month (€290/year) — 500 dynamic QR codes, bulk creation up to 1,000, priority support',
|
||||
};
|
||||
|
||||
export const competitors: Record<string, CompetitorProfile> = {
|
||||
'qr-code-generator': {
|
||||
id: 'qr-code-generator',
|
||||
name: 'QR Code Generator (qr-code-generator.com)',
|
||||
url: 'https://www.qr-code-generator.com',
|
||||
tagline: 'Popular generator, deceptive free tier for dynamic codes',
|
||||
targetAudience: 'General public, small businesses, first-time QR users',
|
||||
positioning: 'High-volume consumer QR generator marketed as easy and free',
|
||||
pricing: {
|
||||
free: 'Static QR only — dynamic QR codes deactivated after ~14-day trial',
|
||||
starter: '~€8.99/month (billed annually) for basic dynamic QR',
|
||||
pro: '~€24.99/month (billed annually) for analytics + more codes',
|
||||
},
|
||||
strengths: [
|
||||
'High brand recognition — many people land here first',
|
||||
'Simple interface for first-time users',
|
||||
'Wide variety of QR types supported',
|
||||
'Large template library for QR styling',
|
||||
],
|
||||
weaknesses: [
|
||||
'Dynamic QR codes are free only for ~14 days, then deactivated without warning',
|
||||
'Users who already printed materials are forced into expensive annual contracts',
|
||||
'Pricing is unclear upfront — trial period is not prominently disclosed',
|
||||
'Analytics require paid plan even for basic scan counts',
|
||||
'No bulk creation capability',
|
||||
'US-based servers — GDPR compliance not a priority',
|
||||
],
|
||||
bestFor: [
|
||||
'One-off static QR code generation with no analytics needed',
|
||||
'Users who need a quick, styled QR for a design project',
|
||||
],
|
||||
notIdealFor: [
|
||||
'Any use case involving dynamic QR codes that will go on printed materials',
|
||||
'Businesses that need GDPR-compliant scan tracking',
|
||||
'Teams that need bulk QR creation',
|
||||
],
|
||||
commonComplaints: [
|
||||
'"I printed 500 flyers and my QR code stopped working after 2 weeks"',
|
||||
'"Felt forced into an annual subscription I didn\'t understand I was signing up for"',
|
||||
'"The free dynamic QR is a trap — it gets deactivated right when you\'ve already distributed materials"',
|
||||
'"No way to export or migrate codes if you want to leave"',
|
||||
],
|
||||
migrationNotes:
|
||||
'Static QR codes cannot be migrated — the destination is baked into the image. Dynamic QR codes need to be re-created. In QR Master, create new dynamic QR codes pointing to your same destinations. Then update your printed materials or digital placements to use the new codes. Static codes from QR Code Generator remain permanently valid even after you stop paying.',
|
||||
features: [
|
||||
{ feature: 'Free static QR codes', qrMaster: true, competitor: true },
|
||||
{
|
||||
feature: 'Free dynamic QR codes (permanent)',
|
||||
qrMaster: '3 always-free',
|
||||
competitor: '~14-day trial then deactivated',
|
||||
note: 'QR Code Generator deactivates dynamic codes after trial — even if already printed',
|
||||
},
|
||||
{ feature: 'Transparent pricing upfront', qrMaster: true, competitor: false },
|
||||
{ feature: 'Scan analytics', qrMaster: 'All plans', competitor: 'Paid only' },
|
||||
{ feature: 'GDPR-compliant analytics (hashed IPs)', qrMaster: true, competitor: false },
|
||||
{ feature: 'Bulk QR creation', qrMaster: 'Business plan (up to 1,000)', competitor: false },
|
||||
{ feature: 'Custom branding / logo in QR', qrMaster: 'Pro+', competitor: 'Paid plans' },
|
||||
{ feature: 'No forced annual subscription', qrMaster: true, competitor: false },
|
||||
{
|
||||
feature: 'Pro pricing',
|
||||
qrMaster: '€9/month',
|
||||
competitor: '~€24.99/month for comparable features',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
flowcode: {
|
||||
id: 'flowcode',
|
||||
name: 'Flowcode',
|
||||
url: 'https://www.flowcode.com',
|
||||
tagline: 'Design-forward QR tool with forced branding and scan-hijacking interstitials',
|
||||
targetAudience: 'Brands, marketers, enterprise design teams',
|
||||
positioning: 'Premium-branded QR code platform emphasizing aesthetics and design',
|
||||
pricing: {
|
||||
free: 'Free — limited codes, Flowcode branding on every QR, interstitial pages on scans',
|
||||
starter: '~$15/month for basic white-label',
|
||||
pro: '~$49/month for team features and full branding removal',
|
||||
enterprise: 'Custom pricing for large teams',
|
||||
},
|
||||
strengths: [
|
||||
'Exceptional QR design options — rounded, branded styles that stand out',
|
||||
'Strong brand recognition in the US market',
|
||||
'Good enterprise team management features',
|
||||
'Analytics dashboard is well-designed',
|
||||
],
|
||||
weaknesses: [
|
||||
'Free tier forces Flowcode logo onto every QR code — your brand takes second place',
|
||||
'Interstitial pages on free tier send scanners through a Flowcode-branded page, stealing attention and analytics',
|
||||
'White-label and clean QR design requires jumping to expensive paid plans',
|
||||
'No bulk QR creation even on paid plans',
|
||||
'US company — GDPR compliance not built in',
|
||||
'Pricing escalates quickly for teams',
|
||||
],
|
||||
bestFor: [
|
||||
'Enterprise brands with budget for premium QR aesthetics and full white-label',
|
||||
'US-based companies prioritizing design over data privacy compliance',
|
||||
'Large teams managing QR codes across multiple campaigns',
|
||||
],
|
||||
notIdealFor: [
|
||||
'Anyone who wants clean QR codes without Flowcode branding on a budget',
|
||||
'EU businesses that need GDPR-compliant tracking',
|
||||
'Marketing teams that need bulk code creation',
|
||||
'SMBs and freelancers who need professional QR without enterprise pricing',
|
||||
],
|
||||
commonComplaints: [
|
||||
'"The free QR codes have Flowcode\'s logo on them, not mine"',
|
||||
'"When customers scan my QR, they see a Flowcode page first — I lose control of the experience"',
|
||||
'"Had to upgrade to $49/month just to get a clean QR code without their branding"',
|
||||
'"No way to create QR codes in bulk even on paid plans"',
|
||||
],
|
||||
migrationNotes:
|
||||
'Flowcode does not provide a bulk export tool. Each dynamic QR code needs to be re-created manually in QR Master. Since Flowcode uses its own redirect infrastructure, your printed QR codes pointing to Flowcode domains will stop working if you cancel — plan migration before canceling. In QR Master, new dynamic QR codes are generated immediately and redirect infrastructure is included in all plans.',
|
||||
features: [
|
||||
{ feature: 'Free dynamic QR codes', qrMaster: '3 always-free', competitor: 'Limited, with branding' },
|
||||
{
|
||||
feature: 'No Flowcode/third-party branding on QR',
|
||||
qrMaster: true,
|
||||
competitor: 'Paid plans only',
|
||||
note: 'Free Flowcode QR codes include Flowcode logo and forced design elements',
|
||||
},
|
||||
{
|
||||
feature: 'No branded interstitial page on scan',
|
||||
qrMaster: 'Fast pass-through — no Flowcode-style marketing page',
|
||||
competitor: 'Paid plans only',
|
||||
note: 'Flowcode free tier shows a branded splash page; QR Master redirects through qrmaster.net without any branded interstitial',
|
||||
},
|
||||
{ feature: 'GDPR-compliant analytics (hashed IPs)', qrMaster: true, competitor: false },
|
||||
{ feature: 'Bulk QR creation', qrMaster: 'Business plan (up to 1,000)', competitor: false },
|
||||
{ feature: 'Custom branding (your logo/colors)', qrMaster: 'Pro+', competitor: 'Paid plans' },
|
||||
{ feature: 'Scan analytics', qrMaster: 'All plans', competitor: 'Paid plans' },
|
||||
{
|
||||
feature: 'Pro pricing',
|
||||
qrMaster: '€9/month',
|
||||
competitor: '~$49/month for comparable white-label',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
beaconstac: {
|
||||
id: 'beaconstac',
|
||||
name: 'Uniqode (formerly Beaconstac)',
|
||||
url: 'https://www.uniqode.com',
|
||||
tagline: 'Enterprise QR platform — excellent for large orgs, overkill for SMBs',
|
||||
targetAudience: 'Enterprise companies, large agencies, compliance-heavy industries',
|
||||
positioning: 'Enterprise-grade QR code platform with SOC2 compliance, SSO, and deep API access',
|
||||
pricing: {
|
||||
free: 'No free plan — paid-only from the start',
|
||||
starter: '~$5/month (very limited features)',
|
||||
pro: '~$49–99/month for meaningful team and analytics features',
|
||||
enterprise: 'Custom pricing for SOC2, SSO, advanced API',
|
||||
},
|
||||
strengths: [
|
||||
'SOC2 Type II certified — serious enterprise security credentials',
|
||||
'Deep API access for custom integrations',
|
||||
'SSO / SAML support for large organizations',
|
||||
'White-label solution for agencies',
|
||||
'Advanced team management and permissions',
|
||||
'Long track record and brand trust',
|
||||
],
|
||||
weaknesses: [
|
||||
'No free plan — must pay from day one even for basic use',
|
||||
'Meaningful features only unlock at $49–99/month+',
|
||||
'Interface is complex for users who just need to manage 50 dynamic QR codes',
|
||||
'US company — GDPR compliance requires additional configuration',
|
||||
'Bulk creation and API access locked behind higher enterprise tiers',
|
||||
'Setup and onboarding takes longer than simpler tools',
|
||||
],
|
||||
bestFor: [
|
||||
'Enterprises with compliance requirements (SOC2, SAML/SSO)',
|
||||
'Large agencies managing QR codes at scale across multiple clients',
|
||||
'Companies with dedicated IT teams who can leverage the API',
|
||||
'Organizations where a QR vendor going through formal procurement/security review',
|
||||
],
|
||||
notIdealFor: [
|
||||
'SMBs and freelancers who just need reliable dynamic QR + analytics',
|
||||
'EU businesses needing out-of-the-box GDPR-compliant tracking',
|
||||
'Marketing teams who want to start for free and upgrade later',
|
||||
'Anyone who finds the interface overwhelming for a straightforward use case',
|
||||
],
|
||||
commonComplaints: [
|
||||
'"The $5 plan is basically useless — you need to spend $49+ to get anything meaningful"',
|
||||
'"Way too complex for what I need — I just want to manage my 30 QR codes"',
|
||||
'"GDPR compliance required a lot of manual configuration"',
|
||||
'"Expensive and hard to justify for a small marketing team"',
|
||||
],
|
||||
migrationNotes:
|
||||
'Uniqode/Beaconstac allows CSV export of your QR code data. You can download your QR code list and re-create dynamic codes in QR Master. Static QR codes are permanently tied to their destination — if you generated them as static codes, they remain valid forever. For dynamic codes, update your redirect destinations in QR Master and replace any printed materials that point to Uniqode redirect URLs.',
|
||||
features: [
|
||||
{ feature: 'Free plan available', qrMaster: true, competitor: false },
|
||||
{ feature: 'Pricing entry point', qrMaster: '€0 (free tier)', competitor: '~$5/month (limited)' },
|
||||
{
|
||||
feature: 'Meaningful features at base paid plan',
|
||||
qrMaster: '€9/month — analytics, branding, 50 dynamic QRs',
|
||||
competitor: '~$49/month for real team + analytics features',
|
||||
},
|
||||
{ feature: 'GDPR-compliant analytics (hashed IPs, out of box)', qrMaster: true, competitor: false },
|
||||
{ feature: 'Bulk QR creation', qrMaster: 'Business plan (up to 1,000)', competitor: 'Enterprise tier' },
|
||||
{ feature: 'SOC2 certification', qrMaster: false, competitor: true, note: 'Enterprise requirement — most SMBs do not need this' },
|
||||
{ feature: 'SSO / SAML', qrMaster: false, competitor: true, note: 'Enterprise requirement' },
|
||||
{ feature: 'Simple onboarding for small teams', qrMaster: true, competitor: false },
|
||||
],
|
||||
},
|
||||
|
||||
bitly: {
|
||||
id: 'bitly',
|
||||
name: 'Bitly',
|
||||
url: 'https://bitly.com',
|
||||
tagline: 'URL shortener with QR codes as a side feature — only 2 QR codes at $10/month',
|
||||
targetAudience: 'Link management users, marketing teams using Bitly for short links',
|
||||
positioning: 'Link management platform that added QR codes as a secondary feature',
|
||||
pricing: {
|
||||
free: 'Free — 1 QR code only',
|
||||
starter: '~$10/month (Core) — "unlimited scans" but only 2 QR codes total',
|
||||
pro: '~$29–35/month — more QR codes but still tightly capped; price jumps fast once you need more',
|
||||
enterprise: 'Custom — needed for meaningful QR code volume',
|
||||
},
|
||||
strengths: [
|
||||
'Very strong brand recognition — many people already use Bitly for link shortening',
|
||||
'Combines link shortening and QR codes in one tool if you need both',
|
||||
'Clean analytics dashboard for link tracking',
|
||||
'Reliable redirect infrastructure',
|
||||
'Good integrations with marketing tools',
|
||||
],
|
||||
weaknesses: [
|
||||
'QR codes are a secondary feature — core product is link shortening',
|
||||
'Core plan (~$10/month) allows "unlimited scans" but only 2 QR codes total — useless for real campaigns',
|
||||
'Need more than 2 QR codes? Pricing jumps disproportionately fast',
|
||||
'Free plan allows only 1 QR code',
|
||||
'No bulk QR creation at any plan level',
|
||||
'No GDPR-specific privacy controls for analytics',
|
||||
'Not built for QR-specific workflows (restaurant menus, product packaging, etc.)',
|
||||
],
|
||||
bestFor: [
|
||||
'Teams already using Bitly for link management who need 1–2 QR codes with no bulk requirements',
|
||||
'Use cases where a QR code is a minor side channel, not a primary campaign element',
|
||||
'Link tracking that happens to include a QR code as one of many channels',
|
||||
],
|
||||
notIdealFor: [
|
||||
'Anyone who needs more than 2 QR codes without paying steep per-step plan upgrades',
|
||||
'Teams needing more than a handful of QR codes',
|
||||
'EU businesses needing GDPR-compliant scan tracking',
|
||||
'Anyone who needs bulk QR creation',
|
||||
'QR code-first workflows like restaurant menus, product packaging, or event materials',
|
||||
],
|
||||
commonComplaints: [
|
||||
'"$10/month and I only get 2 QR codes — that\'s basically unusable for anything real"',
|
||||
'"QR features feel like an afterthought — the UI is built around links, not QR workflows"',
|
||||
'"The free plan gives you exactly 1 QR code — useless for real use"',
|
||||
'"No bulk creation — had to manually create each code one by one"',
|
||||
],
|
||||
migrationNotes:
|
||||
'Bitly short links and QR codes are tied to Bitly\'s redirect infrastructure. If you cancel Bitly, your QR codes that point to bit.ly URLs will stop working. Export your link data from Bitly\'s dashboard before canceling. In QR Master, create new dynamic QR codes pointing directly to your destination URLs — no Bitly redirect layer in between. Replace printed or distributed QR codes with the new QR Master codes.',
|
||||
features: [
|
||||
{ feature: 'Free QR codes', qrMaster: '3 dynamic + unlimited static', competitor: '1 QR code only' },
|
||||
{ feature: 'QR codes on base paid plan (~$10/month)', qrMaster: '50 dynamic QRs (Pro €9/month)', competitor: '2 QR codes total', note: 'Bitly Core plan markets "unlimited scans" but caps you at 2 QR codes' },
|
||||
{ feature: 'Purpose-built for QR management', qrMaster: true, competitor: false },
|
||||
{ feature: 'Bulk QR creation', qrMaster: 'Business plan (up to 1,000)', competitor: false },
|
||||
{ feature: 'GDPR-compliant analytics', qrMaster: true, competitor: false },
|
||||
{ feature: 'QR codes for restaurant menus, packaging, events', qrMaster: true, competitor: 'Possible but not designed for it' },
|
||||
{
|
||||
feature: 'Pro pricing',
|
||||
qrMaster: '€9/month — 50 dynamic QRs, full analytics',
|
||||
competitor: '~$10/month — 2 QR codes only',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,68 +1,68 @@
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
const BASE_URL = 'https://graph.facebook.com/v21.0';
|
||||
const PIXEL_ID = process.env.META_PIXEL_ID;
|
||||
const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN;
|
||||
|
||||
function hashValue(value: string): string {
|
||||
return crypto.createHash('sha256').update(value.trim().toLowerCase()).digest('hex');
|
||||
}
|
||||
|
||||
interface UserData {
|
||||
email?: string;
|
||||
ip?: string;
|
||||
userAgent?: string;
|
||||
fbc?: string;
|
||||
fbp?: string;
|
||||
}
|
||||
|
||||
interface ConversionEvent {
|
||||
eventName: 'CompleteRegistration' | 'Purchase' | 'ViewContent' | 'Lead' | 'InitiateCheckout';
|
||||
eventTime?: number;
|
||||
eventSourceUrl?: string;
|
||||
userData: UserData;
|
||||
customData?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export async function sendConversionEvent(event: ConversionEvent): Promise<void> {
|
||||
if (!PIXEL_ID || !ACCESS_TOKEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hashedUserData: Record<string, string> = {};
|
||||
if (event.userData.email) hashedUserData.em = hashValue(event.userData.email);
|
||||
if (event.userData.ip) hashedUserData.client_ip_address = event.userData.ip;
|
||||
if (event.userData.userAgent) hashedUserData.client_user_agent = event.userData.userAgent;
|
||||
if (event.userData.fbc) hashedUserData.fbc = event.userData.fbc;
|
||||
if (event.userData.fbp) hashedUserData.fbp = event.userData.fbp;
|
||||
|
||||
const payload = {
|
||||
data: [
|
||||
{
|
||||
event_name: event.eventName,
|
||||
event_time: event.eventTime ?? Math.floor(Date.now() / 1000),
|
||||
event_source_url: event.eventSourceUrl ?? process.env.NEXT_PUBLIC_APP_URL,
|
||||
action_source: 'website',
|
||||
user_data: hashedUserData,
|
||||
custom_data: event.customData ?? {},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/${PIXEL_ID}/events?access_token=${ACCESS_TOKEN}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
console.error('[Meta CAPI] Error:', err);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Meta CAPI] Network error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export { hashValue };
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
const BASE_URL = 'https://graph.facebook.com/v21.0';
|
||||
const PIXEL_ID = process.env.META_PIXEL_ID;
|
||||
const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN;
|
||||
|
||||
function hashValue(value: string): string {
|
||||
return crypto.createHash('sha256').update(value.trim().toLowerCase()).digest('hex');
|
||||
}
|
||||
|
||||
interface UserData {
|
||||
email?: string;
|
||||
ip?: string;
|
||||
userAgent?: string;
|
||||
fbc?: string;
|
||||
fbp?: string;
|
||||
}
|
||||
|
||||
interface ConversionEvent {
|
||||
eventName: 'CompleteRegistration' | 'Purchase' | 'ViewContent' | 'Lead' | 'InitiateCheckout';
|
||||
eventTime?: number;
|
||||
eventSourceUrl?: string;
|
||||
userData: UserData;
|
||||
customData?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export async function sendConversionEvent(event: ConversionEvent): Promise<void> {
|
||||
if (!PIXEL_ID || !ACCESS_TOKEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hashedUserData: Record<string, string> = {};
|
||||
if (event.userData.email) hashedUserData.em = hashValue(event.userData.email);
|
||||
if (event.userData.ip) hashedUserData.client_ip_address = event.userData.ip;
|
||||
if (event.userData.userAgent) hashedUserData.client_user_agent = event.userData.userAgent;
|
||||
if (event.userData.fbc) hashedUserData.fbc = event.userData.fbc;
|
||||
if (event.userData.fbp) hashedUserData.fbp = event.userData.fbp;
|
||||
|
||||
const payload = {
|
||||
data: [
|
||||
{
|
||||
event_name: event.eventName,
|
||||
event_time: event.eventTime ?? Math.floor(Date.now() / 1000),
|
||||
event_source_url: event.eventSourceUrl ?? process.env.NEXT_PUBLIC_APP_URL,
|
||||
action_source: 'website',
|
||||
user_data: hashedUserData,
|
||||
custom_data: event.customData ?? {},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/${PIXEL_ID}/events?access_token=${ACCESS_TOKEN}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
console.error('[Meta CAPI] Error:', err);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Meta CAPI] Network error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export { hashValue };
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export const FREE_DYNAMIC_QR_LIMIT = 3;
|
||||
export const PRO_DYNAMIC_QR_LIMIT = 50;
|
||||
export const BUSINESS_DYNAMIC_QR_LIMIT = 500;
|
||||
|
||||
export const DYNAMIC_QR_LIMITS = {
|
||||
FREE: FREE_DYNAMIC_QR_LIMIT,
|
||||
PRO: PRO_DYNAMIC_QR_LIMIT,
|
||||
BUSINESS: BUSINESS_DYNAMIC_QR_LIMIT,
|
||||
ENTERPRISE: 99999,
|
||||
} as const;
|
||||
export const FREE_DYNAMIC_QR_LIMIT = 3;
|
||||
export const PRO_DYNAMIC_QR_LIMIT = 50;
|
||||
export const BUSINESS_DYNAMIC_QR_LIMIT = 500;
|
||||
|
||||
export const DYNAMIC_QR_LIMITS = {
|
||||
FREE: FREE_DYNAMIC_QR_LIMIT,
|
||||
PRO: PRO_DYNAMIC_QR_LIMIT,
|
||||
BUSINESS: BUSINESS_DYNAMIC_QR_LIMIT,
|
||||
ENTERPRISE: 99999,
|
||||
} as const;
|
||||
|
||||
@@ -1,84 +1,84 @@
|
||||
import type { FAQItem } from "@/lib/types";
|
||||
|
||||
export type SeoIntentStage = "top" | "middle" | "bottom";
|
||||
|
||||
export type SeoPrimaryCta = {
|
||||
href: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type SeoRelatedLink = {
|
||||
href: string;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type ComparisonType =
|
||||
| "alternative"
|
||||
| "versus"
|
||||
| "feature"
|
||||
| "model";
|
||||
|
||||
export type GuideType = "problem-solving" | "strategic";
|
||||
|
||||
export type ComparisonScorecardRow = {
|
||||
topic: string;
|
||||
qrMaster: string;
|
||||
alternative: string;
|
||||
};
|
||||
|
||||
export type ComparisonDecisionPoint = {
|
||||
label: string;
|
||||
winner: "qr-master" | "alternative" | "depends";
|
||||
reason: string;
|
||||
};
|
||||
|
||||
export type ComparisonPage = {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
canonicalPath: string;
|
||||
targetKeyword: string;
|
||||
comparisonType: ComparisonType;
|
||||
intentStage: SeoIntentStage;
|
||||
cluster: string;
|
||||
primaryEntity: string;
|
||||
secondaryEntity: string;
|
||||
intro: string;
|
||||
quickAnswer: string;
|
||||
scorecard: ComparisonScorecardRow[];
|
||||
decisionPoints: ComparisonDecisionPoint[];
|
||||
bestForQrMaster: string[];
|
||||
bestForAlternative: string[];
|
||||
migrationSteps: string[];
|
||||
faq: FAQItem[];
|
||||
primaryCta: SeoPrimaryCta;
|
||||
secondaryCta: SeoPrimaryCta;
|
||||
relatedLinks: SeoRelatedLink[];
|
||||
};
|
||||
|
||||
export type GuideChecklistSection = {
|
||||
title: string;
|
||||
items: string[];
|
||||
};
|
||||
|
||||
export type GuidePage = {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
canonicalPath: string;
|
||||
targetKeyword: string;
|
||||
guideType: GuideType;
|
||||
intentStage: SeoIntentStage;
|
||||
cluster: string;
|
||||
intro: string;
|
||||
quickAnswer: string;
|
||||
symptoms?: string[];
|
||||
howToSteps: string[];
|
||||
keyTakeaways: string[];
|
||||
checklist?: GuideChecklistSection;
|
||||
faq: FAQItem[];
|
||||
primaryCta: SeoPrimaryCta;
|
||||
secondaryCta: SeoPrimaryCta;
|
||||
relatedLinks: SeoRelatedLink[];
|
||||
};
|
||||
import type { FAQItem } from "@/lib/types";
|
||||
|
||||
export type SeoIntentStage = "top" | "middle" | "bottom";
|
||||
|
||||
export type SeoPrimaryCta = {
|
||||
href: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type SeoRelatedLink = {
|
||||
href: string;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type ComparisonType =
|
||||
| "alternative"
|
||||
| "versus"
|
||||
| "feature"
|
||||
| "model";
|
||||
|
||||
export type GuideType = "problem-solving" | "strategic";
|
||||
|
||||
export type ComparisonScorecardRow = {
|
||||
topic: string;
|
||||
qrMaster: string;
|
||||
alternative: string;
|
||||
};
|
||||
|
||||
export type ComparisonDecisionPoint = {
|
||||
label: string;
|
||||
winner: "qr-master" | "alternative" | "depends";
|
||||
reason: string;
|
||||
};
|
||||
|
||||
export type ComparisonPage = {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
canonicalPath: string;
|
||||
targetKeyword: string;
|
||||
comparisonType: ComparisonType;
|
||||
intentStage: SeoIntentStage;
|
||||
cluster: string;
|
||||
primaryEntity: string;
|
||||
secondaryEntity: string;
|
||||
intro: string;
|
||||
quickAnswer: string;
|
||||
scorecard: ComparisonScorecardRow[];
|
||||
decisionPoints: ComparisonDecisionPoint[];
|
||||
bestForQrMaster: string[];
|
||||
bestForAlternative: string[];
|
||||
migrationSteps: string[];
|
||||
faq: FAQItem[];
|
||||
primaryCta: SeoPrimaryCta;
|
||||
secondaryCta: SeoPrimaryCta;
|
||||
relatedLinks: SeoRelatedLink[];
|
||||
};
|
||||
|
||||
export type GuideChecklistSection = {
|
||||
title: string;
|
||||
items: string[];
|
||||
};
|
||||
|
||||
export type GuidePage = {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
canonicalPath: string;
|
||||
targetKeyword: string;
|
||||
guideType: GuideType;
|
||||
intentStage: SeoIntentStage;
|
||||
cluster: string;
|
||||
intro: string;
|
||||
quickAnswer: string;
|
||||
symptoms?: string[];
|
||||
howToSteps: string[];
|
||||
keyTakeaways: string[];
|
||||
checklist?: GuideChecklistSection;
|
||||
faq: FAQItem[];
|
||||
primaryCta: SeoPrimaryCta;
|
||||
secondaryCta: SeoPrimaryCta;
|
||||
relatedLinks: SeoRelatedLink[];
|
||||
};
|
||||
|
||||
@@ -1,370 +1,370 @@
|
||||
export const ATTRIBUTION_COOKIE_NAME = 'qrmaster_first_touch';
|
||||
export const ONBOARDING_CHECKLIST_DISMISS_KEY = 'qrmaster_onboarding_checklist_dismissed';
|
||||
export const ONBOARDING_DOWNLOAD_COMPLETE_KEY = 'qrmaster_onboarding_download_complete';
|
||||
export const ONBOARDING_DOWNLOAD_COMPLETE_EVENT = 'qrmaster:onboarding-download-complete';
|
||||
|
||||
export type LifecycleStage =
|
||||
| 'cold'
|
||||
| 'activated'
|
||||
| 'warm'
|
||||
| 'hot'
|
||||
| 'upgrade_candidate'
|
||||
| 'paid';
|
||||
|
||||
export type SignupSourceOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type OnboardingOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type AttributionSnapshot = {
|
||||
signupSource?: string | null;
|
||||
signupMedium?: string | null;
|
||||
signupCampaign?: string | null;
|
||||
signupContent?: string | null;
|
||||
signupTerm?: string | null;
|
||||
signupReferrer?: string | null;
|
||||
signupLandingPath?: string | null;
|
||||
signupFirstSeenAt?: string | null;
|
||||
};
|
||||
|
||||
const FREEMAIL_DOMAINS = new Set([
|
||||
'gmail.com',
|
||||
'yahoo.com',
|
||||
'hotmail.com',
|
||||
'outlook.com',
|
||||
'icloud.com',
|
||||
'gmx.de',
|
||||
'gmx.net',
|
||||
'web.de',
|
||||
'mail.com',
|
||||
'aol.com',
|
||||
'proton.me',
|
||||
'protonmail.com',
|
||||
]);
|
||||
|
||||
export const SIGNUP_SOURCE_OPTIONS: SignupSourceOption[] = [
|
||||
{ value: 'google_search', label: 'Google Search' },
|
||||
{ value: 'instagram', label: 'Instagram' },
|
||||
{ value: 'facebook', label: 'Facebook' },
|
||||
{ value: 'tiktok', label: 'TikTok' },
|
||||
{ value: 'linkedin', label: 'LinkedIn' },
|
||||
{ value: 'youtube', label: 'YouTube' },
|
||||
{ value: 'blog_article', label: 'Blog or article' },
|
||||
{ value: 'friend_colleague', label: 'Friend or colleague' },
|
||||
{ value: 'direct', label: 'Direct / I typed the URL' },
|
||||
{ value: 'other', label: 'Other' },
|
||||
];
|
||||
|
||||
export const PRIMARY_USE_CASE_OPTIONS: OnboardingOption[] = [
|
||||
{ value: 'website_qr', label: 'Website QR Code' },
|
||||
{ value: 'menu_pdf', label: 'Menu or PDF QR Code' },
|
||||
{ value: 'contact_card', label: 'Contact Card QR Code' },
|
||||
{ value: 'wifi_qr', label: 'WiFi QR Code' },
|
||||
{ value: 'marketing_campaign', label: 'Marketing Campaign QR Code' },
|
||||
{ value: 'barcode', label: 'Barcode' },
|
||||
{ value: 'bulk_qr', label: 'Bulk QR Codes' },
|
||||
{ value: 'something_else', label: 'Something else' },
|
||||
];
|
||||
|
||||
export const PRIMARY_GOAL_OPTIONS: OnboardingOption[] = [
|
||||
{ value: 'drive_website_traffic', label: 'Drive website traffic' },
|
||||
{ value: 'track_printed_campaigns', label: 'Track printed campaigns' },
|
||||
{ value: 'share_contact_details', label: 'Share contact details' },
|
||||
{ value: 'replace_printed_menus', label: 'Replace printed menus' },
|
||||
{ value: 'generate_leads', label: 'Generate leads' },
|
||||
{ value: 'label_products', label: 'Label products' },
|
||||
{ value: 'manage_multiple_qr_codes', label: 'Manage multiple QR codes' },
|
||||
{ value: 'something_else', label: 'Something else' },
|
||||
];
|
||||
|
||||
export const JOB_ROLE_OPTIONS: OnboardingOption[] = [
|
||||
{ value: 'founder_owner', label: 'Founder / Owner' },
|
||||
{ value: 'marketing_manager', label: 'Marketing Manager' },
|
||||
{ value: 'operations', label: 'Operations' },
|
||||
{ value: 'agency_freelancer', label: 'Agency / Freelancer' },
|
||||
{ value: 'it_technical', label: 'IT / Technical' },
|
||||
{ value: 'sales', label: 'Sales' },
|
||||
{ value: 'designer', label: 'Designer' },
|
||||
{ value: 'other', label: 'Other' },
|
||||
];
|
||||
|
||||
export const TEAM_SIZE_OPTIONS: OnboardingOption[] = [
|
||||
{ value: 'just_me', label: 'Just me' },
|
||||
{ value: '2_5', label: '2–5' },
|
||||
{ value: '6_20', label: '6–20' },
|
||||
{ value: '21_100', label: '21–100' },
|
||||
{ value: '100_plus', label: '100+' },
|
||||
];
|
||||
|
||||
export function serializeAttributionCookie(snapshot: AttributionSnapshot): string {
|
||||
return JSON.stringify(snapshot);
|
||||
}
|
||||
|
||||
export function parseAttributionCookie(value?: string | null): AttributionSnapshot | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value) as AttributionSnapshot;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getEmailDomain(email?: string | null): string | null {
|
||||
if (!email || !email.includes('@')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return email.split('@')[1]?.trim().toLowerCase() || null;
|
||||
}
|
||||
|
||||
export function isFreemailDomain(domain?: string | null): boolean {
|
||||
return Boolean(domain && FREEMAIL_DOMAINS.has(domain.toLowerCase()));
|
||||
}
|
||||
|
||||
export function getSourceLabel(value?: string | null): string {
|
||||
const option = SIGNUP_SOURCE_OPTIONS.find((item) => item.value === value);
|
||||
return option?.label || 'Unknown';
|
||||
}
|
||||
|
||||
export function getUseCaseLabel(value?: string | null): string {
|
||||
const option = PRIMARY_USE_CASE_OPTIONS.find((item) => item.value === value);
|
||||
return option?.label || 'Unknown';
|
||||
}
|
||||
|
||||
export function getGoalLabel(value?: string | null): string {
|
||||
const option = PRIMARY_GOAL_OPTIONS.find((item) => item.value === value);
|
||||
return option?.label || 'Unknown';
|
||||
}
|
||||
|
||||
export function getRoleLabel(value?: string | null): string {
|
||||
const option = JOB_ROLE_OPTIONS.find((item) => item.value === value);
|
||||
return option?.label || 'Unknown';
|
||||
}
|
||||
|
||||
export function getTeamSizeLabel(value?: string | null): string {
|
||||
const option = TEAM_SIZE_OPTIONS.find((item) => item.value === value);
|
||||
return option?.label || 'Unknown';
|
||||
}
|
||||
|
||||
export function normalizeSource(input: {
|
||||
utmSource?: string | null;
|
||||
referrer?: string | null;
|
||||
landingPath?: string | null;
|
||||
}): string {
|
||||
const utmSource = input.utmSource?.toLowerCase().trim();
|
||||
const referrer = input.referrer?.toLowerCase().trim();
|
||||
const landingPath = input.landingPath?.toLowerCase().trim();
|
||||
|
||||
if (utmSource) {
|
||||
if (utmSource.includes('google')) return 'google_search';
|
||||
if (utmSource.includes('instagram')) return 'instagram';
|
||||
if (utmSource.includes('facebook') || utmSource.includes('meta')) return 'facebook';
|
||||
if (utmSource.includes('tiktok')) return 'tiktok';
|
||||
if (utmSource.includes('linkedin')) return 'linkedin';
|
||||
if (utmSource.includes('youtube')) return 'youtube';
|
||||
if (utmSource.includes('blog') || utmSource.includes('article') || utmSource.includes('seo')) return 'blog_article';
|
||||
}
|
||||
|
||||
if (referrer) {
|
||||
if (referrer.includes('google.')) return 'google_search';
|
||||
if (referrer.includes('instagram.')) return 'instagram';
|
||||
if (referrer.includes('facebook.') || referrer.includes('fb.com')) return 'facebook';
|
||||
if (referrer.includes('tiktok.')) return 'tiktok';
|
||||
if (referrer.includes('linkedin.')) return 'linkedin';
|
||||
if (referrer.includes('youtube.')) return 'youtube';
|
||||
if (referrer.includes('medium.com') || referrer.includes('substack.com') || referrer.includes('/blog')) return 'blog_article';
|
||||
}
|
||||
|
||||
if (landingPath && landingPath.startsWith('/blog')) {
|
||||
return 'blog_article';
|
||||
}
|
||||
|
||||
return 'direct';
|
||||
}
|
||||
|
||||
export function buildAttributionSnapshot(params: {
|
||||
utmSource?: string | null;
|
||||
utmMedium?: string | null;
|
||||
utmCampaign?: string | null;
|
||||
utmContent?: string | null;
|
||||
utmTerm?: string | null;
|
||||
referrer?: string | null;
|
||||
landingPath?: string | null;
|
||||
firstSeenAt?: Date | string | null;
|
||||
}): AttributionSnapshot {
|
||||
const signupFirstSeenAt =
|
||||
params.firstSeenAt instanceof Date
|
||||
? params.firstSeenAt.toISOString()
|
||||
: params.firstSeenAt || new Date().toISOString();
|
||||
|
||||
return {
|
||||
signupSource: normalizeSource({
|
||||
utmSource: params.utmSource,
|
||||
referrer: params.referrer,
|
||||
landingPath: params.landingPath,
|
||||
}),
|
||||
signupMedium: params.utmMedium || null,
|
||||
signupCampaign: params.utmCampaign || null,
|
||||
signupContent: params.utmContent || null,
|
||||
signupTerm: params.utmTerm || null,
|
||||
signupReferrer: params.referrer || null,
|
||||
signupLandingPath: params.landingPath || null,
|
||||
signupFirstSeenAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldResumeOnboarding(user: {
|
||||
onboardingStartedAt?: Date | null;
|
||||
onboardingCompletedAt?: Date | null;
|
||||
} | null): boolean {
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(user.onboardingStartedAt && !user.onboardingCompletedAt);
|
||||
}
|
||||
|
||||
export function getLifecycleStageLabel(stage?: string | null): string {
|
||||
switch (stage) {
|
||||
case 'activated':
|
||||
return 'Activated';
|
||||
case 'warm':
|
||||
return 'Warm';
|
||||
case 'hot':
|
||||
return 'Hot';
|
||||
case 'upgrade_candidate':
|
||||
return 'Upgrade Candidate';
|
||||
case 'paid':
|
||||
return 'Paid';
|
||||
default:
|
||||
return 'Cold';
|
||||
}
|
||||
}
|
||||
|
||||
export function getOnboardingHeadlineForUseCase(useCase?: string | null): string {
|
||||
switch (useCase) {
|
||||
case 'marketing_campaign':
|
||||
return 'Create your first campaign QR code';
|
||||
case 'menu_pdf':
|
||||
return 'Create your first menu QR code';
|
||||
case 'contact_card':
|
||||
return 'Create your first contact QR code';
|
||||
case 'barcode':
|
||||
return 'Create your first barcode';
|
||||
case 'bulk_qr':
|
||||
return 'Set up your first business-ready QR';
|
||||
default:
|
||||
return 'Create your first QR code';
|
||||
}
|
||||
}
|
||||
|
||||
export function getCreatePresetForUseCase(useCase?: string | null): {
|
||||
contentType: string;
|
||||
dynamic: boolean;
|
||||
title: string;
|
||||
content: Record<string, string>;
|
||||
} {
|
||||
switch (useCase) {
|
||||
case 'menu_pdf':
|
||||
return {
|
||||
contentType: 'PDF',
|
||||
dynamic: true,
|
||||
title: 'Restaurant menu',
|
||||
content: {},
|
||||
};
|
||||
case 'contact_card':
|
||||
return {
|
||||
contentType: 'VCARD',
|
||||
dynamic: true,
|
||||
title: 'Business contact card',
|
||||
content: {},
|
||||
};
|
||||
case 'barcode':
|
||||
return {
|
||||
contentType: 'BARCODE',
|
||||
dynamic: false,
|
||||
title: 'Product barcode',
|
||||
content: { format: 'CODE128' },
|
||||
};
|
||||
case 'bulk_qr':
|
||||
return {
|
||||
contentType: 'URL',
|
||||
dynamic: true,
|
||||
title: 'Bulk campaign starter',
|
||||
content: { url: '' },
|
||||
};
|
||||
case 'wifi_qr':
|
||||
return {
|
||||
contentType: 'TEXT',
|
||||
dynamic: false,
|
||||
title: 'WiFi access QR',
|
||||
content: { text: 'WIFI:T:WPA;S:MyNetwork;P:password;;' },
|
||||
};
|
||||
case 'website_qr':
|
||||
return {
|
||||
contentType: 'URL',
|
||||
dynamic: true,
|
||||
title: 'Website QR',
|
||||
content: { url: '' },
|
||||
};
|
||||
case 'marketing_campaign':
|
||||
default:
|
||||
return {
|
||||
contentType: 'URL',
|
||||
dynamic: true,
|
||||
title: 'Campaign landing page',
|
||||
content: { url: '' },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getChecklistItems(user: {
|
||||
signupSourceSelfReported?: string | null;
|
||||
primaryUseCase?: string | null;
|
||||
firstQrCreatedAt?: string | Date | null;
|
||||
firstDynamicQrAt?: string | Date | null;
|
||||
firstScanAt?: string | Date | null;
|
||||
activationAt?: string | Date | null;
|
||||
}): Array<{ id: string; label: string; done: boolean }> {
|
||||
return [
|
||||
{
|
||||
id: 'source',
|
||||
label: 'Confirm how you found QR Master',
|
||||
done: Boolean(user.signupSourceSelfReported),
|
||||
},
|
||||
{
|
||||
id: 'use-case',
|
||||
label: 'Choose your use case',
|
||||
done: Boolean(user.primaryUseCase),
|
||||
},
|
||||
{
|
||||
id: 'first-qr',
|
||||
label: 'Create your first QR code',
|
||||
done: Boolean(user.firstQrCreatedAt),
|
||||
},
|
||||
{
|
||||
id: 'first-dynamic',
|
||||
label: 'Create your first dynamic QR code',
|
||||
done: Boolean(user.firstDynamicQrAt),
|
||||
},
|
||||
{
|
||||
id: 'download',
|
||||
label: 'Download your QR code',
|
||||
done: false,
|
||||
},
|
||||
{
|
||||
id: 'scan',
|
||||
label: 'Get your first scan',
|
||||
done: Boolean(user.firstScanAt),
|
||||
},
|
||||
];
|
||||
}
|
||||
export const ATTRIBUTION_COOKIE_NAME = 'qrmaster_first_touch';
|
||||
export const ONBOARDING_CHECKLIST_DISMISS_KEY = 'qrmaster_onboarding_checklist_dismissed';
|
||||
export const ONBOARDING_DOWNLOAD_COMPLETE_KEY = 'qrmaster_onboarding_download_complete';
|
||||
export const ONBOARDING_DOWNLOAD_COMPLETE_EVENT = 'qrmaster:onboarding-download-complete';
|
||||
|
||||
export type LifecycleStage =
|
||||
| 'cold'
|
||||
| 'activated'
|
||||
| 'warm'
|
||||
| 'hot'
|
||||
| 'upgrade_candidate'
|
||||
| 'paid';
|
||||
|
||||
export type SignupSourceOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type OnboardingOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type AttributionSnapshot = {
|
||||
signupSource?: string | null;
|
||||
signupMedium?: string | null;
|
||||
signupCampaign?: string | null;
|
||||
signupContent?: string | null;
|
||||
signupTerm?: string | null;
|
||||
signupReferrer?: string | null;
|
||||
signupLandingPath?: string | null;
|
||||
signupFirstSeenAt?: string | null;
|
||||
};
|
||||
|
||||
const FREEMAIL_DOMAINS = new Set([
|
||||
'gmail.com',
|
||||
'yahoo.com',
|
||||
'hotmail.com',
|
||||
'outlook.com',
|
||||
'icloud.com',
|
||||
'gmx.de',
|
||||
'gmx.net',
|
||||
'web.de',
|
||||
'mail.com',
|
||||
'aol.com',
|
||||
'proton.me',
|
||||
'protonmail.com',
|
||||
]);
|
||||
|
||||
export const SIGNUP_SOURCE_OPTIONS: SignupSourceOption[] = [
|
||||
{ value: 'google_search', label: 'Google Search' },
|
||||
{ value: 'instagram', label: 'Instagram' },
|
||||
{ value: 'facebook', label: 'Facebook' },
|
||||
{ value: 'tiktok', label: 'TikTok' },
|
||||
{ value: 'linkedin', label: 'LinkedIn' },
|
||||
{ value: 'youtube', label: 'YouTube' },
|
||||
{ value: 'blog_article', label: 'Blog or article' },
|
||||
{ value: 'friend_colleague', label: 'Friend or colleague' },
|
||||
{ value: 'direct', label: 'Direct / I typed the URL' },
|
||||
{ value: 'other', label: 'Other' },
|
||||
];
|
||||
|
||||
export const PRIMARY_USE_CASE_OPTIONS: OnboardingOption[] = [
|
||||
{ value: 'website_qr', label: 'Website QR Code' },
|
||||
{ value: 'menu_pdf', label: 'Menu or PDF QR Code' },
|
||||
{ value: 'contact_card', label: 'Contact Card QR Code' },
|
||||
{ value: 'wifi_qr', label: 'WiFi QR Code' },
|
||||
{ value: 'marketing_campaign', label: 'Marketing Campaign QR Code' },
|
||||
{ value: 'barcode', label: 'Barcode' },
|
||||
{ value: 'bulk_qr', label: 'Bulk QR Codes' },
|
||||
{ value: 'something_else', label: 'Something else' },
|
||||
];
|
||||
|
||||
export const PRIMARY_GOAL_OPTIONS: OnboardingOption[] = [
|
||||
{ value: 'drive_website_traffic', label: 'Drive website traffic' },
|
||||
{ value: 'track_printed_campaigns', label: 'Track printed campaigns' },
|
||||
{ value: 'share_contact_details', label: 'Share contact details' },
|
||||
{ value: 'replace_printed_menus', label: 'Replace printed menus' },
|
||||
{ value: 'generate_leads', label: 'Generate leads' },
|
||||
{ value: 'label_products', label: 'Label products' },
|
||||
{ value: 'manage_multiple_qr_codes', label: 'Manage multiple QR codes' },
|
||||
{ value: 'something_else', label: 'Something else' },
|
||||
];
|
||||
|
||||
export const JOB_ROLE_OPTIONS: OnboardingOption[] = [
|
||||
{ value: 'founder_owner', label: 'Founder / Owner' },
|
||||
{ value: 'marketing_manager', label: 'Marketing Manager' },
|
||||
{ value: 'operations', label: 'Operations' },
|
||||
{ value: 'agency_freelancer', label: 'Agency / Freelancer' },
|
||||
{ value: 'it_technical', label: 'IT / Technical' },
|
||||
{ value: 'sales', label: 'Sales' },
|
||||
{ value: 'designer', label: 'Designer' },
|
||||
{ value: 'other', label: 'Other' },
|
||||
];
|
||||
|
||||
export const TEAM_SIZE_OPTIONS: OnboardingOption[] = [
|
||||
{ value: 'just_me', label: 'Just me' },
|
||||
{ value: '2_5', label: '2–5' },
|
||||
{ value: '6_20', label: '6–20' },
|
||||
{ value: '21_100', label: '21–100' },
|
||||
{ value: '100_plus', label: '100+' },
|
||||
];
|
||||
|
||||
export function serializeAttributionCookie(snapshot: AttributionSnapshot): string {
|
||||
return JSON.stringify(snapshot);
|
||||
}
|
||||
|
||||
export function parseAttributionCookie(value?: string | null): AttributionSnapshot | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value) as AttributionSnapshot;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getEmailDomain(email?: string | null): string | null {
|
||||
if (!email || !email.includes('@')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return email.split('@')[1]?.trim().toLowerCase() || null;
|
||||
}
|
||||
|
||||
export function isFreemailDomain(domain?: string | null): boolean {
|
||||
return Boolean(domain && FREEMAIL_DOMAINS.has(domain.toLowerCase()));
|
||||
}
|
||||
|
||||
export function getSourceLabel(value?: string | null): string {
|
||||
const option = SIGNUP_SOURCE_OPTIONS.find((item) => item.value === value);
|
||||
return option?.label || 'Unknown';
|
||||
}
|
||||
|
||||
export function getUseCaseLabel(value?: string | null): string {
|
||||
const option = PRIMARY_USE_CASE_OPTIONS.find((item) => item.value === value);
|
||||
return option?.label || 'Unknown';
|
||||
}
|
||||
|
||||
export function getGoalLabel(value?: string | null): string {
|
||||
const option = PRIMARY_GOAL_OPTIONS.find((item) => item.value === value);
|
||||
return option?.label || 'Unknown';
|
||||
}
|
||||
|
||||
export function getRoleLabel(value?: string | null): string {
|
||||
const option = JOB_ROLE_OPTIONS.find((item) => item.value === value);
|
||||
return option?.label || 'Unknown';
|
||||
}
|
||||
|
||||
export function getTeamSizeLabel(value?: string | null): string {
|
||||
const option = TEAM_SIZE_OPTIONS.find((item) => item.value === value);
|
||||
return option?.label || 'Unknown';
|
||||
}
|
||||
|
||||
export function normalizeSource(input: {
|
||||
utmSource?: string | null;
|
||||
referrer?: string | null;
|
||||
landingPath?: string | null;
|
||||
}): string {
|
||||
const utmSource = input.utmSource?.toLowerCase().trim();
|
||||
const referrer = input.referrer?.toLowerCase().trim();
|
||||
const landingPath = input.landingPath?.toLowerCase().trim();
|
||||
|
||||
if (utmSource) {
|
||||
if (utmSource.includes('google')) return 'google_search';
|
||||
if (utmSource.includes('instagram')) return 'instagram';
|
||||
if (utmSource.includes('facebook') || utmSource.includes('meta')) return 'facebook';
|
||||
if (utmSource.includes('tiktok')) return 'tiktok';
|
||||
if (utmSource.includes('linkedin')) return 'linkedin';
|
||||
if (utmSource.includes('youtube')) return 'youtube';
|
||||
if (utmSource.includes('blog') || utmSource.includes('article') || utmSource.includes('seo')) return 'blog_article';
|
||||
}
|
||||
|
||||
if (referrer) {
|
||||
if (referrer.includes('google.')) return 'google_search';
|
||||
if (referrer.includes('instagram.')) return 'instagram';
|
||||
if (referrer.includes('facebook.') || referrer.includes('fb.com')) return 'facebook';
|
||||
if (referrer.includes('tiktok.')) return 'tiktok';
|
||||
if (referrer.includes('linkedin.')) return 'linkedin';
|
||||
if (referrer.includes('youtube.')) return 'youtube';
|
||||
if (referrer.includes('medium.com') || referrer.includes('substack.com') || referrer.includes('/blog')) return 'blog_article';
|
||||
}
|
||||
|
||||
if (landingPath && landingPath.startsWith('/blog')) {
|
||||
return 'blog_article';
|
||||
}
|
||||
|
||||
return 'direct';
|
||||
}
|
||||
|
||||
export function buildAttributionSnapshot(params: {
|
||||
utmSource?: string | null;
|
||||
utmMedium?: string | null;
|
||||
utmCampaign?: string | null;
|
||||
utmContent?: string | null;
|
||||
utmTerm?: string | null;
|
||||
referrer?: string | null;
|
||||
landingPath?: string | null;
|
||||
firstSeenAt?: Date | string | null;
|
||||
}): AttributionSnapshot {
|
||||
const signupFirstSeenAt =
|
||||
params.firstSeenAt instanceof Date
|
||||
? params.firstSeenAt.toISOString()
|
||||
: params.firstSeenAt || new Date().toISOString();
|
||||
|
||||
return {
|
||||
signupSource: normalizeSource({
|
||||
utmSource: params.utmSource,
|
||||
referrer: params.referrer,
|
||||
landingPath: params.landingPath,
|
||||
}),
|
||||
signupMedium: params.utmMedium || null,
|
||||
signupCampaign: params.utmCampaign || null,
|
||||
signupContent: params.utmContent || null,
|
||||
signupTerm: params.utmTerm || null,
|
||||
signupReferrer: params.referrer || null,
|
||||
signupLandingPath: params.landingPath || null,
|
||||
signupFirstSeenAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldResumeOnboarding(user: {
|
||||
onboardingStartedAt?: Date | null;
|
||||
onboardingCompletedAt?: Date | null;
|
||||
} | null): boolean {
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(user.onboardingStartedAt && !user.onboardingCompletedAt);
|
||||
}
|
||||
|
||||
export function getLifecycleStageLabel(stage?: string | null): string {
|
||||
switch (stage) {
|
||||
case 'activated':
|
||||
return 'Activated';
|
||||
case 'warm':
|
||||
return 'Warm';
|
||||
case 'hot':
|
||||
return 'Hot';
|
||||
case 'upgrade_candidate':
|
||||
return 'Upgrade Candidate';
|
||||
case 'paid':
|
||||
return 'Paid';
|
||||
default:
|
||||
return 'Cold';
|
||||
}
|
||||
}
|
||||
|
||||
export function getOnboardingHeadlineForUseCase(useCase?: string | null): string {
|
||||
switch (useCase) {
|
||||
case 'marketing_campaign':
|
||||
return 'Create your first campaign QR code';
|
||||
case 'menu_pdf':
|
||||
return 'Create your first menu QR code';
|
||||
case 'contact_card':
|
||||
return 'Create your first contact QR code';
|
||||
case 'barcode':
|
||||
return 'Create your first barcode';
|
||||
case 'bulk_qr':
|
||||
return 'Set up your first business-ready QR';
|
||||
default:
|
||||
return 'Create your first QR code';
|
||||
}
|
||||
}
|
||||
|
||||
export function getCreatePresetForUseCase(useCase?: string | null): {
|
||||
contentType: string;
|
||||
dynamic: boolean;
|
||||
title: string;
|
||||
content: Record<string, string>;
|
||||
} {
|
||||
switch (useCase) {
|
||||
case 'menu_pdf':
|
||||
return {
|
||||
contentType: 'PDF',
|
||||
dynamic: true,
|
||||
title: 'Restaurant menu',
|
||||
content: {},
|
||||
};
|
||||
case 'contact_card':
|
||||
return {
|
||||
contentType: 'VCARD',
|
||||
dynamic: true,
|
||||
title: 'Business contact card',
|
||||
content: {},
|
||||
};
|
||||
case 'barcode':
|
||||
return {
|
||||
contentType: 'BARCODE',
|
||||
dynamic: false,
|
||||
title: 'Product barcode',
|
||||
content: { format: 'CODE128' },
|
||||
};
|
||||
case 'bulk_qr':
|
||||
return {
|
||||
contentType: 'URL',
|
||||
dynamic: true,
|
||||
title: 'Bulk campaign starter',
|
||||
content: { url: '' },
|
||||
};
|
||||
case 'wifi_qr':
|
||||
return {
|
||||
contentType: 'TEXT',
|
||||
dynamic: false,
|
||||
title: 'WiFi access QR',
|
||||
content: { text: 'WIFI:T:WPA;S:MyNetwork;P:password;;' },
|
||||
};
|
||||
case 'website_qr':
|
||||
return {
|
||||
contentType: 'URL',
|
||||
dynamic: true,
|
||||
title: 'Website QR',
|
||||
content: { url: '' },
|
||||
};
|
||||
case 'marketing_campaign':
|
||||
default:
|
||||
return {
|
||||
contentType: 'URL',
|
||||
dynamic: true,
|
||||
title: 'Campaign landing page',
|
||||
content: { url: '' },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getChecklistItems(user: {
|
||||
signupSourceSelfReported?: string | null;
|
||||
primaryUseCase?: string | null;
|
||||
firstQrCreatedAt?: string | Date | null;
|
||||
firstDynamicQrAt?: string | Date | null;
|
||||
firstScanAt?: string | Date | null;
|
||||
activationAt?: string | Date | null;
|
||||
}): Array<{ id: string; label: string; done: boolean }> {
|
||||
return [
|
||||
{
|
||||
id: 'source',
|
||||
label: 'Confirm how you found QR Master',
|
||||
done: Boolean(user.signupSourceSelfReported),
|
||||
},
|
||||
{
|
||||
id: 'use-case',
|
||||
label: 'Choose your use case',
|
||||
done: Boolean(user.primaryUseCase),
|
||||
},
|
||||
{
|
||||
id: 'first-qr',
|
||||
label: 'Create your first QR code',
|
||||
done: Boolean(user.firstQrCreatedAt),
|
||||
},
|
||||
{
|
||||
id: 'first-dynamic',
|
||||
label: 'Create your first dynamic QR code',
|
||||
done: Boolean(user.firstDynamicQrAt),
|
||||
},
|
||||
{
|
||||
id: 'download',
|
||||
label: 'Download your QR code',
|
||||
done: false,
|
||||
},
|
||||
{
|
||||
id: 'scan',
|
||||
label: 'Get your first scan',
|
||||
done: Boolean(user.firstScanAt),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,199 +1,199 @@
|
||||
import { IndustryPage } from './industry-pages';
|
||||
|
||||
export const missingIndustries: IndustryPage[] = [
|
||||
{
|
||||
slug: "tattoo-studios",
|
||||
name: "Tattoo Studios",
|
||||
title: "QR Codes for Tattoo Studios",
|
||||
metaTitle: "QR Codes for Tattoo Studios: Portfolios & Aftercare",
|
||||
metaDescription: "Digitalize your tattoo studio with QR codes for artist portfolios, booking, and digital aftercare instructions.",
|
||||
headline: "QR Codes for Tattoo Studios: From Portfolio to Aftercare Instructions",
|
||||
subheadline: "Let clients browse your artists' work and access healing guides with a simple scan in your shop",
|
||||
heroImage: "/marketing/industries/tattoo-studios.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "More Portfolio Views via Window QR" },
|
||||
{ value: "40%", label: "Increase in Online Bookings" },
|
||||
{ value: "50%", label: "Fewer Aftercare Follow-up Questions" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Digital Artist Portfolios", description: "Place QR codes in your studio window or at each station. Clients scan to view the artist's full Instagram or website portfolio without scrolling through a phone." },
|
||||
{ title: "Frictionless Booking", description: "A QR on your business cards and window posters links directly to your booking system, letting potential clients reserve their spot immediately when inspired." },
|
||||
{ title: "Digital Aftercare Guides", description: "Replace paper pamphlets with a QR link to detailed aftercare instructions and healing videos, ensuring clients always have access to the best care advice." }
|
||||
],
|
||||
useCases: [
|
||||
"Window display QR linking to artist portfolios",
|
||||
"Booking portal link on business cards",
|
||||
"Digital aftercare instructions for new tattoos",
|
||||
"Social media growth via Instagram/TikTok QR",
|
||||
"Google Review collection at the end of a session"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do tattoo studios use QR codes for portfolios?", answer: "Studios place QR codes in their window or at artist stations that link directly to digital portfolios, allowing clients to see more work instantly." },
|
||||
{ question: "Can QR codes replace paper aftercare instructions?", answer: "Yes, many studios use QR codes linking to a permanent aftercare page or PDF, which is easier for clients to keep track of than a physical piece of paper." },
|
||||
{ question: "Where is the best place to put a QR code in a tattoo shop?", answer: "The front window, the reception desk, and the individual workstations are the most effective placements for different use cases." }
|
||||
],
|
||||
placementTips: [
|
||||
"Front window for after-hours portfolio browsing",
|
||||
"Reception desk for booking and review collection",
|
||||
"Station mirrors for social media engagement"
|
||||
],
|
||||
germanKeyword: "Tattoostudios"
|
||||
},
|
||||
{
|
||||
slug: "pharmacies",
|
||||
name: "Pharmacies",
|
||||
title: "QR Codes for Pharmacies",
|
||||
metaTitle: "QR Codes for Pharmacies: Prescription Refills & Patient Info",
|
||||
metaDescription: "Improve patient safety and efficiency with QR codes for prescription refills, medication information, and digital advisory leaflets.",
|
||||
headline: "QR Codes for Pharmacies: Digital Refills and Patient Safety Guides",
|
||||
subheadline: "Help patients manage their prescriptions and access medication information with a simple scan at the counter",
|
||||
heroImage: "/marketing/industries/pharmacies.png",
|
||||
statistics: [
|
||||
{ value: "50%", label: "Faster Prescription Refill Requests" },
|
||||
{ value: "2x", label: "Better Patient Medication Adherence" },
|
||||
{ value: "40%", label: "Reduction in Paper Information Sheets" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Instant Prescription Refills", description: "A QR on the prescription bag or bottle links directly to the refill request portal, letting patients order their next dose in seconds." },
|
||||
{ title: "Digital Patient Education", description: "Provide QR codes that link to video tutorials for complex medications (like inhalers or pens) and digital versions of the Patient Advisory Leaflet." },
|
||||
{ title: "Pharmacy-to-Patient Messaging", description: "A QR code at the counter lets patients sign up for SMS or email notifications when their prescription is ready for collection." }
|
||||
],
|
||||
useCases: [
|
||||
"Prescription refill portal link on bags and bottles",
|
||||
"Medication education videos and digital PALs",
|
||||
"Collection notification sign-up at the counter",
|
||||
"Pharmacy hours and emergency contact info",
|
||||
"Health and wellness newsletter sign-up"
|
||||
],
|
||||
tools: ["url-qr-code", "sms-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do QR codes help with medication safety?", answer: "QR codes link to clear digital instructions and videos that help patients understand exactly how to take their medication, reducing errors." },
|
||||
{ question: "Can patients refill prescriptions via QR codes?", answer: "Yes, a QR on the packaging can link directly to a secure portal where the patient can request a refill with one click." },
|
||||
{ question: "Where should pharmacies place QR codes?", answer: "The most effective spots are on prescription bags, medication bottles, and counter-top displays near the pickup area." }
|
||||
],
|
||||
placementTips: [
|
||||
"Printed on every prescription bag for easy refills",
|
||||
"Counter-top display for digital health newsletter sign-up",
|
||||
"Directly on medication packaging for usage videos"
|
||||
],
|
||||
germanKeyword: "Apotheken"
|
||||
},
|
||||
{
|
||||
slug: "car-dealerships",
|
||||
name: "Car Dealerships",
|
||||
title: "QR Codes for Car Dealerships",
|
||||
metaTitle: "QR Codes for Car Dealerships: Virtual Tours & Lead Gen",
|
||||
metaDescription: "Drive sales with QR codes for car spec sheets, virtual vehicle tours, financing calculators, and test drive booking.",
|
||||
headline: "QR Codes for Car Dealerships: Your 24/7 Digital Sales Floor",
|
||||
subheadline: "Let buyers explore every vehicle's specs, history, and financing options from the window—even when you're closed",
|
||||
heroImage: "/marketing/industries/car-dealerships.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "More Weekend Leads via Window QRs" },
|
||||
{ value: "3x", label: "Higher Engagement with Spec Sheets" },
|
||||
{ value: "45%", label: "Faster Test Drive Appointment Booking" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Interactive Window Stickers", description: "A QR on the vehicle window sticker links to the full digital spec sheet, high-res photos, car history reports, and a financing calculator." },
|
||||
{ title: "After-Hours Lead Generation", description: "Capture interest 24/7. Even when the dealership is closed, passersby scan the car's QR to view details and book a test drive for the following morning." },
|
||||
{ title: "Seamless Test Drive Booking", description: "Reduce friction by letting customers book their spot directly from their phone while they are standing next to the vehicle they like." }
|
||||
],
|
||||
useCases: [
|
||||
"Virtual tour and spec sheet from window stickers",
|
||||
"Immediate test drive booking link on each car",
|
||||
"Financing and trade-in value calculators",
|
||||
"Service department booking and status alerts",
|
||||
"Loyalty and maintenance reminder QR codes"
|
||||
],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "call-qr-code-generator"],
|
||||
faq: [
|
||||
{ question: "How do QR codes help sell more cars?", answer: "QR codes provide instant information on vehicle specs and pricing, keeping customers engaged and capturing leads even after hours." },
|
||||
{ question: "Can a QR code calculate car financing?", answer: "Yes, you can link the QR to a tool that automatically pulls the car's price into a financing calculator for the customer." },
|
||||
{ question: "Where should dealer QR codes be placed?", answer: "The most important placement is on the driver's side window of every vehicle on the lot, followed by the sales desks and service area." }
|
||||
],
|
||||
placementTips: [
|
||||
"Driver-side window of every car on the lot",
|
||||
"Service waiting area for maintenance booking",
|
||||
"Sales desk for digital business card exchange"
|
||||
],
|
||||
germanKeyword: "Autohäuser"
|
||||
},
|
||||
{
|
||||
slug: "museums",
|
||||
name: "Museums",
|
||||
title: "QR Codes for Museums",
|
||||
metaTitle: "QR Codes for Museums: Interactive Tours & Exhibit Info",
|
||||
metaDescription: "Enhance the visitor experience with QR codes for audio guides, interactive exhibit maps, and digital donations.",
|
||||
headline: "QR Codes for Museums: Bring Artifacts to Life Digitally",
|
||||
subheadline: "Offer multilingual audio guides and interactive storytelling at every exhibit without bulky hardware",
|
||||
heroImage: "/marketing/industries/museums.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Higher Engagement with Digital Guides" },
|
||||
{ value: "50%", label: "Lower Cost vs. Physical Audio Hardware" },
|
||||
{ value: "2x", label: "Increase in Small Digital Donations" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Smartphone Audio Guides", description: "Replace expensive, unhygienic audio guide hardware with QR codes. Visitors scan their phone to hear the story of every exhibit in their own language." },
|
||||
{ title: "Interactive Storytelling", description: "Link QR codes to videos, 3D models, or high-res maps that provide a deeper dive into the history of the artifacts on display." },
|
||||
{ title: "Frictionless Donations", description: "A QR code near the exit or major exhibits makes it easy for inspired visitors to leave a small digital donation with one tap." }
|
||||
],
|
||||
useCases: [
|
||||
"Self-guided audio tours from exhibit labels",
|
||||
"Multilingual descriptions for international guests",
|
||||
"Digital floor maps and facility wayfinding",
|
||||
"Donation and membership enrollment links",
|
||||
"Museum shop product info and online ordering"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "youtube-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do museums use QR codes for audio guides?", answer: "Museums place QR codes on exhibit labels that link to hosted audio files, allowing visitors to use their own phones and headphones." },
|
||||
{ question: "Can QR codes help with museum accessibility?", answer: "Absolutely. They can link to sign language videos, high-contrast text descriptions, or translations for any exhibit." },
|
||||
{ question: "Where should museum QR codes be placed?", answer: "On exhibit labels, the main floor plan in the lobby, and on pillars near the cafe or gift shop." }
|
||||
],
|
||||
placementTips: [
|
||||
"Bottom corner of every major exhibit label",
|
||||
"Main entrance floor map pillar",
|
||||
"Checkout and exit areas for donations"
|
||||
],
|
||||
germanKeyword: "Museen"
|
||||
},
|
||||
{
|
||||
slug: "law-firms",
|
||||
name: "Law Firms",
|
||||
title: "QR Codes for Law Firms",
|
||||
metaTitle: "QR Codes for Law Firms: Attorney Portfolios & Consultations",
|
||||
metaDescription: "Modernize your legal practice with QR codes for digital business cards, consultation booking, and secure document access.",
|
||||
headline: "QR Codes for Law Firms: Professional Connectivity and Client Onboarding",
|
||||
subheadline: "Let prospective clients book a consultation and save your contact details with a simple scan of your card or signage",
|
||||
heroImage: "/marketing/industries/law-firms.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "Professional Contact Saves via vCard" },
|
||||
{ value: "35%", label: "More Online Consultation Requests" },
|
||||
{ value: "40%", label: "Faster Client Document Submission" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Digital vCards for Networking", description: "Ensure potential clients and partners actually save your details. A vCard QR on your business card adds your contact info to their phone instantly." },
|
||||
{ title: "Instant Consultation Booking", description: "A QR on your brochures or building signage links directly to your calendar, allowing prospects to book a discovery call when legal needs are urgent." },
|
||||
{ title: "Secure Document Portals", description: "Provide clients with QR codes that link directly to secure portals for uploading sensitive documents or reviewing case status privately." }
|
||||
],
|
||||
useCases: [
|
||||
"Attorney vCard QR on physical business cards",
|
||||
"Consultation booking link on print advertisements",
|
||||
"Client portal access for secure document sharing",
|
||||
"Google review collection for local SEO",
|
||||
"Case study and attorney bio links in brochures"
|
||||
],
|
||||
tools: ["vcard-qr-code", "url-qr-code", "whatsapp-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do attorneys use QR codes for networking?", answer: "Most use vCard QR codes on their business cards to ensure their full contact details are saved directly to a prospect's phone address book." },
|
||||
{ question: "Can a QR code book a legal consultation?", answer: "Yes, the code can link directly to a scheduling tool like Calendly, allowing clients to pick a time without back-and-forth emails." },
|
||||
{ question: "Are QR codes professional enough for law firms?", answer: "Yes, when well-designed and placed on high-quality materials, they demonstrate a firm's commitment to accessibility and modern client service." }
|
||||
],
|
||||
placementTips: [
|
||||
"Physical business card for instant contact saving",
|
||||
"Wait room brochures for attorney bios and case results",
|
||||
"Building directory for consultation booking"
|
||||
],
|
||||
germanKeyword: "Anwaltskanzleien"
|
||||
}
|
||||
];
|
||||
import { IndustryPage } from './industry-pages';
|
||||
|
||||
export const missingIndustries: IndustryPage[] = [
|
||||
{
|
||||
slug: "tattoo-studios",
|
||||
name: "Tattoo Studios",
|
||||
title: "QR Codes for Tattoo Studios",
|
||||
metaTitle: "QR Codes for Tattoo Studios: Portfolios & Aftercare",
|
||||
metaDescription: "Digitalize your tattoo studio with QR codes for artist portfolios, booking, and digital aftercare instructions.",
|
||||
headline: "QR Codes for Tattoo Studios: From Portfolio to Aftercare Instructions",
|
||||
subheadline: "Let clients browse your artists' work and access healing guides with a simple scan in your shop",
|
||||
heroImage: "/marketing/industries/tattoo-studios.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "More Portfolio Views via Window QR" },
|
||||
{ value: "40%", label: "Increase in Online Bookings" },
|
||||
{ value: "50%", label: "Fewer Aftercare Follow-up Questions" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Digital Artist Portfolios", description: "Place QR codes in your studio window or at each station. Clients scan to view the artist's full Instagram or website portfolio without scrolling through a phone." },
|
||||
{ title: "Frictionless Booking", description: "A QR on your business cards and window posters links directly to your booking system, letting potential clients reserve their spot immediately when inspired." },
|
||||
{ title: "Digital Aftercare Guides", description: "Replace paper pamphlets with a QR link to detailed aftercare instructions and healing videos, ensuring clients always have access to the best care advice." }
|
||||
],
|
||||
useCases: [
|
||||
"Window display QR linking to artist portfolios",
|
||||
"Booking portal link on business cards",
|
||||
"Digital aftercare instructions for new tattoos",
|
||||
"Social media growth via Instagram/TikTok QR",
|
||||
"Google Review collection at the end of a session"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do tattoo studios use QR codes for portfolios?", answer: "Studios place QR codes in their window or at artist stations that link directly to digital portfolios, allowing clients to see more work instantly." },
|
||||
{ question: "Can QR codes replace paper aftercare instructions?", answer: "Yes, many studios use QR codes linking to a permanent aftercare page or PDF, which is easier for clients to keep track of than a physical piece of paper." },
|
||||
{ question: "Where is the best place to put a QR code in a tattoo shop?", answer: "The front window, the reception desk, and the individual workstations are the most effective placements for different use cases." }
|
||||
],
|
||||
placementTips: [
|
||||
"Front window for after-hours portfolio browsing",
|
||||
"Reception desk for booking and review collection",
|
||||
"Station mirrors for social media engagement"
|
||||
],
|
||||
germanKeyword: "Tattoostudios"
|
||||
},
|
||||
{
|
||||
slug: "pharmacies",
|
||||
name: "Pharmacies",
|
||||
title: "QR Codes for Pharmacies",
|
||||
metaTitle: "QR Codes for Pharmacies: Prescription Refills & Patient Info",
|
||||
metaDescription: "Improve patient safety and efficiency with QR codes for prescription refills, medication information, and digital advisory leaflets.",
|
||||
headline: "QR Codes for Pharmacies: Digital Refills and Patient Safety Guides",
|
||||
subheadline: "Help patients manage their prescriptions and access medication information with a simple scan at the counter",
|
||||
heroImage: "/marketing/industries/pharmacies.png",
|
||||
statistics: [
|
||||
{ value: "50%", label: "Faster Prescription Refill Requests" },
|
||||
{ value: "2x", label: "Better Patient Medication Adherence" },
|
||||
{ value: "40%", label: "Reduction in Paper Information Sheets" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Instant Prescription Refills", description: "A QR on the prescription bag or bottle links directly to the refill request portal, letting patients order their next dose in seconds." },
|
||||
{ title: "Digital Patient Education", description: "Provide QR codes that link to video tutorials for complex medications (like inhalers or pens) and digital versions of the Patient Advisory Leaflet." },
|
||||
{ title: "Pharmacy-to-Patient Messaging", description: "A QR code at the counter lets patients sign up for SMS or email notifications when their prescription is ready for collection." }
|
||||
],
|
||||
useCases: [
|
||||
"Prescription refill portal link on bags and bottles",
|
||||
"Medication education videos and digital PALs",
|
||||
"Collection notification sign-up at the counter",
|
||||
"Pharmacy hours and emergency contact info",
|
||||
"Health and wellness newsletter sign-up"
|
||||
],
|
||||
tools: ["url-qr-code", "sms-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do QR codes help with medication safety?", answer: "QR codes link to clear digital instructions and videos that help patients understand exactly how to take their medication, reducing errors." },
|
||||
{ question: "Can patients refill prescriptions via QR codes?", answer: "Yes, a QR on the packaging can link directly to a secure portal where the patient can request a refill with one click." },
|
||||
{ question: "Where should pharmacies place QR codes?", answer: "The most effective spots are on prescription bags, medication bottles, and counter-top displays near the pickup area." }
|
||||
],
|
||||
placementTips: [
|
||||
"Printed on every prescription bag for easy refills",
|
||||
"Counter-top display for digital health newsletter sign-up",
|
||||
"Directly on medication packaging for usage videos"
|
||||
],
|
||||
germanKeyword: "Apotheken"
|
||||
},
|
||||
{
|
||||
slug: "car-dealerships",
|
||||
name: "Car Dealerships",
|
||||
title: "QR Codes for Car Dealerships",
|
||||
metaTitle: "QR Codes for Car Dealerships: Virtual Tours & Lead Gen",
|
||||
metaDescription: "Drive sales with QR codes for car spec sheets, virtual vehicle tours, financing calculators, and test drive booking.",
|
||||
headline: "QR Codes for Car Dealerships: Your 24/7 Digital Sales Floor",
|
||||
subheadline: "Let buyers explore every vehicle's specs, history, and financing options from the window—even when you're closed",
|
||||
heroImage: "/marketing/industries/car-dealerships.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "More Weekend Leads via Window QRs" },
|
||||
{ value: "3x", label: "Higher Engagement with Spec Sheets" },
|
||||
{ value: "45%", label: "Faster Test Drive Appointment Booking" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Interactive Window Stickers", description: "A QR on the vehicle window sticker links to the full digital spec sheet, high-res photos, car history reports, and a financing calculator." },
|
||||
{ title: "After-Hours Lead Generation", description: "Capture interest 24/7. Even when the dealership is closed, passersby scan the car's QR to view details and book a test drive for the following morning." },
|
||||
{ title: "Seamless Test Drive Booking", description: "Reduce friction by letting customers book their spot directly from their phone while they are standing next to the vehicle they like." }
|
||||
],
|
||||
useCases: [
|
||||
"Virtual tour and spec sheet from window stickers",
|
||||
"Immediate test drive booking link on each car",
|
||||
"Financing and trade-in value calculators",
|
||||
"Service department booking and status alerts",
|
||||
"Loyalty and maintenance reminder QR codes"
|
||||
],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "call-qr-code-generator"],
|
||||
faq: [
|
||||
{ question: "How do QR codes help sell more cars?", answer: "QR codes provide instant information on vehicle specs and pricing, keeping customers engaged and capturing leads even after hours." },
|
||||
{ question: "Can a QR code calculate car financing?", answer: "Yes, you can link the QR to a tool that automatically pulls the car's price into a financing calculator for the customer." },
|
||||
{ question: "Where should dealer QR codes be placed?", answer: "The most important placement is on the driver's side window of every vehicle on the lot, followed by the sales desks and service area." }
|
||||
],
|
||||
placementTips: [
|
||||
"Driver-side window of every car on the lot",
|
||||
"Service waiting area for maintenance booking",
|
||||
"Sales desk for digital business card exchange"
|
||||
],
|
||||
germanKeyword: "Autohäuser"
|
||||
},
|
||||
{
|
||||
slug: "museums",
|
||||
name: "Museums",
|
||||
title: "QR Codes for Museums",
|
||||
metaTitle: "QR Codes for Museums: Interactive Tours & Exhibit Info",
|
||||
metaDescription: "Enhance the visitor experience with QR codes for audio guides, interactive exhibit maps, and digital donations.",
|
||||
headline: "QR Codes for Museums: Bring Artifacts to Life Digitally",
|
||||
subheadline: "Offer multilingual audio guides and interactive storytelling at every exhibit without bulky hardware",
|
||||
heroImage: "/marketing/industries/museums.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Higher Engagement with Digital Guides" },
|
||||
{ value: "50%", label: "Lower Cost vs. Physical Audio Hardware" },
|
||||
{ value: "2x", label: "Increase in Small Digital Donations" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Smartphone Audio Guides", description: "Replace expensive, unhygienic audio guide hardware with QR codes. Visitors scan their phone to hear the story of every exhibit in their own language." },
|
||||
{ title: "Interactive Storytelling", description: "Link QR codes to videos, 3D models, or high-res maps that provide a deeper dive into the history of the artifacts on display." },
|
||||
{ title: "Frictionless Donations", description: "A QR code near the exit or major exhibits makes it easy for inspired visitors to leave a small digital donation with one tap." }
|
||||
],
|
||||
useCases: [
|
||||
"Self-guided audio tours from exhibit labels",
|
||||
"Multilingual descriptions for international guests",
|
||||
"Digital floor maps and facility wayfinding",
|
||||
"Donation and membership enrollment links",
|
||||
"Museum shop product info and online ordering"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "youtube-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do museums use QR codes for audio guides?", answer: "Museums place QR codes on exhibit labels that link to hosted audio files, allowing visitors to use their own phones and headphones." },
|
||||
{ question: "Can QR codes help with museum accessibility?", answer: "Absolutely. They can link to sign language videos, high-contrast text descriptions, or translations for any exhibit." },
|
||||
{ question: "Where should museum QR codes be placed?", answer: "On exhibit labels, the main floor plan in the lobby, and on pillars near the cafe or gift shop." }
|
||||
],
|
||||
placementTips: [
|
||||
"Bottom corner of every major exhibit label",
|
||||
"Main entrance floor map pillar",
|
||||
"Checkout and exit areas for donations"
|
||||
],
|
||||
germanKeyword: "Museen"
|
||||
},
|
||||
{
|
||||
slug: "law-firms",
|
||||
name: "Law Firms",
|
||||
title: "QR Codes for Law Firms",
|
||||
metaTitle: "QR Codes for Law Firms: Attorney Portfolios & Consultations",
|
||||
metaDescription: "Modernize your legal practice with QR codes for digital business cards, consultation booking, and secure document access.",
|
||||
headline: "QR Codes for Law Firms: Professional Connectivity and Client Onboarding",
|
||||
subheadline: "Let prospective clients book a consultation and save your contact details with a simple scan of your card or signage",
|
||||
heroImage: "/marketing/industries/law-firms.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "Professional Contact Saves via vCard" },
|
||||
{ value: "35%", label: "More Online Consultation Requests" },
|
||||
{ value: "40%", label: "Faster Client Document Submission" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Digital vCards for Networking", description: "Ensure potential clients and partners actually save your details. A vCard QR on your business card adds your contact info to their phone instantly." },
|
||||
{ title: "Instant Consultation Booking", description: "A QR on your brochures or building signage links directly to your calendar, allowing prospects to book a discovery call when legal needs are urgent." },
|
||||
{ title: "Secure Document Portals", description: "Provide clients with QR codes that link directly to secure portals for uploading sensitive documents or reviewing case status privately." }
|
||||
],
|
||||
useCases: [
|
||||
"Attorney vCard QR on physical business cards",
|
||||
"Consultation booking link on print advertisements",
|
||||
"Client portal access for secure document sharing",
|
||||
"Google review collection for local SEO",
|
||||
"Case study and attorney bio links in brochures"
|
||||
],
|
||||
tools: ["vcard-qr-code", "url-qr-code", "whatsapp-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do attorneys use QR codes for networking?", answer: "Most use vCard QR codes on their business cards to ensure their full contact details are saved directly to a prospect's phone address book." },
|
||||
{ question: "Can a QR code book a legal consultation?", answer: "Yes, the code can link directly to a scheduling tool like Calendly, allowing clients to pick a time without back-and-forth emails." },
|
||||
{ question: "Are QR codes professional enough for law firms?", answer: "Yes, when well-designed and placed on high-quality materials, they demonstrate a firm's commitment to accessibility and modern client service." }
|
||||
],
|
||||
placementTips: [
|
||||
"Physical business card for instant contact saving",
|
||||
"Wait room brochures for attorney bios and case results",
|
||||
"Building directory for consultation booking"
|
||||
],
|
||||
germanKeyword: "Anwaltskanzleien"
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,199 +1,199 @@
|
||||
import { IndustryPage } from './industry-pages';
|
||||
|
||||
export const missingIndustries2: IndustryPage[] = [
|
||||
{
|
||||
slug: "accountants",
|
||||
name: "Accountants",
|
||||
title: "QR Codes for Accountants",
|
||||
metaTitle: "QR Codes for Accountants: Secure Portals & Consultations",
|
||||
metaDescription: "Digitalize your accountancy firm with QR codes for secure document portals, consultation booking, and digital business cards.",
|
||||
headline: "QR Codes for Accountants: Document Security and Client Efficiency",
|
||||
subheadline: "Let clients upload documents and book consultations from a simple scan on your brochures or business cards",
|
||||
heroImage: "/marketing/industries/accountants.png",
|
||||
statistics: [
|
||||
{ value: "40%", label: "Faster Tax Document Collection" },
|
||||
{ value: "2x", label: "More Online Discovery Calls" },
|
||||
{ value: "45%", label: "More Recurring Client Portal Logins" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Secure Document Upload Portals", description: "Provide clients with a QR code in their tax pack that links directly to their secure upload portal, making document submission as easy as taking a photo." },
|
||||
{ title: "One-Tap Appointment Booking", description: "A QR on your lobby signage or brochures links to your calendar, allowing busy business owners to book their quarterly reviews or tax meetings in seconds." },
|
||||
{ title: "Professional vCard Branding", description: "Maintain a modern image by including a vCard QR on your business card, ensuring clients always have your full contact info and office hours on their phone." }
|
||||
],
|
||||
useCases: [
|
||||
"Tax document portal link in physical mailers",
|
||||
"Discovery call and consultation booking",
|
||||
"Office location and building directions",
|
||||
"vCard link on employee business cards",
|
||||
"Newsletter sign-up for tax law updates"
|
||||
],
|
||||
tools: ["vcard-qr-code", "url-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do accounting firms use QR codes for security?", answer: "Codes link directly to secure, encrypted portals, ensuring sensitive tax documents are never sent via unencrypted email." },
|
||||
{ question: "Can a QR code help with tax season document gathering?", answer: "Yes, by linking a QR in the annual tax letter to the personal checklist and upload portal, accounting firms significantly speed up the intake process." },
|
||||
{ question: "Where should an accountant place a QR code?", answer: "On annual tax letters, business cards, the office reception desk, and inside client welcome folders." }
|
||||
],
|
||||
placementTips: [
|
||||
"Annual tax letter for portal access and document guide",
|
||||
"Physical business card for instant contact saving",
|
||||
"Reception desk sign for WiFi and appointment booking"
|
||||
],
|
||||
germanKeyword: "Steuerberater"
|
||||
},
|
||||
{
|
||||
slug: "insurance-agencies",
|
||||
name: "Insurance Agencies",
|
||||
title: "QR Codes for Insurance Agencies",
|
||||
metaTitle: "QR Codes for Insurance: Claim Filing & Policy Access",
|
||||
metaDescription: "Improve client service and lead generation with QR codes for claim filing, policy document access, and consultation booking.",
|
||||
headline: "QR Codes for Insurance Agencies: Instant Policy Access and Faster Claims",
|
||||
subheadline: "Give your clients a direct line to support and claims portals from every policy document and ID card",
|
||||
heroImage: "/marketing/industries/insurance-agencies.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "Faster Claim Reporting via ID Card QR" },
|
||||
{ value: "30%", label: "More Online Policy Quote Requests" },
|
||||
{ value: "50%", label: "Reductions in Inbound Status Calls" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Immediate Claim Submission", description: "A QR code on the physical insurance ID card or policy folder links directly to the claim portal, helping clients file a report immediately after an incident." },
|
||||
{ title: "Instant Policy Access", description: "Link a QR in your welcome pack to the client's current policy PDF and coverage overview, ensuring they always have the details at hand when they need them most." },
|
||||
{ title: "Lead Generation from Print advertising", description: "QR codes on direct mail and local signage link prospects directly to your quote tool, capturing high-intent leads before they look elsewhere." }
|
||||
],
|
||||
useCases: [
|
||||
"Claim reporting link on policy ID cards",
|
||||
"Digital policy documents and coverage info",
|
||||
"Quote request form link on direct mail ads",
|
||||
"Consultation booking with a local agent",
|
||||
"Emergency contact numbers and chat link"
|
||||
],
|
||||
tools: ["vcard-qr-code", "url-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do insurance agencies use QR codes on ID cards?", answer: "A QR on the card links directly to the mobile claim filing portal or the agent's emergency contact numbers—providing help in high-stress moments." },
|
||||
{ question: "Can QR codes help generate more insurance quotes?", answer: "Yes, by adding a QR to local signage and direct mail, agencies bridge the gap between people seeing an ad and getting a price instantly on their phone." },
|
||||
{ question: "Where should insurance QR codes be placed?", answer: "The back of ID cards, the front page of policy folders, and on all direct mail flyers are the most effective areas." }
|
||||
],
|
||||
placementTips: [
|
||||
"Back of the insurance ID card for claims and support",
|
||||
"Welcome folder with policy guide and agent vCard",
|
||||
"Direct mail flyer for instant quote requests"
|
||||
],
|
||||
germanKeyword: "Versicherungsagenturen"
|
||||
},
|
||||
{
|
||||
slug: "bookstores",
|
||||
name: "Bookstores",
|
||||
title: "QR Codes for Bookstores",
|
||||
metaTitle: "QR Codes for Bookstores: Author Interviews & Loyalty",
|
||||
metaDescription: "Engage book lovers with QR codes for author interviews, reading lists, loyalty programs, and online ordering for out-of-stock titles.",
|
||||
headline: "QR Codes for Bookstores: Bringing the Digital World to the Shelves",
|
||||
subheadline: "Offer book trailers, author interviews, and effortless online ordering from every shelf tag and window display",
|
||||
heroImage: "/marketing/industries/bookstores.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "More Loyalty Program Enrollments" },
|
||||
{ value: "40%", label: "Higher Order Vol for Out-of-Stock Books" },
|
||||
{ value: "2x", label: "Increase in Reading-List Engagement" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Shelf-Edge Storytelling", description: "A QR on the shelf tag links to author interviews, book trailers, or community reviews—helping customers discover their next favorite read." },
|
||||
{ title: "Digital Loyalty and Rewards", description: "A QR at the checkout or on the bookmark given with each purchase allows customers to join your loyalty program without filling out a paper form." },
|
||||
{ title: "Order-from-Shelf Capability", description: "If a popular title is out of stock, a QR on the shelf lets the customer order the book for home delivery immediately, preventing them from leaving for a competitor." }
|
||||
],
|
||||
useCases: [
|
||||
"Author interviews and book trailers on shelf tags",
|
||||
"Loyalty and membership sign-up at checkout",
|
||||
"Out-of-stock ordering and home delivery portal",
|
||||
"Digital reading-list and staff-recommendation guides",
|
||||
"Event sign-up for readings and book signings"
|
||||
],
|
||||
tools: ["url-qr-code", "youtube-qr-code", "instagram-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do bookstores use QR codes on shelves?", answer: "Bookstores place codes on shelf edges that link to author interviews, similar book recommendations, or staff review videos." },
|
||||
{ question: "Can QR codes help with bookstore inventory issues?", answer: "Yes, if a book is sold out, a QR on the empty shelf space can link to your online shop for immediate home delivery." },
|
||||
{ question: "Where should bookstore QR codes be placed?", answer: "On shelf tags under key titles, the front window for event info, and the checkout counter for loyalty program sign-ups." }
|
||||
],
|
||||
placementTips: [
|
||||
"Shelf-edge label for staff-recommended titles",
|
||||
"Front door poster for upcoming author events",
|
||||
"Bookmarks given with every purchase linking to loyalty"
|
||||
],
|
||||
germanKeyword: "Buchhandlungen"
|
||||
},
|
||||
{
|
||||
slug: "libraries",
|
||||
name: "Libraries",
|
||||
title: "QR Codes for Libraries",
|
||||
metaTitle: "QR Codes for Libraries: Digital Catalogs & Multimedia",
|
||||
metaDescription: "Modernize your library with QR codes for digital catalog access, e-book downloads, event registration, and multimedia learning guides.",
|
||||
headline: "QR Codes for Libraries: A Digital Bridge to Every Shelf",
|
||||
subheadline: "Let visitors download e-books, listen to audio samples, and register for community events with a simple scan",
|
||||
heroImage: "/marketing/industries/libraries.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "More Multimedia Resource Access" },
|
||||
{ value: "40%", label: "Increase in Event Registrations" },
|
||||
{ value: "50%", label: "Faster E-Book and Audio Sample Access" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Instant E-Source Access", description: "Place QR codes on physical shelves that link directly to the e-book or audiobook versions of those titles—ideal for when the physical copy is checked out." },
|
||||
{ title: "Digital Event Calendars", description: "A QR on the main announcement board or entrance door links to your full digital event calendar, allowing visitors to register for workshops and storytime sessions." },
|
||||
{ title: "Learning Companion Guides", description: "For study groups and non-fiction sections, QR codes can link to high-quality external resources, video lessons, or digital bibliographies for deeper research." }
|
||||
],
|
||||
useCases: [
|
||||
"E-book and audiobook links on physical shelves",
|
||||
"Event registration and community calendar sign-up",
|
||||
"Museum pass and library card digital registration",
|
||||
"Multimedia guides for non-fiction and research sections",
|
||||
"Silent study WiFi access from desk QR codes"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "wifi-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do libraries use QR codes for e-books?", answer: "Libraries place QR codes next to physical books that link to the e-book version in their digital collection, ensuring access even when shelves are empty." },
|
||||
{ question: "Can QR codes help with library event management?", answer: "Yes, a single QR on an event poster links to the registration form and automatically adds the library event to the visitor's phone calendar." },
|
||||
{ question: "Where should library QR codes be placed?", answer: "On bookshelf ends, study hall desks for WiFi, and the main entrance pillar for the event calendar." }
|
||||
],
|
||||
placementTips: [
|
||||
"End of setiap book aisle for category guides",
|
||||
"Study desks for quick WiFi connection and quiet rules",
|
||||
"Main entrance notice board for today's events"
|
||||
],
|
||||
germanKeyword: "Bibliotheken"
|
||||
},
|
||||
{
|
||||
slug: "universities",
|
||||
name: "Universities & Colleges",
|
||||
title: "QR Codes for Universities & Colleges",
|
||||
metaTitle: "QR Codes for Universities: Campus Maps & Event Tracking",
|
||||
metaDescription: "Improve the campus experience with QR codes for interactive maps, attendance tracking, cafeteria menus, and student resource portals.",
|
||||
headline: "QR Codes for Universities: Connectivity Across Campus",
|
||||
subheadline: "Modernize your student experience with instant access to course materials, maps, and events from any campus sign",
|
||||
heroImage: "/marketing/industries/universities.png",
|
||||
statistics: [
|
||||
{ value: "50%", label: "Faster Orientation Week Navigation" },
|
||||
{ value: "3x", label: "More Student Event RSVP Completions" },
|
||||
{ value: "40%", label: "Reduction in Printed Campus Materials" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Interactive Campus Wayfinding", description: "QR codes on pillars and building signs link to real-time interactive campus maps, helping new students find lecture halls and facilities effortlessly." },
|
||||
{ title: "Instant Course Material Distribution", description: "Professors place QR codes on lecture slides linking to PDFs, reference lists, or digital quizzes—ensuring every student has the material before the class ends." },
|
||||
{ title: "Seamless Event Engagement", description: "A QR on the student union board or event poster links to the ticketing and RSVP portal, sending event reminders directly to student phones." }
|
||||
],
|
||||
useCases: [
|
||||
"Interactive campus maps at entrances and pillars",
|
||||
"Course material PDFs for lecture distribution",
|
||||
"Student union and community event sign-ups",
|
||||
"Cafeteria menus and pre-order payment links",
|
||||
"Library resource booking and silent study WiFi"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "wifi-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do universities use QR codes for wayfinding?", answer: "QR codes on campus maps and building signs link to mobile-friendly interactive maps that show exactly where students are and how to get to their next class." },
|
||||
{ question: "Can QR codes track student attendance?", answer: "Yes, many professors use unique QR codes at the start of a lecture that students scan to verify their presence via a secure student portal." },
|
||||
{ question: "Where are the best places for QR codes on campus?", answer: "Main entrance gates, outside lecture halls, on social and sports club posters, and at the cafeteria payment counter." }
|
||||
],
|
||||
placementTips: [
|
||||
"Main campus entrance pillars for the digital map",
|
||||
"Outside lecture halls for course schedules and materials",
|
||||
"Student hub notice board for social and club events"
|
||||
],
|
||||
germanKeyword: "Universitäten"
|
||||
}
|
||||
];
|
||||
import { IndustryPage } from './industry-pages';
|
||||
|
||||
export const missingIndustries2: IndustryPage[] = [
|
||||
{
|
||||
slug: "accountants",
|
||||
name: "Accountants",
|
||||
title: "QR Codes for Accountants",
|
||||
metaTitle: "QR Codes for Accountants: Secure Portals & Consultations",
|
||||
metaDescription: "Digitalize your accountancy firm with QR codes for secure document portals, consultation booking, and digital business cards.",
|
||||
headline: "QR Codes for Accountants: Document Security and Client Efficiency",
|
||||
subheadline: "Let clients upload documents and book consultations from a simple scan on your brochures or business cards",
|
||||
heroImage: "/marketing/industries/accountants.png",
|
||||
statistics: [
|
||||
{ value: "40%", label: "Faster Tax Document Collection" },
|
||||
{ value: "2x", label: "More Online Discovery Calls" },
|
||||
{ value: "45%", label: "More Recurring Client Portal Logins" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Secure Document Upload Portals", description: "Provide clients with a QR code in their tax pack that links directly to their secure upload portal, making document submission as easy as taking a photo." },
|
||||
{ title: "One-Tap Appointment Booking", description: "A QR on your lobby signage or brochures links to your calendar, allowing busy business owners to book their quarterly reviews or tax meetings in seconds." },
|
||||
{ title: "Professional vCard Branding", description: "Maintain a modern image by including a vCard QR on your business card, ensuring clients always have your full contact info and office hours on their phone." }
|
||||
],
|
||||
useCases: [
|
||||
"Tax document portal link in physical mailers",
|
||||
"Discovery call and consultation booking",
|
||||
"Office location and building directions",
|
||||
"vCard link on employee business cards",
|
||||
"Newsletter sign-up for tax law updates"
|
||||
],
|
||||
tools: ["vcard-qr-code", "url-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do accounting firms use QR codes for security?", answer: "Codes link directly to secure, encrypted portals, ensuring sensitive tax documents are never sent via unencrypted email." },
|
||||
{ question: "Can a QR code help with tax season document gathering?", answer: "Yes, by linking a QR in the annual tax letter to the personal checklist and upload portal, accounting firms significantly speed up the intake process." },
|
||||
{ question: "Where should an accountant place a QR code?", answer: "On annual tax letters, business cards, the office reception desk, and inside client welcome folders." }
|
||||
],
|
||||
placementTips: [
|
||||
"Annual tax letter for portal access and document guide",
|
||||
"Physical business card for instant contact saving",
|
||||
"Reception desk sign for WiFi and appointment booking"
|
||||
],
|
||||
germanKeyword: "Steuerberater"
|
||||
},
|
||||
{
|
||||
slug: "insurance-agencies",
|
||||
name: "Insurance Agencies",
|
||||
title: "QR Codes for Insurance Agencies",
|
||||
metaTitle: "QR Codes for Insurance: Claim Filing & Policy Access",
|
||||
metaDescription: "Improve client service and lead generation with QR codes for claim filing, policy document access, and consultation booking.",
|
||||
headline: "QR Codes for Insurance Agencies: Instant Policy Access and Faster Claims",
|
||||
subheadline: "Give your clients a direct line to support and claims portals from every policy document and ID card",
|
||||
heroImage: "/marketing/industries/insurance-agencies.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "Faster Claim Reporting via ID Card QR" },
|
||||
{ value: "30%", label: "More Online Policy Quote Requests" },
|
||||
{ value: "50%", label: "Reductions in Inbound Status Calls" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Immediate Claim Submission", description: "A QR code on the physical insurance ID card or policy folder links directly to the claim portal, helping clients file a report immediately after an incident." },
|
||||
{ title: "Instant Policy Access", description: "Link a QR in your welcome pack to the client's current policy PDF and coverage overview, ensuring they always have the details at hand when they need them most." },
|
||||
{ title: "Lead Generation from Print advertising", description: "QR codes on direct mail and local signage link prospects directly to your quote tool, capturing high-intent leads before they look elsewhere." }
|
||||
],
|
||||
useCases: [
|
||||
"Claim reporting link on policy ID cards",
|
||||
"Digital policy documents and coverage info",
|
||||
"Quote request form link on direct mail ads",
|
||||
"Consultation booking with a local agent",
|
||||
"Emergency contact numbers and chat link"
|
||||
],
|
||||
tools: ["vcard-qr-code", "url-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do insurance agencies use QR codes on ID cards?", answer: "A QR on the card links directly to the mobile claim filing portal or the agent's emergency contact numbers—providing help in high-stress moments." },
|
||||
{ question: "Can QR codes help generate more insurance quotes?", answer: "Yes, by adding a QR to local signage and direct mail, agencies bridge the gap between people seeing an ad and getting a price instantly on their phone." },
|
||||
{ question: "Where should insurance QR codes be placed?", answer: "The back of ID cards, the front page of policy folders, and on all direct mail flyers are the most effective areas." }
|
||||
],
|
||||
placementTips: [
|
||||
"Back of the insurance ID card for claims and support",
|
||||
"Welcome folder with policy guide and agent vCard",
|
||||
"Direct mail flyer for instant quote requests"
|
||||
],
|
||||
germanKeyword: "Versicherungsagenturen"
|
||||
},
|
||||
{
|
||||
slug: "bookstores",
|
||||
name: "Bookstores",
|
||||
title: "QR Codes for Bookstores",
|
||||
metaTitle: "QR Codes for Bookstores: Author Interviews & Loyalty",
|
||||
metaDescription: "Engage book lovers with QR codes for author interviews, reading lists, loyalty programs, and online ordering for out-of-stock titles.",
|
||||
headline: "QR Codes for Bookstores: Bringing the Digital World to the Shelves",
|
||||
subheadline: "Offer book trailers, author interviews, and effortless online ordering from every shelf tag and window display",
|
||||
heroImage: "/marketing/industries/bookstores.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "More Loyalty Program Enrollments" },
|
||||
{ value: "40%", label: "Higher Order Vol for Out-of-Stock Books" },
|
||||
{ value: "2x", label: "Increase in Reading-List Engagement" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Shelf-Edge Storytelling", description: "A QR on the shelf tag links to author interviews, book trailers, or community reviews—helping customers discover their next favorite read." },
|
||||
{ title: "Digital Loyalty and Rewards", description: "A QR at the checkout or on the bookmark given with each purchase allows customers to join your loyalty program without filling out a paper form." },
|
||||
{ title: "Order-from-Shelf Capability", description: "If a popular title is out of stock, a QR on the shelf lets the customer order the book for home delivery immediately, preventing them from leaving for a competitor." }
|
||||
],
|
||||
useCases: [
|
||||
"Author interviews and book trailers on shelf tags",
|
||||
"Loyalty and membership sign-up at checkout",
|
||||
"Out-of-stock ordering and home delivery portal",
|
||||
"Digital reading-list and staff-recommendation guides",
|
||||
"Event sign-up for readings and book signings"
|
||||
],
|
||||
tools: ["url-qr-code", "youtube-qr-code", "instagram-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do bookstores use QR codes on shelves?", answer: "Bookstores place codes on shelf edges that link to author interviews, similar book recommendations, or staff review videos." },
|
||||
{ question: "Can QR codes help with bookstore inventory issues?", answer: "Yes, if a book is sold out, a QR on the empty shelf space can link to your online shop for immediate home delivery." },
|
||||
{ question: "Where should bookstore QR codes be placed?", answer: "On shelf tags under key titles, the front window for event info, and the checkout counter for loyalty program sign-ups." }
|
||||
],
|
||||
placementTips: [
|
||||
"Shelf-edge label for staff-recommended titles",
|
||||
"Front door poster for upcoming author events",
|
||||
"Bookmarks given with every purchase linking to loyalty"
|
||||
],
|
||||
germanKeyword: "Buchhandlungen"
|
||||
},
|
||||
{
|
||||
slug: "libraries",
|
||||
name: "Libraries",
|
||||
title: "QR Codes for Libraries",
|
||||
metaTitle: "QR Codes for Libraries: Digital Catalogs & Multimedia",
|
||||
metaDescription: "Modernize your library with QR codes for digital catalog access, e-book downloads, event registration, and multimedia learning guides.",
|
||||
headline: "QR Codes for Libraries: A Digital Bridge to Every Shelf",
|
||||
subheadline: "Let visitors download e-books, listen to audio samples, and register for community events with a simple scan",
|
||||
heroImage: "/marketing/industries/libraries.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "More Multimedia Resource Access" },
|
||||
{ value: "40%", label: "Increase in Event Registrations" },
|
||||
{ value: "50%", label: "Faster E-Book and Audio Sample Access" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Instant E-Source Access", description: "Place QR codes on physical shelves that link directly to the e-book or audiobook versions of those titles—ideal for when the physical copy is checked out." },
|
||||
{ title: "Digital Event Calendars", description: "A QR on the main announcement board or entrance door links to your full digital event calendar, allowing visitors to register for workshops and storytime sessions." },
|
||||
{ title: "Learning Companion Guides", description: "For study groups and non-fiction sections, QR codes can link to high-quality external resources, video lessons, or digital bibliographies for deeper research." }
|
||||
],
|
||||
useCases: [
|
||||
"E-book and audiobook links on physical shelves",
|
||||
"Event registration and community calendar sign-up",
|
||||
"Museum pass and library card digital registration",
|
||||
"Multimedia guides for non-fiction and research sections",
|
||||
"Silent study WiFi access from desk QR codes"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "wifi-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do libraries use QR codes for e-books?", answer: "Libraries place QR codes next to physical books that link to the e-book version in their digital collection, ensuring access even when shelves are empty." },
|
||||
{ question: "Can QR codes help with library event management?", answer: "Yes, a single QR on an event poster links to the registration form and automatically adds the library event to the visitor's phone calendar." },
|
||||
{ question: "Where should library QR codes be placed?", answer: "On bookshelf ends, study hall desks for WiFi, and the main entrance pillar for the event calendar." }
|
||||
],
|
||||
placementTips: [
|
||||
"End of setiap book aisle for category guides",
|
||||
"Study desks for quick WiFi connection and quiet rules",
|
||||
"Main entrance notice board for today's events"
|
||||
],
|
||||
germanKeyword: "Bibliotheken"
|
||||
},
|
||||
{
|
||||
slug: "universities",
|
||||
name: "Universities & Colleges",
|
||||
title: "QR Codes for Universities & Colleges",
|
||||
metaTitle: "QR Codes for Universities: Campus Maps & Event Tracking",
|
||||
metaDescription: "Improve the campus experience with QR codes for interactive maps, attendance tracking, cafeteria menus, and student resource portals.",
|
||||
headline: "QR Codes for Universities: Connectivity Across Campus",
|
||||
subheadline: "Modernize your student experience with instant access to course materials, maps, and events from any campus sign",
|
||||
heroImage: "/marketing/industries/universities.png",
|
||||
statistics: [
|
||||
{ value: "50%", label: "Faster Orientation Week Navigation" },
|
||||
{ value: "3x", label: "More Student Event RSVP Completions" },
|
||||
{ value: "40%", label: "Reduction in Printed Campus Materials" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Interactive Campus Wayfinding", description: "QR codes on pillars and building signs link to real-time interactive campus maps, helping new students find lecture halls and facilities effortlessly." },
|
||||
{ title: "Instant Course Material Distribution", description: "Professors place QR codes on lecture slides linking to PDFs, reference lists, or digital quizzes—ensuring every student has the material before the class ends." },
|
||||
{ title: "Seamless Event Engagement", description: "A QR on the student union board or event poster links to the ticketing and RSVP portal, sending event reminders directly to student phones." }
|
||||
],
|
||||
useCases: [
|
||||
"Interactive campus maps at entrances and pillars",
|
||||
"Course material PDFs for lecture distribution",
|
||||
"Student union and community event sign-ups",
|
||||
"Cafeteria menus and pre-order payment links",
|
||||
"Library resource booking and silent study WiFi"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "wifi-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do universities use QR codes for wayfinding?", answer: "QR codes on campus maps and building signs link to mobile-friendly interactive maps that show exactly where students are and how to get to their next class." },
|
||||
{ question: "Can QR codes track student attendance?", answer: "Yes, many professors use unique QR codes at the start of a lecture that students scan to verify their presence via a secure student portal." },
|
||||
{ question: "Where are the best places for QR codes on campus?", answer: "Main entrance gates, outside lecture halls, on social and sports club posters, and at the cafeteria payment counter." }
|
||||
],
|
||||
placementTips: [
|
||||
"Main campus entrance pillars for the digital map",
|
||||
"Outside lecture halls for course schedules and materials",
|
||||
"Student hub notice board for social and club events"
|
||||
],
|
||||
germanKeyword: "Universitäten"
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,199 +1,199 @@
|
||||
import { IndustryPage } from './industry-pages';
|
||||
|
||||
export const missingIndustries3: IndustryPage[] = [
|
||||
{
|
||||
slug: "schools",
|
||||
name: "Schools",
|
||||
title: "QR Codes for Schools",
|
||||
metaTitle: "QR Codes for Schools: Parent Communication & Resources",
|
||||
metaDescription: "Improve parent-teacher communication and student resource sharing with QR codes for newsletters, homework portals, and event sign-ups.",
|
||||
headline: "QR Codes for Schools: Simple Parent-Teacher Connectivity",
|
||||
subheadline: "Let parents access the latest newsletter and homework updates from the school gate or lobby",
|
||||
heroImage: "/marketing/industries/schools.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "More Parent Newsletter Read Rates" },
|
||||
{ value: "40%", label: "Faster Event RSVP Responses" },
|
||||
{ value: "3x", label: "Higher Resource Hub Engagement" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Parent Newsletter Access", description: "Place a QR code at the school gate or reception. Parents scan while picking up children to read the latest digital newsletter or school announcements." },
|
||||
{ title: "Digital Homework Portals", description: "Direct links to the school's virtual learning environment (VLE) on homework planners ensure students and parents always find assignments." },
|
||||
{ title: "One-Tap Event RSVP", description: "QR codes on paper flyers for school plays, sports days, and parent-teacher meetings link to the digital sign-up form instantly." }
|
||||
],
|
||||
useCases: [
|
||||
"Parent newsletter link at school gates",
|
||||
"Homework and learning portal links on planners",
|
||||
"School play and sports day event RSVPs",
|
||||
"Cafeteria payment and menu links",
|
||||
"Teacher vCard and contact info for meetings"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "event-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do schools use QR codes for parents?", answer: "Schools place codes at gates or in reception that link to newsletters and school announcements—making mobile-first communication effortless for parents." },
|
||||
{ question: "Can QR codes help with school event planning?", answer: "Yes, by putting a QR on any paper flyer, schools can get instant RSVPs and track attendance for events like plays and fairs." },
|
||||
{ question: "Where should school QR codes be placed?", answer: "The school gates, the main reception desk, student homework planners, and on all physical event posters." }
|
||||
],
|
||||
placementTips: [
|
||||
"Main entrance gate for parent newsletters",
|
||||
"Reception desk sign for school visitor sign-in",
|
||||
"Physical event posters for digital RSVPs"
|
||||
],
|
||||
germanKeyword: "Schulen"
|
||||
},
|
||||
{
|
||||
slug: "churches",
|
||||
name: "Churches & Places of Worship",
|
||||
title: "QR Codes for Churches & Worship",
|
||||
metaTitle: "QR Codes for Churches: Digital Giving & Bulletins",
|
||||
metaDescription: "Increase community engagement with QR codes for digital donations, mobile bulletins, event calendars, and newsletter sign-ups.",
|
||||
headline: "QR Codes for Churches: Digital Bulletins and Simple Giving",
|
||||
subheadline: "Help your congregation stay connected with digital programs, hymnals, and one-tap donations from every pew",
|
||||
heroImage: "/marketing/industries/churches.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Increase in Weekly Digital Giving" },
|
||||
{ value: "50%", label: "Reduction in Printed Bulletin Costs" },
|
||||
{ value: "2x", label: "More Community Event Registrations" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Frictionless Digital Giving", description: "A QR code on the back of every pew or in the weekly program links directly to your secure donation portal—making it easy for the modern congregation to give." },
|
||||
{ title: "Digital Bulletins and Hymnals", description: "Reduce your environmental impact and printing costs by linking to the week's program, lyrics, and announcements via a single scan." },
|
||||
{ title: "Community Calendar and Groups", description: "Link QR codes to your small group sign-up forms and community events calendar, encouraging deeper engagement beyond Sunday service." }
|
||||
],
|
||||
useCases: [
|
||||
"Digital donation link on pews and programs",
|
||||
"Sunday bulletin and hymnal PDF access",
|
||||
"Community event and small group sign-ups",
|
||||
"Monthly newsletter and news sign-up links",
|
||||
"Prayer request form from the back of the pew"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "event-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do churches use QR codes for giving?", answer: "Churches place codes on pews or in bulletins that link directly to their payment provider, letting the congregation donate securely from their phones." },
|
||||
{ question: "Can QR codes replace printed church bulletins?", answer: "Yes, many congregations now scan a QR at the entrance to access the digital program and hymnal—saving thousands in annual printing costs." },
|
||||
{ question: "Where should churches place QR codes?", answer: "The back of every pew, the main lobby notice board, and the front cover of the physical weekly program." }
|
||||
],
|
||||
placementTips: [
|
||||
"Back of every pew for donations and prayers",
|
||||
"Lobby notice board for community events",
|
||||
"Front of paper programs for digital access"
|
||||
],
|
||||
germanKeyword: "Kirchen"
|
||||
},
|
||||
{
|
||||
slug: "art-galleries",
|
||||
name: "Art Galleries",
|
||||
title: "QR Codes for Art Galleries",
|
||||
metaTitle: "QR Codes for Art Galleries: Artist Bios & Purchasing",
|
||||
metaDescription: "Engage art collectors with QR codes for artist biographies, price lists, virtual exhibitions, and purchase inquiries.",
|
||||
headline: "QR Codes for Art Galleries: Storytelling and Seamless Sales",
|
||||
subheadline: "Let visitors dive deeper into every artwork with artist biographies and purchase links at every label",
|
||||
heroImage: "/marketing/industries/art-galleries.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "More Artist Bio Views via Exhibit QR" },
|
||||
{ value: "40%", label: "Faster Inquiry-to-Sale Conversion" },
|
||||
{ value: "2x", label: "Higher Instagram Follower Growth" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Deep-Dive Artist Biographies", description: "A QR on the wall label links to the artist's full biography, studio videos, and available works—providing context without crowding the gallery walls." },
|
||||
{ title: "Immediate Purchase Inquiries", description: "Link QR codes to a direct inquiry form or purchase link for every artwork, allowing collectors to start the buying process the moment they feel inspired." },
|
||||
{ title: "Virtual Exhibition Catalogs", description: "Give visitors a QR code at the entrance linking to the full digital exhibition catalog—which they can easily share with fellow art lovers." }
|
||||
],
|
||||
useCases: [
|
||||
"Artist bio and video link on each artwork label",
|
||||
"Price list and purchase inquiry portal",
|
||||
"Digital exhibition catalog at the entrance",
|
||||
"Social media following and artist tags",
|
||||
"Mailing list and upcoming show announcements"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do art galleries use QR codes for sales?", answer: "Galleries place QR codes next to artworks that link directly to an inquiry form or a purchase portal, capturing interest while the collector is in front of the piece." },
|
||||
{ question: "Can QR codes show more work from the same artist?", answer: "Yes, the code can link to a digital portfolio showing other available works by the artist that aren't physically in the current show." },
|
||||
{ question: "Where should art gallery QR codes be placed?", answer: "Discreetly next to each piece's label, on the front desk, and in the main exhibition brochure." }
|
||||
],
|
||||
placementTips: [
|
||||
"Bottom-right of artwork identification labels",
|
||||
"Front desk pillar for the full digital catalog",
|
||||
"Social media wall for visitor tagging and following"
|
||||
],
|
||||
germanKeyword: "Kunstgalerien"
|
||||
},
|
||||
{
|
||||
slug: "stadiums",
|
||||
name: "Stadiums & Arenas",
|
||||
title: "QR Codes for Stadiums & Arenas",
|
||||
metaTitle: "QR Codes for Stadiums: Seat Delivery & Digital Tickets",
|
||||
metaDescription: "Improve the fan experience with QR codes for in-seat food delivery, digital ticketing, stadium maps, and exclusive merch offers.",
|
||||
headline: "QR Codes for Stadiums: Bringing the Fan Experience to Every Seat",
|
||||
subheadline: "Shorten queues and engage fans with in-seat ordering, interactive maps, and exclusive mobile-only offers",
|
||||
heroImage: "/marketing/industries/stadiums.png",
|
||||
statistics: [
|
||||
{ value: "40%", label: "Reduction in Concession Stand Queues" },
|
||||
{ value: "2x", label: "More Merch Sales via Exclusive Seats QR" },
|
||||
{ value: "50%", label: "Faster Wayfinding to Block and Row" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "In-Seat Concession Ordering", description: "Place a QR code on the back of every stadium seat. Fans scan to order food and drinks for collection—or even delivery—without missing a minute of the match." },
|
||||
{ title: "Interactive Stadium Wayfinding", description: "Help fans find their block, row, nearest restrooms, and parking with QR codes on pillars that link to real-time interactive arena maps." },
|
||||
{ title: "Exclusive Fan Zone Content", description: "Link seat QR codes to digital match programs, team stats, and exclusive merch offers that are only available during the event." }
|
||||
],
|
||||
useCases: [
|
||||
"Food and drink pre-ordering from every seat",
|
||||
"Interactive block and row wayfinding from pillars",
|
||||
"Digital match day programs and live stats",
|
||||
"Fan zone game and exclusive merch offer links",
|
||||
"Contact info for security and incident reporting"
|
||||
],
|
||||
tools: ["url-qr-code", "wifi-qr-code", "sms-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do stadiums use QR codes for concessions?", answer: "Stadiums put QR codes on seat backs that link to the concessions menu. Fans order from their phone and get a notification when it's ready for pickup." },
|
||||
{ question: "Can QR codes help with stadium wayfinding?", answer: "Yes, codes on terminal pillars link to maps that show the fan's current location and the fastest route to their specific seating block." },
|
||||
{ question: "Where are the best places for stadium QR codes?", answer: "The back of every seat, the main entrance arches, and the concession stand banners for menu browsing." }
|
||||
],
|
||||
placementTips: [
|
||||
"Back of every individual stadium seat for food",
|
||||
"Main entrance pillars for block-specific maps",
|
||||
"Concession stand area for digital menu viewing"
|
||||
],
|
||||
germanKeyword: "Stadien"
|
||||
},
|
||||
{
|
||||
slug: "theaters",
|
||||
name: "Theaters & Cinemas",
|
||||
title: "QR Codes for Theaters & Cinemas",
|
||||
metaTitle: "QR Codes for Theaters: Digital Programs & Concessions",
|
||||
metaDescription: "Modernize your theater or cinema with QR codes for digital programs, movie trailers, in-seat ordering, and future show bookings.",
|
||||
headline: "QR Codes for Theaters: Programs and Popcorn from Your Phone",
|
||||
subheadline: "Replace paper programs and shorten popcorn queues with digital show guides and mobile-first concessions",
|
||||
heroImage: "/marketing/industries/theaters.png",
|
||||
statistics: [
|
||||
{ value: "50%", label: "Reduction in Concession Waiting Times" },
|
||||
{ value: "3x", label: "More Future Show Bookings via Post-Show QR" },
|
||||
{ value: "40%", label: "Higher Engagement with Digital Programs" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Digital Show Programs", description: "Place a QR on the back of every theater seat linking to the full cast list, director's notes, and show information—saving thousands on printing costs." },
|
||||
{ title: "Fast-Track Concessions", description: "A QR in the lobby or on seats allows guests to pre-order popcorn and snacks, allowing them to skip the queue during the interval." },
|
||||
{ title: "Immediate Future Booking", description: "Place a QR code on the screen or in the exit foyer linking to tickets for the next show or film—capturing interest while the guest is still in the moment." }
|
||||
],
|
||||
useCases: [
|
||||
"Digital show guides and cast info from seat QR",
|
||||
"Mobile concessions and snack pre-ordering",
|
||||
"Movie trailers and behind-the-scenes content",
|
||||
"Future show ticketing links in the exit foyer",
|
||||
"Digital ticket wallet and scanner at the door"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "youtube-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do theaters use QR codes for programs?", answer: "They place codes on the back of seats that link to a PDF or web version of the program—meaning no more paper litter and easier updates." },
|
||||
{ question: "Can I order popcorn with a QR code?", answer: "Yes, many modern cinemas have QR codes in the lobby that let you order and pay for snacks from your phone to avoid waiting in line." },
|
||||
{ question: "Where should theater QR codes be placed?", answer: "The back of every theater seat, the main lobby counter, and on giant posters for upcoming films." }
|
||||
],
|
||||
placementTips: [
|
||||
"Individual seat backs for programs and concessions",
|
||||
"Entrance counter for digital ticketing and trailers",
|
||||
"Exit foyer posters for future show bookings"
|
||||
],
|
||||
germanKeyword: "Theater"
|
||||
}
|
||||
];
|
||||
import { IndustryPage } from './industry-pages';
|
||||
|
||||
export const missingIndustries3: IndustryPage[] = [
|
||||
{
|
||||
slug: "schools",
|
||||
name: "Schools",
|
||||
title: "QR Codes for Schools",
|
||||
metaTitle: "QR Codes for Schools: Parent Communication & Resources",
|
||||
metaDescription: "Improve parent-teacher communication and student resource sharing with QR codes for newsletters, homework portals, and event sign-ups.",
|
||||
headline: "QR Codes for Schools: Simple Parent-Teacher Connectivity",
|
||||
subheadline: "Let parents access the latest newsletter and homework updates from the school gate or lobby",
|
||||
heroImage: "/marketing/industries/schools.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "More Parent Newsletter Read Rates" },
|
||||
{ value: "40%", label: "Faster Event RSVP Responses" },
|
||||
{ value: "3x", label: "Higher Resource Hub Engagement" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Parent Newsletter Access", description: "Place a QR code at the school gate or reception. Parents scan while picking up children to read the latest digital newsletter or school announcements." },
|
||||
{ title: "Digital Homework Portals", description: "Direct links to the school's virtual learning environment (VLE) on homework planners ensure students and parents always find assignments." },
|
||||
{ title: "One-Tap Event RSVP", description: "QR codes on paper flyers for school plays, sports days, and parent-teacher meetings link to the digital sign-up form instantly." }
|
||||
],
|
||||
useCases: [
|
||||
"Parent newsletter link at school gates",
|
||||
"Homework and learning portal links on planners",
|
||||
"School play and sports day event RSVPs",
|
||||
"Cafeteria payment and menu links",
|
||||
"Teacher vCard and contact info for meetings"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "event-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do schools use QR codes for parents?", answer: "Schools place codes at gates or in reception that link to newsletters and school announcements—making mobile-first communication effortless for parents." },
|
||||
{ question: "Can QR codes help with school event planning?", answer: "Yes, by putting a QR on any paper flyer, schools can get instant RSVPs and track attendance for events like plays and fairs." },
|
||||
{ question: "Where should school QR codes be placed?", answer: "The school gates, the main reception desk, student homework planners, and on all physical event posters." }
|
||||
],
|
||||
placementTips: [
|
||||
"Main entrance gate for parent newsletters",
|
||||
"Reception desk sign for school visitor sign-in",
|
||||
"Physical event posters for digital RSVPs"
|
||||
],
|
||||
germanKeyword: "Schulen"
|
||||
},
|
||||
{
|
||||
slug: "churches",
|
||||
name: "Churches & Places of Worship",
|
||||
title: "QR Codes for Churches & Worship",
|
||||
metaTitle: "QR Codes for Churches: Digital Giving & Bulletins",
|
||||
metaDescription: "Increase community engagement with QR codes for digital donations, mobile bulletins, event calendars, and newsletter sign-ups.",
|
||||
headline: "QR Codes for Churches: Digital Bulletins and Simple Giving",
|
||||
subheadline: "Help your congregation stay connected with digital programs, hymnals, and one-tap donations from every pew",
|
||||
heroImage: "/marketing/industries/churches.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Increase in Weekly Digital Giving" },
|
||||
{ value: "50%", label: "Reduction in Printed Bulletin Costs" },
|
||||
{ value: "2x", label: "More Community Event Registrations" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Frictionless Digital Giving", description: "A QR code on the back of every pew or in the weekly program links directly to your secure donation portal—making it easy for the modern congregation to give." },
|
||||
{ title: "Digital Bulletins and Hymnals", description: "Reduce your environmental impact and printing costs by linking to the week's program, lyrics, and announcements via a single scan." },
|
||||
{ title: "Community Calendar and Groups", description: "Link QR codes to your small group sign-up forms and community events calendar, encouraging deeper engagement beyond Sunday service." }
|
||||
],
|
||||
useCases: [
|
||||
"Digital donation link on pews and programs",
|
||||
"Sunday bulletin and hymnal PDF access",
|
||||
"Community event and small group sign-ups",
|
||||
"Monthly newsletter and news sign-up links",
|
||||
"Prayer request form from the back of the pew"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "event-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do churches use QR codes for giving?", answer: "Churches place codes on pews or in bulletins that link directly to their payment provider, letting the congregation donate securely from their phones." },
|
||||
{ question: "Can QR codes replace printed church bulletins?", answer: "Yes, many congregations now scan a QR at the entrance to access the digital program and hymnal—saving thousands in annual printing costs." },
|
||||
{ question: "Where should churches place QR codes?", answer: "The back of every pew, the main lobby notice board, and the front cover of the physical weekly program." }
|
||||
],
|
||||
placementTips: [
|
||||
"Back of every pew for donations and prayers",
|
||||
"Lobby notice board for community events",
|
||||
"Front of paper programs for digital access"
|
||||
],
|
||||
germanKeyword: "Kirchen"
|
||||
},
|
||||
{
|
||||
slug: "art-galleries",
|
||||
name: "Art Galleries",
|
||||
title: "QR Codes for Art Galleries",
|
||||
metaTitle: "QR Codes for Art Galleries: Artist Bios & Purchasing",
|
||||
metaDescription: "Engage art collectors with QR codes for artist biographies, price lists, virtual exhibitions, and purchase inquiries.",
|
||||
headline: "QR Codes for Art Galleries: Storytelling and Seamless Sales",
|
||||
subheadline: "Let visitors dive deeper into every artwork with artist biographies and purchase links at every label",
|
||||
heroImage: "/marketing/industries/art-galleries.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "More Artist Bio Views via Exhibit QR" },
|
||||
{ value: "40%", label: "Faster Inquiry-to-Sale Conversion" },
|
||||
{ value: "2x", label: "Higher Instagram Follower Growth" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Deep-Dive Artist Biographies", description: "A QR on the wall label links to the artist's full biography, studio videos, and available works—providing context without crowding the gallery walls." },
|
||||
{ title: "Immediate Purchase Inquiries", description: "Link QR codes to a direct inquiry form or purchase link for every artwork, allowing collectors to start the buying process the moment they feel inspired." },
|
||||
{ title: "Virtual Exhibition Catalogs", description: "Give visitors a QR code at the entrance linking to the full digital exhibition catalog—which they can easily share with fellow art lovers." }
|
||||
],
|
||||
useCases: [
|
||||
"Artist bio and video link on each artwork label",
|
||||
"Price list and purchase inquiry portal",
|
||||
"Digital exhibition catalog at the entrance",
|
||||
"Social media following and artist tags",
|
||||
"Mailing list and upcoming show announcements"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do art galleries use QR codes for sales?", answer: "Galleries place QR codes next to artworks that link directly to an inquiry form or a purchase portal, capturing interest while the collector is in front of the piece." },
|
||||
{ question: "Can QR codes show more work from the same artist?", answer: "Yes, the code can link to a digital portfolio showing other available works by the artist that aren't physically in the current show." },
|
||||
{ question: "Where should art gallery QR codes be placed?", answer: "Discreetly next to each piece's label, on the front desk, and in the main exhibition brochure." }
|
||||
],
|
||||
placementTips: [
|
||||
"Bottom-right of artwork identification labels",
|
||||
"Front desk pillar for the full digital catalog",
|
||||
"Social media wall for visitor tagging and following"
|
||||
],
|
||||
germanKeyword: "Kunstgalerien"
|
||||
},
|
||||
{
|
||||
slug: "stadiums",
|
||||
name: "Stadiums & Arenas",
|
||||
title: "QR Codes for Stadiums & Arenas",
|
||||
metaTitle: "QR Codes for Stadiums: Seat Delivery & Digital Tickets",
|
||||
metaDescription: "Improve the fan experience with QR codes for in-seat food delivery, digital ticketing, stadium maps, and exclusive merch offers.",
|
||||
headline: "QR Codes for Stadiums: Bringing the Fan Experience to Every Seat",
|
||||
subheadline: "Shorten queues and engage fans with in-seat ordering, interactive maps, and exclusive mobile-only offers",
|
||||
heroImage: "/marketing/industries/stadiums.png",
|
||||
statistics: [
|
||||
{ value: "40%", label: "Reduction in Concession Stand Queues" },
|
||||
{ value: "2x", label: "More Merch Sales via Exclusive Seats QR" },
|
||||
{ value: "50%", label: "Faster Wayfinding to Block and Row" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "In-Seat Concession Ordering", description: "Place a QR code on the back of every stadium seat. Fans scan to order food and drinks for collection—or even delivery—without missing a minute of the match." },
|
||||
{ title: "Interactive Stadium Wayfinding", description: "Help fans find their block, row, nearest restrooms, and parking with QR codes on pillars that link to real-time interactive arena maps." },
|
||||
{ title: "Exclusive Fan Zone Content", description: "Link seat QR codes to digital match programs, team stats, and exclusive merch offers that are only available during the event." }
|
||||
],
|
||||
useCases: [
|
||||
"Food and drink pre-ordering from every seat",
|
||||
"Interactive block and row wayfinding from pillars",
|
||||
"Digital match day programs and live stats",
|
||||
"Fan zone game and exclusive merch offer links",
|
||||
"Contact info for security and incident reporting"
|
||||
],
|
||||
tools: ["url-qr-code", "wifi-qr-code", "sms-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do stadiums use QR codes for concessions?", answer: "Stadiums put QR codes on seat backs that link to the concessions menu. Fans order from their phone and get a notification when it's ready for pickup." },
|
||||
{ question: "Can QR codes help with stadium wayfinding?", answer: "Yes, codes on terminal pillars link to maps that show the fan's current location and the fastest route to their specific seating block." },
|
||||
{ question: "Where are the best places for stadium QR codes?", answer: "The back of every seat, the main entrance arches, and the concession stand banners for menu browsing." }
|
||||
],
|
||||
placementTips: [
|
||||
"Back of every individual stadium seat for food",
|
||||
"Main entrance pillars for block-specific maps",
|
||||
"Concession stand area for digital menu viewing"
|
||||
],
|
||||
germanKeyword: "Stadien"
|
||||
},
|
||||
{
|
||||
slug: "theaters",
|
||||
name: "Theaters & Cinemas",
|
||||
title: "QR Codes for Theaters & Cinemas",
|
||||
metaTitle: "QR Codes for Theaters: Digital Programs & Concessions",
|
||||
metaDescription: "Modernize your theater or cinema with QR codes for digital programs, movie trailers, in-seat ordering, and future show bookings.",
|
||||
headline: "QR Codes for Theaters: Programs and Popcorn from Your Phone",
|
||||
subheadline: "Replace paper programs and shorten popcorn queues with digital show guides and mobile-first concessions",
|
||||
heroImage: "/marketing/industries/theaters.png",
|
||||
statistics: [
|
||||
{ value: "50%", label: "Reduction in Concession Waiting Times" },
|
||||
{ value: "3x", label: "More Future Show Bookings via Post-Show QR" },
|
||||
{ value: "40%", label: "Higher Engagement with Digital Programs" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Digital Show Programs", description: "Place a QR on the back of every theater seat linking to the full cast list, director's notes, and show information—saving thousands on printing costs." },
|
||||
{ title: "Fast-Track Concessions", description: "A QR in the lobby or on seats allows guests to pre-order popcorn and snacks, allowing them to skip the queue during the interval." },
|
||||
{ title: "Immediate Future Booking", description: "Place a QR code on the screen or in the exit foyer linking to tickets for the next show or film—capturing interest while the guest is still in the moment." }
|
||||
],
|
||||
useCases: [
|
||||
"Digital show guides and cast info from seat QR",
|
||||
"Mobile concessions and snack pre-ordering",
|
||||
"Movie trailers and behind-the-scenes content",
|
||||
"Future show ticketing links in the exit foyer",
|
||||
"Digital ticket wallet and scanner at the door"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "youtube-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do theaters use QR codes for programs?", answer: "They place codes on the back of seats that link to a PDF or web version of the program—meaning no more paper litter and easier updates." },
|
||||
{ question: "Can I order popcorn with a QR code?", answer: "Yes, many modern cinemas have QR codes in the lobby that let you order and pay for snacks from your phone to avoid waiting in line." },
|
||||
{ question: "Where should theater QR codes be placed?", answer: "The back of every theater seat, the main lobby counter, and on giant posters for upcoming films." }
|
||||
],
|
||||
placementTips: [
|
||||
"Individual seat backs for programs and concessions",
|
||||
"Entrance counter for digital ticketing and trailers",
|
||||
"Exit foyer posters for future show bookings"
|
||||
],
|
||||
germanKeyword: "Theater"
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,199 +1,199 @@
|
||||
import { IndustryPage } from './industry-pages';
|
||||
|
||||
export const missingIndustries4: IndustryPage[] = [
|
||||
{
|
||||
slug: "wedding-planners",
|
||||
name: "Wedding Planners",
|
||||
title: "QR Codes for Wedding Planners",
|
||||
metaTitle: "QR Codes for Weddings: RSVPs & Photo Sharing",
|
||||
metaDescription: "Make every wedding day more special with QR codes for digital RSVPs, guest photo sharing, interactive seating charts, and gift registries.",
|
||||
headline: "QR Codes for Wedding Planners: Digital Elegance and Simple Guest Management",
|
||||
subheadline: "Let guests RSVP, find their seat, and share their favorite photos from a simple scan on your save-the-date or welcome sign",
|
||||
heroImage: "/marketing/industries/wedding-planners.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Faster RSVP Response Collection" },
|
||||
{ value: "2x", label: "More Guest Photos Collected via Shared Album" },
|
||||
{ value: "40%", label: "Reduction in Paper Information Sheets" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "One-Tap RSVPs and Registries", description: "Place a QR on the physical save-the-date or invitation. Guests scan to RSVP, select their meal, and access the gift registry instantly." },
|
||||
{ title: "Day-of Digital Wayfinding", description: "A QR on the welcome sign links to an interactive seating chart, timeline of events, and a digital guestbook—keeping everyone informed without bulky signage." },
|
||||
{ title: "Instant Shared Photo Albums", description: "Place a QR at every table. Guests scan to upload their photos and videos from the night directly to the couple's shared digital album." }
|
||||
],
|
||||
useCases: [
|
||||
"Digital RSVP and meal selection link on invitations",
|
||||
"Interactive seating chart and timeline from welcome signs",
|
||||
"Shared photo album and guestbook from table tents",
|
||||
"Digital gift registry links for guests",
|
||||
"Venue directions and local accommodation guides"
|
||||
],
|
||||
tools: ["url-qr-code", "google-maps-qr-code", "instagram-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do wedding planners use QR codes for RSVPs?", answer: "Planners include a QR code on physical invitations that links directly to a digital RSVP form, collecting all responses in one place." },
|
||||
{ question: "Can wedding guests share photos via QR codes?", answer: "Yes, by scanning a QR on their table, guests can upload their own photos from the night directly to a shared gallery or Google Drive." },
|
||||
{ question: "Where are the best places for wedding QR codes?", answer: "The physical invitation, the welcome sign at the venue entrance, and the individual dinner tables." }
|
||||
],
|
||||
placementTips: [
|
||||
"Physical invitation for RSVPs and registries",
|
||||
"Welcome sign for seating charts and event timelines",
|
||||
"Every dinner table for photo sharing and digital guestbooks"
|
||||
],
|
||||
germanKeyword: "Hochzeitsplaner"
|
||||
},
|
||||
{
|
||||
slug: "photographers",
|
||||
name: "Photographers",
|
||||
title: "QR Codes for Photographers",
|
||||
metaTitle: "QR Codes for Photographers: Portfolios & Booking",
|
||||
metaDescription: "Grow your photography business with QR codes for digital portfolios, online booking, and secure client gallery access.",
|
||||
headline: "QR Codes for Photographers: Share Your Work, Capture More Leads",
|
||||
subheadline: "Let clients view your full portfolio and book their next session with a simple scan of your business card or promotional material",
|
||||
heroImage: "/marketing/industries/photographers.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "More Portfolio Views via vCard QR" },
|
||||
{ value: "40%", label: "Higher Higher Discovery-to-Booking Rates" },
|
||||
{ value: "3x", label: "More Social Media Referral Traffic" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Mobile Portfolio Access", description: "Ensure your best work is always with you. A QR on your business card links directly to your digital portfolio or Instagram—no more searching for URLs." },
|
||||
{ title: "Self-Service Session Booking", description: "Link a QR on your brochures or studio window directly to your booking calendar, allowing clients to pick a date and pay deposits from their phone." },
|
||||
{ title: "Simple Client Gallery Delivery", description: "Give clients a small branded card with a QR code linking to their secure private gallery for viewing and downloading their final photos." }
|
||||
],
|
||||
useCases: [
|
||||
"Portfolio and social media link on business cards",
|
||||
"Booking portal and package info on brochures",
|
||||
"Client gallery access cards after a shoot",
|
||||
"Leave behind cards at events for attendee photo access",
|
||||
"Google review collection after a successful session"
|
||||
],
|
||||
tools: ["vcard-qr-code", "url-qr-code", "instagram-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do photographers use QR codes for networking?", answer: "Most use vCard QR codes on their business cards to share their contact info and a link to their digital portfolio in a single scan." },
|
||||
{ question: "Can QR codes help deliver client photos?", answer: "Yes, you can provide a QR code that links directly to the client's secure, password-protected online gallery." },
|
||||
{ question: "Where should photographer QR codes be placed?", answer: "Business cards, brochures given at events, studio windows, and the final delivery card for clients." }
|
||||
],
|
||||
placementTips: [
|
||||
"Back of the physical business card for portfolio",
|
||||
"Branded card given after a shoot for gallery access",
|
||||
"Brochures handed out at weddings or corporate events"
|
||||
],
|
||||
germanKeyword: "Fotografen"
|
||||
},
|
||||
{
|
||||
slug: "trade-shows",
|
||||
name: "Trade Shows & Events",
|
||||
title: "QR Codes for Trade Shows",
|
||||
metaTitle: "QR Codes for Trade Shows: Lead Gen & Digital Brochures",
|
||||
metaDescription: "Maximize your trade show ROI with QR codes for lead capture, digital brochures, product demos, and booth scheduling.",
|
||||
headline: "QR Codes for Trade Shows: No More Heavy Brochures, More High-Quality Leads",
|
||||
subheadline: "Replace expensive paper materials with digital brochures and seamless lead capture at your booth",
|
||||
heroImage: "/marketing/industries/trade-shows.png",
|
||||
statistics: [
|
||||
{ value: "40%", label: "More Higher Lead Capture Rates" },
|
||||
{ value: "3x", label: "Fewer Printed Brochure Costs" },
|
||||
{ value: "2x", label: "Higher Post-Event Inquiry Follow-ups" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Digital Brochure Distributions", description: "Save on printing and shipping. A QR at your booth links visitors to your full digital brochure or product catalog, which they can take with them on their phone." },
|
||||
{ title: "One-Scan Lead Generation", description: "Capture interest instantly. A QR code on your booth signage links to a lead form where visitors can request a demo or a follow-up call." },
|
||||
{ title: "Interactive Product Demos", description: "Link QR codes to unboxing videos, case studies, or interactive 3D models that bring your product to life for every visitor." }
|
||||
],
|
||||
useCases: [
|
||||
"Digital brochure and catalog access from booth pillar",
|
||||
"Lead generation and demo request forms",
|
||||
"Staff vCard exchange from physical badges",
|
||||
"Interactive venue and booth maps for attendees",
|
||||
"Social media growth via booth Instagram/LinkedIn QR"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "vcard-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do QR codes work for trade show lead gen?", answer: "Visitors scan a code at your booth that takes them to a form where they enter their details to receive a brochure or book a meeting." },
|
||||
{ question: "Can I replace all papers with QR codes at an event?", answer: "Yes, many booths use 'digital first' signage where all info, catalogs, and bios are accessed via QR codes—eliminating heavy paper waste." },
|
||||
{ question: "Where is the best place to put a QR code at a trade show?", answer: "Booth pillars at eye level, counter-top display cards, and on every staff member's badge or business card." }
|
||||
],
|
||||
placementTips: [
|
||||
"Main eye-level booth pillar for general info",
|
||||
"Front counter display for lead capture from a scan",
|
||||
"Back of staff member badges for digital business cards"
|
||||
],
|
||||
germanKeyword: "Messen"
|
||||
},
|
||||
{
|
||||
slug: "clothing-stores",
|
||||
name: "Clothing Stores",
|
||||
title: "QR Codes for Clothing Stores",
|
||||
metaTitle: "QR Codes for Fashion: Fit Guides & Inventory",
|
||||
metaDescription: "Improve the fashion retail experience with QR codes for size guides, styling videos, inventory lookup, and digital loyalty rewards.",
|
||||
headline: "QR Codes for Clothing Stores: Finding the Perfect Fit and Style",
|
||||
subheadline: "Let customers view styling videos and check sizes from every rack and fitting room",
|
||||
heroImage: "/marketing/industries/clothing-stores.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Higher Engagement with Style Guides" },
|
||||
{ value: "40%", label: "Increase in Online Order Completion" },
|
||||
{ value: "2x", label: "More Loyalty Program Registrations" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Fitting Room Style Guides", description: "Place QR codes in every fitting room that link to styling videos or 'shop the look' guides, encouraging customers to buy complete outfits." },
|
||||
{ title: "In-Store Size and Stock Check", description: "If a size isn't on the rack, a QR on the shelf lets the customer check online stock and order it for home delivery or store collection immediately." },
|
||||
{ title: "Instant Loyalty Rewards", description: "A QR at the checkout allows customers to join your loyalty program and earn points without filling out a paper form." }
|
||||
],
|
||||
useCases: [
|
||||
"Styling videos and size guides in fitting rooms",
|
||||
"Out-of-stock ordering from every rack QR",
|
||||
"Digital loyalty program and coupon access",
|
||||
"Instagram wall for customer selfie tagging",
|
||||
"Product care and washing instruction videos"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "app-store-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do clothing stores use QR codes in fitting rooms?", answer: "They place codes in the mirrors that link to outfit inspiration videos, size guides, or allow the customer to 'call for assistance'." },
|
||||
{ question: "Can QR codes help when a size is out of stock?", answer: "Yes, the code on the price tag can link directly to the online shop where the customer can order their size for home delivery." },
|
||||
{ question: "Where should fashion retailers place QR codes?", answer: "Fitting room mirrors, rack-end signs, and at the checkout counter for sign-ups." }
|
||||
],
|
||||
placementTips: [
|
||||
"Fitting room mirror for inspiration and guides",
|
||||
"Rack-end display for inventory and size lookups",
|
||||
"Checkout counter for loyalty and email sign-up"
|
||||
],
|
||||
germanKeyword: "Bekleidungsgeschäfte"
|
||||
},
|
||||
{
|
||||
slug: "florists",
|
||||
name: "Florists",
|
||||
title: "QR Codes for Florists",
|
||||
metaTitle: "QR Codes for Florists: Plant Care & Video Messages",
|
||||
metaDescription: "Make every bouquet special with QR codes for personalized video messages, plant care instructions, and easy delivery tracking.",
|
||||
headline: "QR Codes for Florists: Adding a Personal Touch to Every Bloom",
|
||||
subheadline: "Let your customers attach video messages to their flowers and access care guides with a single scan",
|
||||
heroImage: "/marketing/industries/florists.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "Higher Customer Satisfaction via Care Guides" },
|
||||
{ value: "3x", label: "More Repeated Orders from loyalty QRs" },
|
||||
{ value: "50%", label: "Increase in Video Message Attachment Adoption" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Personal Video Messages", description: "Let customers record a personalized video card and attach it to their bouquet via a QR code—creating a unique, modern gifting experience." },
|
||||
{ title: "Digital Flower Care Guides", description: "Provide a QR on the care card that links to detailed instructions and videos on how to keep their specific flowers fresh for longer." },
|
||||
{ title: "Subscription and Re-Order Ease", description: "A QR on the wrapping paper or card links to your subscription service or a one-click re-order page for future occasions." }
|
||||
],
|
||||
useCases: [
|
||||
"Personalized video message attached to bouquets",
|
||||
"Digital plant and flower care instructions",
|
||||
"Subscription service and loyalty sign-up links",
|
||||
"Order tracking and delivery status portal",
|
||||
"Instagram tagging for beautiful arrangements"
|
||||
],
|
||||
tools: ["url-qr-code", "youtube-qr-code", "instagram-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do florists use QR codes for video messages?", answer: "Customers record a video, and the florist generates a QR code that is printed on the gift card for the recipient to scan and watch." },
|
||||
{ question: "Can QR codes help flowers last longer?", answer: "Yes, by linking to specific care guides, florists ensure customers know exactly how to trim and water their blooms." },
|
||||
{ question: "Where should florists place QR codes?", answer: "On the gift card, the wrapping paper, and at the shop window for after-hours ordering." }
|
||||
],
|
||||
placementTips: [
|
||||
"Gift card for personalized video messages",
|
||||
"Care card for plant and flower maintenance videos",
|
||||
"Shop window for easy after-hours ordering"
|
||||
],
|
||||
germanKeyword: "Blumengeschäfte"
|
||||
}
|
||||
];
|
||||
import { IndustryPage } from './industry-pages';
|
||||
|
||||
export const missingIndustries4: IndustryPage[] = [
|
||||
{
|
||||
slug: "wedding-planners",
|
||||
name: "Wedding Planners",
|
||||
title: "QR Codes for Wedding Planners",
|
||||
metaTitle: "QR Codes for Weddings: RSVPs & Photo Sharing",
|
||||
metaDescription: "Make every wedding day more special with QR codes for digital RSVPs, guest photo sharing, interactive seating charts, and gift registries.",
|
||||
headline: "QR Codes for Wedding Planners: Digital Elegance and Simple Guest Management",
|
||||
subheadline: "Let guests RSVP, find their seat, and share their favorite photos from a simple scan on your save-the-date or welcome sign",
|
||||
heroImage: "/marketing/industries/wedding-planners.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Faster RSVP Response Collection" },
|
||||
{ value: "2x", label: "More Guest Photos Collected via Shared Album" },
|
||||
{ value: "40%", label: "Reduction in Paper Information Sheets" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "One-Tap RSVPs and Registries", description: "Place a QR on the physical save-the-date or invitation. Guests scan to RSVP, select their meal, and access the gift registry instantly." },
|
||||
{ title: "Day-of Digital Wayfinding", description: "A QR on the welcome sign links to an interactive seating chart, timeline of events, and a digital guestbook—keeping everyone informed without bulky signage." },
|
||||
{ title: "Instant Shared Photo Albums", description: "Place a QR at every table. Guests scan to upload their photos and videos from the night directly to the couple's shared digital album." }
|
||||
],
|
||||
useCases: [
|
||||
"Digital RSVP and meal selection link on invitations",
|
||||
"Interactive seating chart and timeline from welcome signs",
|
||||
"Shared photo album and guestbook from table tents",
|
||||
"Digital gift registry links for guests",
|
||||
"Venue directions and local accommodation guides"
|
||||
],
|
||||
tools: ["url-qr-code", "google-maps-qr-code", "instagram-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do wedding planners use QR codes for RSVPs?", answer: "Planners include a QR code on physical invitations that links directly to a digital RSVP form, collecting all responses in one place." },
|
||||
{ question: "Can wedding guests share photos via QR codes?", answer: "Yes, by scanning a QR on their table, guests can upload their own photos from the night directly to a shared gallery or Google Drive." },
|
||||
{ question: "Where are the best places for wedding QR codes?", answer: "The physical invitation, the welcome sign at the venue entrance, and the individual dinner tables." }
|
||||
],
|
||||
placementTips: [
|
||||
"Physical invitation for RSVPs and registries",
|
||||
"Welcome sign for seating charts and event timelines",
|
||||
"Every dinner table for photo sharing and digital guestbooks"
|
||||
],
|
||||
germanKeyword: "Hochzeitsplaner"
|
||||
},
|
||||
{
|
||||
slug: "photographers",
|
||||
name: "Photographers",
|
||||
title: "QR Codes for Photographers",
|
||||
metaTitle: "QR Codes for Photographers: Portfolios & Booking",
|
||||
metaDescription: "Grow your photography business with QR codes for digital portfolios, online booking, and secure client gallery access.",
|
||||
headline: "QR Codes for Photographers: Share Your Work, Capture More Leads",
|
||||
subheadline: "Let clients view your full portfolio and book their next session with a simple scan of your business card or promotional material",
|
||||
heroImage: "/marketing/industries/photographers.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "More Portfolio Views via vCard QR" },
|
||||
{ value: "40%", label: "Higher Higher Discovery-to-Booking Rates" },
|
||||
{ value: "3x", label: "More Social Media Referral Traffic" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Mobile Portfolio Access", description: "Ensure your best work is always with you. A QR on your business card links directly to your digital portfolio or Instagram—no more searching for URLs." },
|
||||
{ title: "Self-Service Session Booking", description: "Link a QR on your brochures or studio window directly to your booking calendar, allowing clients to pick a date and pay deposits from their phone." },
|
||||
{ title: "Simple Client Gallery Delivery", description: "Give clients a small branded card with a QR code linking to their secure private gallery for viewing and downloading their final photos." }
|
||||
],
|
||||
useCases: [
|
||||
"Portfolio and social media link on business cards",
|
||||
"Booking portal and package info on brochures",
|
||||
"Client gallery access cards after a shoot",
|
||||
"Leave behind cards at events for attendee photo access",
|
||||
"Google review collection after a successful session"
|
||||
],
|
||||
tools: ["vcard-qr-code", "url-qr-code", "instagram-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do photographers use QR codes for networking?", answer: "Most use vCard QR codes on their business cards to share their contact info and a link to their digital portfolio in a single scan." },
|
||||
{ question: "Can QR codes help deliver client photos?", answer: "Yes, you can provide a QR code that links directly to the client's secure, password-protected online gallery." },
|
||||
{ question: "Where should photographer QR codes be placed?", answer: "Business cards, brochures given at events, studio windows, and the final delivery card for clients." }
|
||||
],
|
||||
placementTips: [
|
||||
"Back of the physical business card for portfolio",
|
||||
"Branded card given after a shoot for gallery access",
|
||||
"Brochures handed out at weddings or corporate events"
|
||||
],
|
||||
germanKeyword: "Fotografen"
|
||||
},
|
||||
{
|
||||
slug: "trade-shows",
|
||||
name: "Trade Shows & Events",
|
||||
title: "QR Codes for Trade Shows",
|
||||
metaTitle: "QR Codes for Trade Shows: Lead Gen & Digital Brochures",
|
||||
metaDescription: "Maximize your trade show ROI with QR codes for lead capture, digital brochures, product demos, and booth scheduling.",
|
||||
headline: "QR Codes for Trade Shows: No More Heavy Brochures, More High-Quality Leads",
|
||||
subheadline: "Replace expensive paper materials with digital brochures and seamless lead capture at your booth",
|
||||
heroImage: "/marketing/industries/trade-shows.png",
|
||||
statistics: [
|
||||
{ value: "40%", label: "More Higher Lead Capture Rates" },
|
||||
{ value: "3x", label: "Fewer Printed Brochure Costs" },
|
||||
{ value: "2x", label: "Higher Post-Event Inquiry Follow-ups" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Digital Brochure Distributions", description: "Save on printing and shipping. A QR at your booth links visitors to your full digital brochure or product catalog, which they can take with them on their phone." },
|
||||
{ title: "One-Scan Lead Generation", description: "Capture interest instantly. A QR code on your booth signage links to a lead form where visitors can request a demo or a follow-up call." },
|
||||
{ title: "Interactive Product Demos", description: "Link QR codes to unboxing videos, case studies, or interactive 3D models that bring your product to life for every visitor." }
|
||||
],
|
||||
useCases: [
|
||||
"Digital brochure and catalog access from booth pillar",
|
||||
"Lead generation and demo request forms",
|
||||
"Staff vCard exchange from physical badges",
|
||||
"Interactive venue and booth maps for attendees",
|
||||
"Social media growth via booth Instagram/LinkedIn QR"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "vcard-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do QR codes work for trade show lead gen?", answer: "Visitors scan a code at your booth that takes them to a form where they enter their details to receive a brochure or book a meeting." },
|
||||
{ question: "Can I replace all papers with QR codes at an event?", answer: "Yes, many booths use 'digital first' signage where all info, catalogs, and bios are accessed via QR codes—eliminating heavy paper waste." },
|
||||
{ question: "Where is the best place to put a QR code at a trade show?", answer: "Booth pillars at eye level, counter-top display cards, and on every staff member's badge or business card." }
|
||||
],
|
||||
placementTips: [
|
||||
"Main eye-level booth pillar for general info",
|
||||
"Front counter display for lead capture from a scan",
|
||||
"Back of staff member badges for digital business cards"
|
||||
],
|
||||
germanKeyword: "Messen"
|
||||
},
|
||||
{
|
||||
slug: "clothing-stores",
|
||||
name: "Clothing Stores",
|
||||
title: "QR Codes for Clothing Stores",
|
||||
metaTitle: "QR Codes for Fashion: Fit Guides & Inventory",
|
||||
metaDescription: "Improve the fashion retail experience with QR codes for size guides, styling videos, inventory lookup, and digital loyalty rewards.",
|
||||
headline: "QR Codes for Clothing Stores: Finding the Perfect Fit and Style",
|
||||
subheadline: "Let customers view styling videos and check sizes from every rack and fitting room",
|
||||
heroImage: "/marketing/industries/clothing-stores.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Higher Engagement with Style Guides" },
|
||||
{ value: "40%", label: "Increase in Online Order Completion" },
|
||||
{ value: "2x", label: "More Loyalty Program Registrations" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Fitting Room Style Guides", description: "Place QR codes in every fitting room that link to styling videos or 'shop the look' guides, encouraging customers to buy complete outfits." },
|
||||
{ title: "In-Store Size and Stock Check", description: "If a size isn't on the rack, a QR on the shelf lets the customer check online stock and order it for home delivery or store collection immediately." },
|
||||
{ title: "Instant Loyalty Rewards", description: "A QR at the checkout allows customers to join your loyalty program and earn points without filling out a paper form." }
|
||||
],
|
||||
useCases: [
|
||||
"Styling videos and size guides in fitting rooms",
|
||||
"Out-of-stock ordering from every rack QR",
|
||||
"Digital loyalty program and coupon access",
|
||||
"Instagram wall for customer selfie tagging",
|
||||
"Product care and washing instruction videos"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "app-store-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do clothing stores use QR codes in fitting rooms?", answer: "They place codes in the mirrors that link to outfit inspiration videos, size guides, or allow the customer to 'call for assistance'." },
|
||||
{ question: "Can QR codes help when a size is out of stock?", answer: "Yes, the code on the price tag can link directly to the online shop where the customer can order their size for home delivery." },
|
||||
{ question: "Where should fashion retailers place QR codes?", answer: "Fitting room mirrors, rack-end signs, and at the checkout counter for sign-ups." }
|
||||
],
|
||||
placementTips: [
|
||||
"Fitting room mirror for inspiration and guides",
|
||||
"Rack-end display for inventory and size lookups",
|
||||
"Checkout counter for loyalty and email sign-up"
|
||||
],
|
||||
germanKeyword: "Bekleidungsgeschäfte"
|
||||
},
|
||||
{
|
||||
slug: "florists",
|
||||
name: "Florists",
|
||||
title: "QR Codes for Florists",
|
||||
metaTitle: "QR Codes for Florists: Plant Care & Video Messages",
|
||||
metaDescription: "Make every bouquet special with QR codes for personalized video messages, plant care instructions, and easy delivery tracking.",
|
||||
headline: "QR Codes for Florists: Adding a Personal Touch to Every Bloom",
|
||||
subheadline: "Let your customers attach video messages to their flowers and access care guides with a single scan",
|
||||
heroImage: "/marketing/industries/florists.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "Higher Customer Satisfaction via Care Guides" },
|
||||
{ value: "3x", label: "More Repeated Orders from loyalty QRs" },
|
||||
{ value: "50%", label: "Increase in Video Message Attachment Adoption" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Personal Video Messages", description: "Let customers record a personalized video card and attach it to their bouquet via a QR code—creating a unique, modern gifting experience." },
|
||||
{ title: "Digital Flower Care Guides", description: "Provide a QR on the care card that links to detailed instructions and videos on how to keep their specific flowers fresh for longer." },
|
||||
{ title: "Subscription and Re-Order Ease", description: "A QR on the wrapping paper or card links to your subscription service or a one-click re-order page for future occasions." }
|
||||
],
|
||||
useCases: [
|
||||
"Personalized video message attached to bouquets",
|
||||
"Digital plant and flower care instructions",
|
||||
"Subscription service and loyalty sign-up links",
|
||||
"Order tracking and delivery status portal",
|
||||
"Instagram tagging for beautiful arrangements"
|
||||
],
|
||||
tools: ["url-qr-code", "youtube-qr-code", "instagram-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do florists use QR codes for video messages?", answer: "Customers record a video, and the florist generates a QR code that is printed on the gift card for the recipient to scan and watch." },
|
||||
{ question: "Can QR codes help flowers last longer?", answer: "Yes, by linking to specific care guides, florists ensure customers know exactly how to trim and water their blooms." },
|
||||
{ question: "Where should florists place QR codes?", answer: "On the gift card, the wrapping paper, and at the shop window for after-hours ordering." }
|
||||
],
|
||||
placementTips: [
|
||||
"Gift card for personalized video messages",
|
||||
"Care card for plant and flower maintenance videos",
|
||||
"Shop window for easy after-hours ordering"
|
||||
],
|
||||
germanKeyword: "Blumengeschäfte"
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,199 +1,199 @@
|
||||
import { IndustryPage } from './industry-pages';
|
||||
|
||||
export const missingIndustries5: IndustryPage[] = [
|
||||
{
|
||||
slug: "pet-stores",
|
||||
name: "Pet Stores",
|
||||
title: "QR Codes for Pet Stores",
|
||||
metaTitle: "QR Codes for Pet Stores: Nutrition & Care Guides",
|
||||
metaDescription: "Give pet owners the best advice with QR codes for nutrition information, pet care tutorials, and digital loyalty rewards.",
|
||||
headline: "QR Codes for Pet Stores: Expert Care for Every Pet",
|
||||
subheadline: "Let customers scan for expert nutrition advice and care videos at every shelf and aisle",
|
||||
heroImage: "/marketing/industries/pet-stores.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Higher Engagement with Nutrition Guides" },
|
||||
{ value: "40%", label: "Increase in Online Order Re-Subscribing" },
|
||||
{ value: "2x", label: "More Loyalty Program Registrations" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Digital Nutrition and Ingredient Guides", description: "Place QR codes near specialized pet food racks. Owners scan to read full ingredient lists, allergy info, and feeding guides from their phone." },
|
||||
{ title: "One-Tap Loyalty and Rewards", description: "A QR at the checkout allows pet owners to join your rewards club and track their 'buy 10, get 1 free' progress without a paper card." },
|
||||
{ title: "Pet Care Tutorial Videos", description: "For complex products like aquarium setups or grooming tools, a QR on the shelf links to step-by-step video tutorials and setup guides." }
|
||||
],
|
||||
useCases: [
|
||||
"Nutrition and ingredient info on pet food racks",
|
||||
"Grooming and setup tutorial videos for products",
|
||||
"Loyalty and reward program enrollment at checkout",
|
||||
"Adoption event and community poster sign-ups",
|
||||
"Online re-order and subscription portal link"
|
||||
],
|
||||
tools: ["url-qr-code", "youtube-qr-code", "instagram-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do pet stores use QR codes for nutrition?", answer: "They place codes on food shelves that link to detailed nutritional breakdowns and portion calculators for different breeds." },
|
||||
{ question: "Can QR codes help with pet store loyalty cards?", answer: "Yes, by scanning a QR at the counter, customers can store their points and rewards digitally on their phone instead of carrying a paper card." },
|
||||
{ question: "Where should pet stores place QR codes?", answer: "The food aisles, high-tech product boxes, and the main checkout counter for loyalty sign-ups." }
|
||||
],
|
||||
placementTips: [
|
||||
"Specialized food section for nutrition guides",
|
||||
"Grooming or complex tool shelves for video tutorials",
|
||||
"Checkout counter for digital loyalty card storage"
|
||||
],
|
||||
germanKeyword: "Zoohandlungen"
|
||||
},
|
||||
{
|
||||
slug: "electronics-stores",
|
||||
name: "Electronics Stores",
|
||||
title: "QR Codes for Electronics Stores",
|
||||
metaTitle: "QR Codes for Electronics: Product Manuals & Demos",
|
||||
metaDescription: "Boost sales with QR codes for product manuals, unboxing videos, comparison charts, and warranty registration in-store.",
|
||||
headline: "QR Codes for Electronics Stores: No More Manuals, More Demos",
|
||||
subheadline: "Let customers watch unboxing videos and find exact specs from every product shelf",
|
||||
heroImage: "/marketing/industries/electronics-stores.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "Higher Higher Discovery-to-Lead Rates" },
|
||||
{ value: "45%", label: "Increase in Online Warranty Registrations" },
|
||||
{ value: "3x", label: "More Product Comparison Chart Views" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Unboxing and Demo Video Links", description: "Place a QR on the product display. Customers scan to watch real-world unboxing and performance videos, making the buying decision easier." },
|
||||
{ title: "Instant Product Comparison Charts", description: "A QR on the shelf edge links to a digital table comparing the current model with others in the same category, helping customers find the right fit." },
|
||||
{ title: "Simplified Warranty Registration", description: "Ensure every customer registers their product. A QR on the receipt or the box links to your online warranty portal for instant sign-up." }
|
||||
],
|
||||
useCases: [
|
||||
"Unboxing and demo videos on display models",
|
||||
"Digital comparison charts on shelf edges",
|
||||
"Warranty registration links on receipts and boxes",
|
||||
"Product manuals and setup guides via PDF QR",
|
||||
"In-store tech support and status update portal"
|
||||
],
|
||||
tools: ["url-qr-code", "youtube-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do electronics stores use QR codes for manuals?", answer: "They place QR codes on shelf labels or boxes that link to the official digital PDF manual—saving paper and making it easier to search." },
|
||||
{ question: "Can QR codes help with product comparisons?", answer: "Yes, the code on the shelf can link to a digital comparison tool where customers can see specs side-by-side on their own phone." },
|
||||
{ question: "Where are the best places for electronics store QR codes?", answer: "The shelf edge under the product name, on the back of display models for specs, and on the checkout receipt for warranties." }
|
||||
],
|
||||
placementTips: [
|
||||
"Shelf edge underทุก item for specs and demos",
|
||||
"Inside the product box for warranty and setup guides",
|
||||
"Display models for expert video review links"
|
||||
],
|
||||
germanKeyword: "Elektronikgeschäfte"
|
||||
},
|
||||
{
|
||||
slug: "jewelry-stores",
|
||||
name: "Jewelry Stores",
|
||||
title: "QR Codes for Jewelry Stores",
|
||||
metaTitle: "QR Codes for Jewelry: Authentication & Styling",
|
||||
metaDescription: "Give every piece a story with QR codes for certificates of authenticity, styling guides, and custom design inquiries.",
|
||||
headline: "QR Codes for Jewelry Stores: Every Piece Tells a Story",
|
||||
subheadline: "Let customers access certificates of authenticity and styling guides at every display case",
|
||||
heroImage: "/marketing/industries/jewelry-stores.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Higher Engagement with Certification Info" },
|
||||
{ value: "40%", label: "Increase in Custom Design Inquiries" },
|
||||
{ value: "2x", label: "More Social Media Referral Traffic" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Interactive Certificates of Authenticity", description: "Provide a QR code with every major purchase that links to the digital certification and history of the gemstone—boosting trust and transparency." },
|
||||
{ title: "Digital Style and Pairing Guides", description: "A QR on the display links to high-end lifestyle videos and guides showing how the piece can be styled and paired with other jewelry in your collection." },
|
||||
{ title: "Custom Design Collaboration", description: "Let clients start their unique journey. A QR on your showroom floor links to a design inquiry form where they can upload inspiration and book a consultation." }
|
||||
],
|
||||
useCases: [
|
||||
"Authenticity certificate and diamond report links",
|
||||
"Jewelry styling and care video guides",
|
||||
"Custom design inquiry and appointment booking",
|
||||
"Engagement and wedding band gallery links",
|
||||
"Instagram tagging for beautiful showroom pieces"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "whatsapp-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do jewelry stores use QR codes for certificates?", answer: "They provide a QR code that links to the specific gemstone's diamond report (GIA/IGI) or the store's own certificate of authenticity." },
|
||||
{ question: "Can I book a custom jewelry session via QR?", answer: "Yes, the code in the shop window or showroom can link directly to a booking form for a private consultation with a jeweler." },
|
||||
{ question: "Where should jewelry QR codes be placed?", answer: "Discreetly inside the display cases, on the physical gift card, and on the front of the jewelry box." }
|
||||
],
|
||||
placementTips: [
|
||||
"Bottom corner of every gemstone display case",
|
||||
"Inside the physical jewelry box for care and guides",
|
||||
"Physical gift cards for the digital story of the piece"
|
||||
],
|
||||
germanKeyword: "Juweliere"
|
||||
},
|
||||
{
|
||||
slug: "hardware-stores",
|
||||
name: "Hardware Stores",
|
||||
title: "QR Codes for Hardware Stores",
|
||||
metaTitle: "QR Codes for Hardware: DIY Tutorials & Equipment Rental",
|
||||
metaDescription: "Help your customers build better with QR codes for DIY tutorial videos, project guides, and easy equipment rental booking.",
|
||||
headline: "QR Codes for Hardware Stores: Your In-Store DIY Expert",
|
||||
subheadline: "Let customers scan for project tutorials and product setup videos at every aisle and shelf",
|
||||
heroImage: "/marketing/industries/hardware-stores.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Higher Higher Discovery-to-Lead Rates" },
|
||||
{ value: "50%", label: "Faster Equipment Rental Booking" },
|
||||
{ value: "2x", label: "More Project Guide Video Completes" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "In-Aisle DIY Tutorials", description: "Place QR codes on shelves for power tools or plumbing. Customers scan to watch 'how-to' videos and project guides while they shop for their materials." },
|
||||
{ title: "Frictionless Equipment Rental", description: "A QR on the rental fleet in the store links directly to the booking calendar, allowing customers to reserve a tool for the weekend without waiting in line." },
|
||||
{ title: "Product Compatibility Check", description: "Link QR codes to digital tables that show which drill bits, blades, or filters are compatible with every major power tool you sell." }
|
||||
],
|
||||
useCases: [
|
||||
"DIY 'how-to' project videos and guide links",
|
||||
"Equipment and tool rental booking portal",
|
||||
"Product part and accessory compatibility tables",
|
||||
"Contractor account and loyalty sign-up links",
|
||||
"Lawn care and seasonal garden guide PDFs"
|
||||
],
|
||||
tools: ["url-qr-code", "youtube-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do hardware stores use QR codes for DIY?", answer: "They place codes on the shelf next to complex items like tiles or power tools that link to video tutorials showing how to use them." },
|
||||
{ question: "Can QR codes help with renting tools?", answer: "Yes, by scanning a QR on the rental machine, customers can check availability and book the tool for their duration immediately." },
|
||||
{ question: "Where is the best place to put a QR code in a DIY shop?", answer: "The end of product aisles for guide catalogs, on power tool display racks, and on the sides of large rental machines." }
|
||||
],
|
||||
placementTips: [
|
||||
"Aisle end banners for category-specific DIY guides",
|
||||
"Product boxes for assembly and warranty links",
|
||||
"Rental counter and equipment for booking access"
|
||||
],
|
||||
germanKeyword: "Baumärkte"
|
||||
},
|
||||
{
|
||||
slug: "cinemas",
|
||||
name: "Cinemas",
|
||||
title: "QR Codes for Cinemas",
|
||||
metaTitle: "QR Codes for Cinemas: Trailers & Fast-Track Concessions",
|
||||
metaDescription: "Modernize your cinema with QR codes for movie trailers, fast-track popcorn ordering, and future show booking in the lobby.",
|
||||
headline: "QR Codes for Cinemas: More Trailers, Less Queueing",
|
||||
subheadline: "Let guests watch trailers and order snacks from their phone while waiting for their film to start",
|
||||
heroImage: "/marketing/industries/cinemas.png",
|
||||
statistics: [
|
||||
{ value: "50%", label: "Reduction in Popcorn Stand Queues" },
|
||||
{ value: "2x", label: "More Trailers Watched via Poster QRs" },
|
||||
{ value: "3x", label: "Higher Loyalty Program Registrations" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Instant Movie Trailers", description: "Place a QR code on every physical movie poster in the lobby. Guests scan to watch the high-res trailer and book tickets for that film on their phone." },
|
||||
{ title: "Queue-Free Concessions", description: "A QR in the lobby allows guests to pre-order and pay for popcorn and drinks, giving them a collection point and avoiding long queues." },
|
||||
{ title: "Digital Loyalty and Rewards", description: "A QR at the ticket counter or in the cinema's own app links to the loyalty program, where fans can earn points for every film they see." }
|
||||
],
|
||||
useCases: [
|
||||
"Movie trailers and ticket booking from posters",
|
||||
"In-lobby popcorn and snack pre-ordering",
|
||||
"Loyalty and reward membership sign-up at the door",
|
||||
"Exclusive behind-the-scenes content links",
|
||||
"Digital ticket wallet and gate entry scanning"
|
||||
],
|
||||
tools: ["url-qr-code", "youtube-qr-code", "app-store-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do cinemas use QR codes for trailers?", answer: "They place codes on the corner of movie posters so guests can watch the full trailer and book tickets for future dates as they walk past." },
|
||||
{ question: "Can I skip the popcorn line with a QR code?", answer: "Yes, many cinemas provide QR codes in the lobby that let you pre-order your snacks so they are ready for pickup just as your movie starts." },
|
||||
{ question: "Where should cinema QR codes be placed?", answer: "On the physical movie posters, the concessions counter, and at the entrance gates for ticket scanning." }
|
||||
],
|
||||
placementTips: [
|
||||
"Bottom-right of every physical movie poster",
|
||||
"Lobby counter area for fast-track snacks",
|
||||
"Exit doors for ticketing future releases"
|
||||
],
|
||||
germanKeyword: "Kinos"
|
||||
}
|
||||
];
|
||||
import { IndustryPage } from './industry-pages';
|
||||
|
||||
export const missingIndustries5: IndustryPage[] = [
|
||||
{
|
||||
slug: "pet-stores",
|
||||
name: "Pet Stores",
|
||||
title: "QR Codes for Pet Stores",
|
||||
metaTitle: "QR Codes for Pet Stores: Nutrition & Care Guides",
|
||||
metaDescription: "Give pet owners the best advice with QR codes for nutrition information, pet care tutorials, and digital loyalty rewards.",
|
||||
headline: "QR Codes for Pet Stores: Expert Care for Every Pet",
|
||||
subheadline: "Let customers scan for expert nutrition advice and care videos at every shelf and aisle",
|
||||
heroImage: "/marketing/industries/pet-stores.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Higher Engagement with Nutrition Guides" },
|
||||
{ value: "40%", label: "Increase in Online Order Re-Subscribing" },
|
||||
{ value: "2x", label: "More Loyalty Program Registrations" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Digital Nutrition and Ingredient Guides", description: "Place QR codes near specialized pet food racks. Owners scan to read full ingredient lists, allergy info, and feeding guides from their phone." },
|
||||
{ title: "One-Tap Loyalty and Rewards", description: "A QR at the checkout allows pet owners to join your rewards club and track their 'buy 10, get 1 free' progress without a paper card." },
|
||||
{ title: "Pet Care Tutorial Videos", description: "For complex products like aquarium setups or grooming tools, a QR on the shelf links to step-by-step video tutorials and setup guides." }
|
||||
],
|
||||
useCases: [
|
||||
"Nutrition and ingredient info on pet food racks",
|
||||
"Grooming and setup tutorial videos for products",
|
||||
"Loyalty and reward program enrollment at checkout",
|
||||
"Adoption event and community poster sign-ups",
|
||||
"Online re-order and subscription portal link"
|
||||
],
|
||||
tools: ["url-qr-code", "youtube-qr-code", "instagram-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do pet stores use QR codes for nutrition?", answer: "They place codes on food shelves that link to detailed nutritional breakdowns and portion calculators for different breeds." },
|
||||
{ question: "Can QR codes help with pet store loyalty cards?", answer: "Yes, by scanning a QR at the counter, customers can store their points and rewards digitally on their phone instead of carrying a paper card." },
|
||||
{ question: "Where should pet stores place QR codes?", answer: "The food aisles, high-tech product boxes, and the main checkout counter for loyalty sign-ups." }
|
||||
],
|
||||
placementTips: [
|
||||
"Specialized food section for nutrition guides",
|
||||
"Grooming or complex tool shelves for video tutorials",
|
||||
"Checkout counter for digital loyalty card storage"
|
||||
],
|
||||
germanKeyword: "Zoohandlungen"
|
||||
},
|
||||
{
|
||||
slug: "electronics-stores",
|
||||
name: "Electronics Stores",
|
||||
title: "QR Codes for Electronics Stores",
|
||||
metaTitle: "QR Codes for Electronics: Product Manuals & Demos",
|
||||
metaDescription: "Boost sales with QR codes for product manuals, unboxing videos, comparison charts, and warranty registration in-store.",
|
||||
headline: "QR Codes for Electronics Stores: No More Manuals, More Demos",
|
||||
subheadline: "Let customers watch unboxing videos and find exact specs from every product shelf",
|
||||
heroImage: "/marketing/industries/electronics-stores.png",
|
||||
statistics: [
|
||||
{ value: "2x", label: "Higher Higher Discovery-to-Lead Rates" },
|
||||
{ value: "45%", label: "Increase in Online Warranty Registrations" },
|
||||
{ value: "3x", label: "More Product Comparison Chart Views" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Unboxing and Demo Video Links", description: "Place a QR on the product display. Customers scan to watch real-world unboxing and performance videos, making the buying decision easier." },
|
||||
{ title: "Instant Product Comparison Charts", description: "A QR on the shelf edge links to a digital table comparing the current model with others in the same category, helping customers find the right fit." },
|
||||
{ title: "Simplified Warranty Registration", description: "Ensure every customer registers their product. A QR on the receipt or the box links to your online warranty portal for instant sign-up." }
|
||||
],
|
||||
useCases: [
|
||||
"Unboxing and demo videos on display models",
|
||||
"Digital comparison charts on shelf edges",
|
||||
"Warranty registration links on receipts and boxes",
|
||||
"Product manuals and setup guides via PDF QR",
|
||||
"In-store tech support and status update portal"
|
||||
],
|
||||
tools: ["url-qr-code", "youtube-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do electronics stores use QR codes for manuals?", answer: "They place QR codes on shelf labels or boxes that link to the official digital PDF manual—saving paper and making it easier to search." },
|
||||
{ question: "Can QR codes help with product comparisons?", answer: "Yes, the code on the shelf can link to a digital comparison tool where customers can see specs side-by-side on their own phone." },
|
||||
{ question: "Where are the best places for electronics store QR codes?", answer: "The shelf edge under the product name, on the back of display models for specs, and on the checkout receipt for warranties." }
|
||||
],
|
||||
placementTips: [
|
||||
"Shelf edge underทุก item for specs and demos",
|
||||
"Inside the product box for warranty and setup guides",
|
||||
"Display models for expert video review links"
|
||||
],
|
||||
germanKeyword: "Elektronikgeschäfte"
|
||||
},
|
||||
{
|
||||
slug: "jewelry-stores",
|
||||
name: "Jewelry Stores",
|
||||
title: "QR Codes for Jewelry Stores",
|
||||
metaTitle: "QR Codes for Jewelry: Authentication & Styling",
|
||||
metaDescription: "Give every piece a story with QR codes for certificates of authenticity, styling guides, and custom design inquiries.",
|
||||
headline: "QR Codes for Jewelry Stores: Every Piece Tells a Story",
|
||||
subheadline: "Let customers access certificates of authenticity and styling guides at every display case",
|
||||
heroImage: "/marketing/industries/jewelry-stores.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Higher Engagement with Certification Info" },
|
||||
{ value: "40%", label: "Increase in Custom Design Inquiries" },
|
||||
{ value: "2x", label: "More Social Media Referral Traffic" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Interactive Certificates of Authenticity", description: "Provide a QR code with every major purchase that links to the digital certification and history of the gemstone—boosting trust and transparency." },
|
||||
{ title: "Digital Style and Pairing Guides", description: "A QR on the display links to high-end lifestyle videos and guides showing how the piece can be styled and paired with other jewelry in your collection." },
|
||||
{ title: "Custom Design Collaboration", description: "Let clients start their unique journey. A QR on your showroom floor links to a design inquiry form where they can upload inspiration and book a consultation." }
|
||||
],
|
||||
useCases: [
|
||||
"Authenticity certificate and diamond report links",
|
||||
"Jewelry styling and care video guides",
|
||||
"Custom design inquiry and appointment booking",
|
||||
"Engagement and wedding band gallery links",
|
||||
"Instagram tagging for beautiful showroom pieces"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "whatsapp-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do jewelry stores use QR codes for certificates?", answer: "They provide a QR code that links to the specific gemstone's diamond report (GIA/IGI) or the store's own certificate of authenticity." },
|
||||
{ question: "Can I book a custom jewelry session via QR?", answer: "Yes, the code in the shop window or showroom can link directly to a booking form for a private consultation with a jeweler." },
|
||||
{ question: "Where should jewelry QR codes be placed?", answer: "Discreetly inside the display cases, on the physical gift card, and on the front of the jewelry box." }
|
||||
],
|
||||
placementTips: [
|
||||
"Bottom corner of every gemstone display case",
|
||||
"Inside the physical jewelry box for care and guides",
|
||||
"Physical gift cards for the digital story of the piece"
|
||||
],
|
||||
germanKeyword: "Juweliere"
|
||||
},
|
||||
{
|
||||
slug: "hardware-stores",
|
||||
name: "Hardware Stores",
|
||||
title: "QR Codes for Hardware Stores",
|
||||
metaTitle: "QR Codes for Hardware: DIY Tutorials & Equipment Rental",
|
||||
metaDescription: "Help your customers build better with QR codes for DIY tutorial videos, project guides, and easy equipment rental booking.",
|
||||
headline: "QR Codes for Hardware Stores: Your In-Store DIY Expert",
|
||||
subheadline: "Let customers scan for project tutorials and product setup videos at every aisle and shelf",
|
||||
heroImage: "/marketing/industries/hardware-stores.png",
|
||||
statistics: [
|
||||
{ value: "3x", label: "Higher Higher Discovery-to-Lead Rates" },
|
||||
{ value: "50%", label: "Faster Equipment Rental Booking" },
|
||||
{ value: "2x", label: "More Project Guide Video Completes" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "In-Aisle DIY Tutorials", description: "Place QR codes on shelves for power tools or plumbing. Customers scan to watch 'how-to' videos and project guides while they shop for their materials." },
|
||||
{ title: "Frictionless Equipment Rental", description: "A QR on the rental fleet in the store links directly to the booking calendar, allowing customers to reserve a tool for the weekend without waiting in line." },
|
||||
{ title: "Product Compatibility Check", description: "Link QR codes to digital tables that show which drill bits, blades, or filters are compatible with every major power tool you sell." }
|
||||
],
|
||||
useCases: [
|
||||
"DIY 'how-to' project videos and guide links",
|
||||
"Equipment and tool rental booking portal",
|
||||
"Product part and accessory compatibility tables",
|
||||
"Contractor account and loyalty sign-up links",
|
||||
"Lawn care and seasonal garden guide PDFs"
|
||||
],
|
||||
tools: ["url-qr-code", "youtube-qr-code", "pdf-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do hardware stores use QR codes for DIY?", answer: "They place codes on the shelf next to complex items like tiles or power tools that link to video tutorials showing how to use them." },
|
||||
{ question: "Can QR codes help with renting tools?", answer: "Yes, by scanning a QR on the rental machine, customers can check availability and book the tool for their duration immediately." },
|
||||
{ question: "Where is the best place to put a QR code in a DIY shop?", answer: "The end of product aisles for guide catalogs, on power tool display racks, and on the sides of large rental machines." }
|
||||
],
|
||||
placementTips: [
|
||||
"Aisle end banners for category-specific DIY guides",
|
||||
"Product boxes for assembly and warranty links",
|
||||
"Rental counter and equipment for booking access"
|
||||
],
|
||||
germanKeyword: "Baumärkte"
|
||||
},
|
||||
{
|
||||
slug: "cinemas",
|
||||
name: "Cinemas",
|
||||
title: "QR Codes for Cinemas",
|
||||
metaTitle: "QR Codes for Cinemas: Trailers & Fast-Track Concessions",
|
||||
metaDescription: "Modernize your cinema with QR codes for movie trailers, fast-track popcorn ordering, and future show booking in the lobby.",
|
||||
headline: "QR Codes for Cinemas: More Trailers, Less Queueing",
|
||||
subheadline: "Let guests watch trailers and order snacks from their phone while waiting for their film to start",
|
||||
heroImage: "/marketing/industries/cinemas.png",
|
||||
statistics: [
|
||||
{ value: "50%", label: "Reduction in Popcorn Stand Queues" },
|
||||
{ value: "2x", label: "More Trailers Watched via Poster QRs" },
|
||||
{ value: "3x", label: "Higher Loyalty Program Registrations" }
|
||||
],
|
||||
benefits: [
|
||||
{ title: "Instant Movie Trailers", description: "Place a QR code on every physical movie poster in the lobby. Guests scan to watch the high-res trailer and book tickets for that film on their phone." },
|
||||
{ title: "Queue-Free Concessions", description: "A QR in the lobby allows guests to pre-order and pay for popcorn and drinks, giving them a collection point and avoiding long queues." },
|
||||
{ title: "Digital Loyalty and Rewards", description: "A QR at the ticket counter or in the cinema's own app links to the loyalty program, where fans can earn points for every film they see." }
|
||||
],
|
||||
useCases: [
|
||||
"Movie trailers and ticket booking from posters",
|
||||
"In-lobby popcorn and snack pre-ordering",
|
||||
"Loyalty and reward membership sign-up at the door",
|
||||
"Exclusive behind-the-scenes content links",
|
||||
"Digital ticket wallet and gate entry scanning"
|
||||
],
|
||||
tools: ["url-qr-code", "youtube-qr-code", "app-store-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do cinemas use QR codes for trailers?", answer: "They place codes on the corner of movie posters so guests can watch the full trailer and book tickets for future dates as they walk past." },
|
||||
{ question: "Can I skip the popcorn line with a QR code?", answer: "Yes, many cinemas provide QR codes in the lobby that let you pre-order your snacks so they are ready for pickup just as your movie starts." },
|
||||
{ question: "Where should cinema QR codes be placed?", answer: "On the physical movie posters, the concessions counter, and at the entrance gates for ticket scanning." }
|
||||
],
|
||||
placementTips: [
|
||||
"Bottom-right of every physical movie poster",
|
||||
"Lobby counter area for fast-track snacks",
|
||||
"Exit doors for ticketing future releases"
|
||||
],
|
||||
germanKeyword: "Kinos"
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,212 +1,212 @@
|
||||
import type { Testimonial, AggregateRating } from '@/lib/types';
|
||||
export type { Testimonial, AggregateRating };
|
||||
|
||||
export const testimonials: Testimonial[] = [
|
||||
{
|
||||
id: "pottery-claudia-knuth-001",
|
||||
rating: 5,
|
||||
title: "Perfect for my pottery",
|
||||
content: "I use QR-Master for my pottery as a link to my homepage and as a digital business card. I place the codes directly on my pottery pieces so interested customers can instantly access my website. Reliable and practical – a great solution!",
|
||||
author: {
|
||||
name: "Claudia",
|
||||
role: "Owner",
|
||||
location: "Texas"
|
||||
},
|
||||
date: "January 2026",
|
||||
datePublished: "2026-01-15T00:00:00Z",
|
||||
verified: true,
|
||||
featured: true,
|
||||
useCase: "pottery"
|
||||
},
|
||||
{
|
||||
id: "restaurant-thomas-002",
|
||||
rating: 5,
|
||||
title: "Finally no more reprinting menus",
|
||||
content: "We used to reprint our menu every time prices changed — that was costing us hundreds of euros a year. With QR Master, I update the PDF online and all table QR codes instantly point to the new version. Setup took 10 minutes. This pays for itself after the first price adjustment.",
|
||||
author: {
|
||||
name: "Thomas B.",
|
||||
role: "Restaurant Owner",
|
||||
location: "Hamburg, Germany"
|
||||
},
|
||||
date: "February 2026",
|
||||
datePublished: "2026-02-08T00:00:00Z",
|
||||
verified: true,
|
||||
featured: true,
|
||||
useCase: "restaurant-menu"
|
||||
},
|
||||
{
|
||||
id: "marketing-sarah-003",
|
||||
rating: 5,
|
||||
title: "Finally measurable ROI on print campaigns",
|
||||
content: "We run multi-channel campaigns and print materials were always the black box. With QR Master's scan analytics I can now see exactly which flyer, city, and event drove traffic. The location and device breakdown alone justified the Business plan subscription.",
|
||||
author: {
|
||||
name: "Sarah M.",
|
||||
role: "Head of Marketing",
|
||||
location: "Berlin, Germany"
|
||||
},
|
||||
date: "March 2026",
|
||||
datePublished: "2026-03-10T00:00:00Z",
|
||||
verified: true,
|
||||
featured: true,
|
||||
useCase: "marketing-campaigns"
|
||||
},
|
||||
{
|
||||
id: "retail-jennifer-004",
|
||||
rating: 5,
|
||||
title: "Generated 800 unique codes in under 5 minutes",
|
||||
content: "We needed individual QR codes for product packaging — one per SKU pointing to the corresponding product page. I uploaded our CSV with 800 rows and had every code ready to download in minutes. The bulk feature is exactly what large-scale packaging operations need.",
|
||||
author: {
|
||||
name: "Jennifer K.",
|
||||
role: "Operations Manager",
|
||||
location: "London, UK"
|
||||
},
|
||||
date: "March 2026",
|
||||
datePublished: "2026-03-18T00:00:00Z",
|
||||
verified: true,
|
||||
featured: true,
|
||||
useCase: "retail-packaging"
|
||||
},
|
||||
{
|
||||
id: "events-marco-005",
|
||||
rating: 5,
|
||||
title: "Essential tool for event management",
|
||||
content: "We manage 20+ events per year and QR Master has become part of our standard workflow. I create a dynamic code for each event, and if the venue or schedule changes last-minute, I update the link without reprinting anything. The scan data also helps us measure engagement per event.",
|
||||
author: {
|
||||
name: "Marco F.",
|
||||
role: "Event Director",
|
||||
location: "Munich, Germany"
|
||||
},
|
||||
date: "January 2026",
|
||||
datePublished: "2026-01-28T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "events"
|
||||
},
|
||||
{
|
||||
id: "gdpr-stefan-006",
|
||||
rating: 5,
|
||||
title: "The only QR tool that takes GDPR seriously",
|
||||
content: "Our legal team reviewed several platforms before approving one for use. QR Master hashes IPs by default and doesn't store personally identifiable information — we also confirmed DNT header compliance. For a company operating under GDPR, this isn't optional, it's the baseline. QR Master delivered.",
|
||||
author: {
|
||||
name: "Stefan W.",
|
||||
role: "Data Protection Officer",
|
||||
location: "Frankfurt, Germany"
|
||||
},
|
||||
date: "February 2026",
|
||||
datePublished: "2026-02-20T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "gdpr-compliance"
|
||||
},
|
||||
{
|
||||
id: "agency-david-007",
|
||||
rating: 4,
|
||||
title: "Great platform for managing multiple client campaigns",
|
||||
content: "I manage QR code campaigns for six clients and QR Master keeps everything organized in one dashboard. The dynamic links mean I can redirect codes per campaign phase without going back to print. Does exactly what it says — clean, reliable, no bloat.",
|
||||
author: {
|
||||
name: "David L.",
|
||||
role: "Digital Marketing Consultant",
|
||||
location: "Vienna, Austria"
|
||||
},
|
||||
date: "March 2026",
|
||||
datePublished: "2026-03-05T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "agency"
|
||||
},
|
||||
{
|
||||
id: "fitness-anna-008",
|
||||
rating: 5,
|
||||
title: "Members scan for class schedules, WiFi, and waivers",
|
||||
content: "I put QR codes on every surface in my studio — reception desk, mirrors, equipment. One links to our class booking page, one shares WiFi, one opens the digital waiver form. When we switched booking software, I updated the URL in QR Master and nothing else had to change. Brilliant.",
|
||||
author: {
|
||||
name: "Anna R.",
|
||||
role: "Studio Owner",
|
||||
location: "Zurich, Switzerland"
|
||||
},
|
||||
date: "February 2026",
|
||||
datePublished: "2026-02-14T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "fitness-studio"
|
||||
},
|
||||
{
|
||||
id: "hotel-pierre-009",
|
||||
rating: 5,
|
||||
title: "Contactless check-in and room service made easy",
|
||||
content: "We placed dynamic QR codes on every room door and in the lobby. Guests scan to access our digital welcome guide, room service menu, and local recommendations — all updated centrally. When our restaurant hours changed, one edit in QR Master updated all 48 room codes instantly.",
|
||||
author: {
|
||||
name: "Pierre D.",
|
||||
role: "General Manager",
|
||||
location: "Paris, France"
|
||||
},
|
||||
date: "January 2026",
|
||||
datePublished: "2026-01-22T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "hospitality"
|
||||
},
|
||||
{
|
||||
id: "freelancer-maya-010",
|
||||
rating: 5,
|
||||
title: "My vCard QR is now on every business card I print",
|
||||
content: "I switched to a vCard QR code from QR Master after my phone number changed and I had to throw away 200 printed cards. Now the QR on my card always points to the latest version of my contact info — I just update it online. It's the smartest small change I've made to my personal branding.",
|
||||
author: {
|
||||
name: "Maya S.",
|
||||
role: "Freelance Brand Designer",
|
||||
location: "London, UK"
|
||||
},
|
||||
date: "March 2026",
|
||||
datePublished: "2026-03-25T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "vcard"
|
||||
},
|
||||
{
|
||||
id: "cafe-lars-011",
|
||||
rating: 5,
|
||||
title: "WiFi sharing has never been this clean",
|
||||
content: "Customers used to ask for our WiFi password 30 times a day. Now there's a QR code on every table — they scan, connect, done. We also linked a second code to our digital menu. QR Master was the easiest free tool I found that actually lets you keep the codes active without surprise paywalls.",
|
||||
author: {
|
||||
name: "Lars N.",
|
||||
role: "Co-Owner",
|
||||
location: "Copenhagen, Denmark"
|
||||
},
|
||||
date: "February 2026",
|
||||
datePublished: "2026-02-03T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "cafe-wifi"
|
||||
},
|
||||
{
|
||||
id: "realestate-carlos-012",
|
||||
rating: 5,
|
||||
title: "Property listing QR codes that stay current",
|
||||
content: "Real estate moves fast — a property I list today might be under offer tomorrow. With dynamic QR codes from QR Master, I print a yard sign once and update the link to the listing, price, or status page whenever things change. My signs stay accurate without paying for new prints.",
|
||||
author: {
|
||||
name: "Carlos M.",
|
||||
role: "Real Estate Agent",
|
||||
location: "Miami, FL"
|
||||
},
|
||||
date: "March 2026",
|
||||
datePublished: "2026-03-12T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "real-estate"
|
||||
}
|
||||
];
|
||||
|
||||
export function getAggregateRating(): AggregateRating {
|
||||
const ratings = testimonials.map(t => t.rating);
|
||||
const avgRating = ratings.reduce((a, b) => a + b, 0) / ratings.length;
|
||||
return {
|
||||
ratingValue: Number(avgRating.toFixed(1)),
|
||||
reviewCount: testimonials.length,
|
||||
bestRating: 5,
|
||||
worstRating: 1
|
||||
};
|
||||
}
|
||||
|
||||
export function getFeaturedTestimonials(): Testimonial[] {
|
||||
return testimonials.filter(t => t.featured);
|
||||
}
|
||||
import type { Testimonial, AggregateRating } from '@/lib/types';
|
||||
export type { Testimonial, AggregateRating };
|
||||
|
||||
export const testimonials: Testimonial[] = [
|
||||
{
|
||||
id: "pottery-claudia-knuth-001",
|
||||
rating: 5,
|
||||
title: "Perfect for my pottery",
|
||||
content: "I use QR-Master for my pottery as a link to my homepage and as a digital business card. I place the codes directly on my pottery pieces so interested customers can instantly access my website. Reliable and practical – a great solution!",
|
||||
author: {
|
||||
name: "Claudia",
|
||||
role: "Owner",
|
||||
location: "Texas"
|
||||
},
|
||||
date: "January 2026",
|
||||
datePublished: "2026-01-15T00:00:00Z",
|
||||
verified: true,
|
||||
featured: true,
|
||||
useCase: "pottery"
|
||||
},
|
||||
{
|
||||
id: "restaurant-thomas-002",
|
||||
rating: 5,
|
||||
title: "Finally no more reprinting menus",
|
||||
content: "We used to reprint our menu every time prices changed — that was costing us hundreds of euros a year. With QR Master, I update the PDF online and all table QR codes instantly point to the new version. Setup took 10 minutes. This pays for itself after the first price adjustment.",
|
||||
author: {
|
||||
name: "Thomas B.",
|
||||
role: "Restaurant Owner",
|
||||
location: "Hamburg, Germany"
|
||||
},
|
||||
date: "February 2026",
|
||||
datePublished: "2026-02-08T00:00:00Z",
|
||||
verified: true,
|
||||
featured: true,
|
||||
useCase: "restaurant-menu"
|
||||
},
|
||||
{
|
||||
id: "marketing-sarah-003",
|
||||
rating: 5,
|
||||
title: "Finally measurable ROI on print campaigns",
|
||||
content: "We run multi-channel campaigns and print materials were always the black box. With QR Master's scan analytics I can now see exactly which flyer, city, and event drove traffic. The location and device breakdown alone justified the Business plan subscription.",
|
||||
author: {
|
||||
name: "Sarah M.",
|
||||
role: "Head of Marketing",
|
||||
location: "Berlin, Germany"
|
||||
},
|
||||
date: "March 2026",
|
||||
datePublished: "2026-03-10T00:00:00Z",
|
||||
verified: true,
|
||||
featured: true,
|
||||
useCase: "marketing-campaigns"
|
||||
},
|
||||
{
|
||||
id: "retail-jennifer-004",
|
||||
rating: 5,
|
||||
title: "Generated 800 unique codes in under 5 minutes",
|
||||
content: "We needed individual QR codes for product packaging — one per SKU pointing to the corresponding product page. I uploaded our CSV with 800 rows and had every code ready to download in minutes. The bulk feature is exactly what large-scale packaging operations need.",
|
||||
author: {
|
||||
name: "Jennifer K.",
|
||||
role: "Operations Manager",
|
||||
location: "London, UK"
|
||||
},
|
||||
date: "March 2026",
|
||||
datePublished: "2026-03-18T00:00:00Z",
|
||||
verified: true,
|
||||
featured: true,
|
||||
useCase: "retail-packaging"
|
||||
},
|
||||
{
|
||||
id: "events-marco-005",
|
||||
rating: 5,
|
||||
title: "Essential tool for event management",
|
||||
content: "We manage 20+ events per year and QR Master has become part of our standard workflow. I create a dynamic code for each event, and if the venue or schedule changes last-minute, I update the link without reprinting anything. The scan data also helps us measure engagement per event.",
|
||||
author: {
|
||||
name: "Marco F.",
|
||||
role: "Event Director",
|
||||
location: "Munich, Germany"
|
||||
},
|
||||
date: "January 2026",
|
||||
datePublished: "2026-01-28T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "events"
|
||||
},
|
||||
{
|
||||
id: "gdpr-stefan-006",
|
||||
rating: 5,
|
||||
title: "The only QR tool that takes GDPR seriously",
|
||||
content: "Our legal team reviewed several platforms before approving one for use. QR Master hashes IPs by default and doesn't store personally identifiable information — we also confirmed DNT header compliance. For a company operating under GDPR, this isn't optional, it's the baseline. QR Master delivered.",
|
||||
author: {
|
||||
name: "Stefan W.",
|
||||
role: "Data Protection Officer",
|
||||
location: "Frankfurt, Germany"
|
||||
},
|
||||
date: "February 2026",
|
||||
datePublished: "2026-02-20T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "gdpr-compliance"
|
||||
},
|
||||
{
|
||||
id: "agency-david-007",
|
||||
rating: 4,
|
||||
title: "Great platform for managing multiple client campaigns",
|
||||
content: "I manage QR code campaigns for six clients and QR Master keeps everything organized in one dashboard. The dynamic links mean I can redirect codes per campaign phase without going back to print. Does exactly what it says — clean, reliable, no bloat.",
|
||||
author: {
|
||||
name: "David L.",
|
||||
role: "Digital Marketing Consultant",
|
||||
location: "Vienna, Austria"
|
||||
},
|
||||
date: "March 2026",
|
||||
datePublished: "2026-03-05T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "agency"
|
||||
},
|
||||
{
|
||||
id: "fitness-anna-008",
|
||||
rating: 5,
|
||||
title: "Members scan for class schedules, WiFi, and waivers",
|
||||
content: "I put QR codes on every surface in my studio — reception desk, mirrors, equipment. One links to our class booking page, one shares WiFi, one opens the digital waiver form. When we switched booking software, I updated the URL in QR Master and nothing else had to change. Brilliant.",
|
||||
author: {
|
||||
name: "Anna R.",
|
||||
role: "Studio Owner",
|
||||
location: "Zurich, Switzerland"
|
||||
},
|
||||
date: "February 2026",
|
||||
datePublished: "2026-02-14T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "fitness-studio"
|
||||
},
|
||||
{
|
||||
id: "hotel-pierre-009",
|
||||
rating: 5,
|
||||
title: "Contactless check-in and room service made easy",
|
||||
content: "We placed dynamic QR codes on every room door and in the lobby. Guests scan to access our digital welcome guide, room service menu, and local recommendations — all updated centrally. When our restaurant hours changed, one edit in QR Master updated all 48 room codes instantly.",
|
||||
author: {
|
||||
name: "Pierre D.",
|
||||
role: "General Manager",
|
||||
location: "Paris, France"
|
||||
},
|
||||
date: "January 2026",
|
||||
datePublished: "2026-01-22T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "hospitality"
|
||||
},
|
||||
{
|
||||
id: "freelancer-maya-010",
|
||||
rating: 5,
|
||||
title: "My vCard QR is now on every business card I print",
|
||||
content: "I switched to a vCard QR code from QR Master after my phone number changed and I had to throw away 200 printed cards. Now the QR on my card always points to the latest version of my contact info — I just update it online. It's the smartest small change I've made to my personal branding.",
|
||||
author: {
|
||||
name: "Maya S.",
|
||||
role: "Freelance Brand Designer",
|
||||
location: "London, UK"
|
||||
},
|
||||
date: "March 2026",
|
||||
datePublished: "2026-03-25T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "vcard"
|
||||
},
|
||||
{
|
||||
id: "cafe-lars-011",
|
||||
rating: 5,
|
||||
title: "WiFi sharing has never been this clean",
|
||||
content: "Customers used to ask for our WiFi password 30 times a day. Now there's a QR code on every table — they scan, connect, done. We also linked a second code to our digital menu. QR Master was the easiest free tool I found that actually lets you keep the codes active without surprise paywalls.",
|
||||
author: {
|
||||
name: "Lars N.",
|
||||
role: "Co-Owner",
|
||||
location: "Copenhagen, Denmark"
|
||||
},
|
||||
date: "February 2026",
|
||||
datePublished: "2026-02-03T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "cafe-wifi"
|
||||
},
|
||||
{
|
||||
id: "realestate-carlos-012",
|
||||
rating: 5,
|
||||
title: "Property listing QR codes that stay current",
|
||||
content: "Real estate moves fast — a property I list today might be under offer tomorrow. With dynamic QR codes from QR Master, I print a yard sign once and update the link to the listing, price, or status page whenever things change. My signs stay accurate without paying for new prints.",
|
||||
author: {
|
||||
name: "Carlos M.",
|
||||
role: "Real Estate Agent",
|
||||
location: "Miami, FL"
|
||||
},
|
||||
date: "March 2026",
|
||||
datePublished: "2026-03-12T00:00:00Z",
|
||||
verified: true,
|
||||
featured: false,
|
||||
useCase: "real-estate"
|
||||
}
|
||||
];
|
||||
|
||||
export function getAggregateRating(): AggregateRating {
|
||||
const ratings = testimonials.map(t => t.rating);
|
||||
const avgRating = ratings.reduce((a, b) => a + b, 0) / ratings.length;
|
||||
return {
|
||||
ratingValue: Number(avgRating.toFixed(1)),
|
||||
reviewCount: testimonials.length,
|
||||
bestRating: 5,
|
||||
worstRating: 1
|
||||
};
|
||||
}
|
||||
|
||||
export function getFeaturedTestimonials(): Testimonial[] {
|
||||
return testimonials.filter(t => t.featured);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user