diff --git a/CHECKLISTE_UMSETZUNG_2026-07-27.md b/CHECKLISTE_UMSETZUNG_2026-07-27.md
new file mode 100644
index 0000000..23c4e5a
--- /dev/null
+++ b/CHECKLISTE_UMSETZUNG_2026-07-27.md
@@ -0,0 +1,131 @@
+# Checkliste - Stand nach der Umsetzung
+
+27. Juli 2026. 118 Dateien, 0 Syntaxfehler, Prisma-Schema valide, beide i18n-JSONs valide, keine langen Striche.
+
+---
+
+## FERTIG
+
+### `/create` - Limit-Moment
+
+- [x] Redirect auf `/pricing` entfernt, Formularzustand überlebt den 403
+- [x] `UpgradeModal.tsx` mit drei Anlässen: `limit`, `logo`, `shapes`
+- [x] Direkt-Checkout mit `returnPath` (pfadvalidiert gegen offene Redirects)
+- [x] Option "bestehenden Code pausieren", danach Auto-Retry des Speicherns
+- [x] Option "als statischen Code weitermachen"
+- [x] Scan-Zahlen der letzten 30 Tage je Code, damit die Pausier-Entscheidung informiert ist
+- [x] PostHog-Events für alle vier Ausgänge
+
+### Limit-Logik und gestopfte Löcher
+
+- [x] `POST /api/qrs` und `GET /api/user/stats` zählen nur noch `status: 'ACTIVE'`
+- [x] `GET /api/qrs` liefert `scans30d` je Code
+- [x] `PATCH /api/qrs/[id]` akzeptiert `status` (kannte es vorher nicht, Pausieren wäre wirkungslos gewesen)
+- [x] Reaktivieren prüft gegen das Kontingent (sonst Limit-Umgehung durch pausieren/neu/reaktivieren)
+- [x] `bulk-creation` lädt das Restkontingent serverseitig nach jedem Lauf
+
+### `/bulk-creation`
+
+- [x] Stille Fehler beendet: fehlgeschlagene Zeilen werden gesammelt statt verschluckt
+- [x] Ergebnis-Panel mit Zeilennummer, Titel und Grund je Fehlzeile
+- [x] CSV-Download der fehlenden Zeilen
+- [x] Grüner Toast nur bei vollständigem Lauf
+- [x] Drei deutsche Toasts auf Englisch
+
+### Upgrade-Wege
+
+- [x] Neue Seite `/upgrade` in der `(app)`-Route-Group, Sidebar bleibt
+- [x] Alle 6 In-App-Links umgebogen: AppLayout, Dashboard, Settings (2x), Bulk (2x)
+- [x] `reason`- und `from`-Parameter, `from` geht als `returnPath` in den Checkout
+
+### QR-Design
+
+- [x] Showcase von Canvas auf **SVG** portiert (`lib/qr-shapes.ts`)
+- [x] `StyledQRCode.tsx` für die Live-Vorschau, `lib/render-qr-svg.ts` für Bulk - gemeinsame Shape-Funktionen, können nicht auseinanderlaufen
+- [x] 11 Modulformen, plangestaffelt: Free `square`, Pro 4, Business alle
+- [x] Eye-Frames und Eye-Balls getrennt wählbar
+- [x] Farbverläufe linear und radial, Business
+- [x] Gesperrte Formen sind klickbar: Vorschau zuerst, Modal danach
+- [x] Automatische Anhebung der Fehlerkorrektur bei kritischen Formen und bei Logo, mit sichtbarer Begründung
+- [x] Druckgrößen- und Test-Scan-Hinweis
+- [x] Toter `toPng`-Aufruf im SVG-Download entfernt
+
+### Scanbarkeit - gemessen, nicht geschätzt
+
+Gerendert und mit einem echten Decoder zurückgelesen, 5 Inhalte x mehrere Auflösungen:
+
+- [x] Alle 11 Modulformen 5/5
+- [x] Eye-Balls square, rounded, circle, diamond, hexagon 5/5
+- [x] **star als Eye-Ball 3/5 - entfernt**
+- [x] **Eye-Frames circle, leaf, flower, hexagon 0/15 - entfernt**
+- [x] **Rundungsradius 0.28 0/15, auf 0.10 korrigiert (15/15)**
+- [x] Alle 10 verbleibenden Eye-Kombinationen 30/30
+- [x] Bulk-Renderer separat geprüft, inklusive Verlauf: 12/12
+
+### Design-Vorlagen
+
+- [x] `QRDesignPreset`-Modell in Prisma, Relation am User
+- [x] `GET/POST/DELETE /api/design-presets`, Business-gated, CSRF-geschützt
+- [x] Gleicher Name überschreibt statt Duplikat, Obergrenze 50
+- [x] Speichern und Anwenden in `/create`
+- [x] Preset-Auswahl im Bulk-Flow: ein Design für den ganzen Upload
+
+### Retention-Mails
+
+- [x] Tag-3-Betreff entschärft
+- [x] Tag-7 zur verhaltensbasierten Limit-Mail umgebaut
+- [x] Tabellenzeile `CSV export ✓/✓` gestrichen, `Brand colors` ersetzt (war nach der Farbfreigabe falsch)
+- [x] Neue Erster-Scan-Mail, einziger Trigger ohne Kalender
+- [x] Tag-30 auf eigene Scan-Zahlen, unbelegte Testimonial-Behauptung gestrichen
+- [x] Tag-30 entfällt bei null Scans
+- [x] Cron-Route neu, vier Trigger
+
+### Farben ab Free und Folge-Copy
+
+- [x] `canCustomizeColors = true`, neue Gates `canUseShapes`, `canUseLogo`, `canUseFullDesign`
+- [x] `en.json`, `PricingClient`, Dashboard-Pro-Karte
+- [x] `/alternatives/flowcode`, 5 Stellen
+- [x] `/pricing` FAQ: Bulk-Antwort war noch static-only
+- [x] `competitor-data.ts`: Vergleichszeile führte Farben als Pro
+
+### Formales
+
+- [x] 729 lange Striche ersetzt, keine mehr im Quellcode
+- [x] CRLF-Zeilenenden durchgehend erhalten
+- [x] SQL-Datei aktualisiert: Block 4 ist jetzt Pflicht, nicht optional
+
+---
+
+## NICHT GEBAUT
+
+### Vor dem Deploy zwingend
+
+- [ ] **SQL ausführen** (`sql/2026-07-27_cro_retention_design.txt`), alle sechs Blöcke
+- [ ] **`npx prisma generate`** - ohne das kennt der Client `QRDesignPreset` nicht und `/api/design-presets` wirft zur Laufzeit
+- [ ] **Block 3.2** - sonst geht die Erster-Scan-Mail an die gesamte Bestandsbasis
+- [ ] Entscheidung zu Block 3.1
+
+### Nicht getestet
+
+- [ ] Kein Browser-Durchlauf. Der Renderer ist gegen einen Decoder geprüft, nicht im echten DOM
+- [ ] PNG-Download über `html-to-image` mit dem neuen SVG nicht verifiziert
+- [ ] SVG-Download nicht verifiziert
+- [ ] Logo-Overlay im neuen Renderer nicht visuell geprüft
+- [ ] Stripe-Checkout mit `returnPath` nicht live durchlaufen
+- [ ] Pausieren und Reaktivieren nicht gegen echte Daten
+- [ ] Preset speichern, laden, auf Bulk anwenden nicht gegen echte Daten
+
+### Bewusst weggelassen
+
+- [ ] Rahmen mit Label als Vektor. Die bestehenden Rahmen sind HTML um den QR herum; der SVG-Download fällt dort weiterhin auf PNG zurück und sagt es
+- [ ] Logo-Formen (Punkt, Herz, Blitz, WLAN) aus der Showcase
+- [ ] Eye-Frames circle, leaf, flower, hexagon - Messergebnis 0/15
+- [ ] star als Eye-Ball - 3/5
+
+### Offene inhaltliche Frage
+
+- [ ] Tag-30-Mail: gab es die Gespräche mit Pro-Nutzern über Branding? Bei Ja gehört ein wörtliches Zitat rein
+
+### Vorbehalt zur Messung
+
+Getestet wurde mit OpenCVs Decoder. Der ist strenger als iPhone- und Android-Kameras. Es ist möglich, dass ein Kreis-Eye-Frame auf echten Geräten funktioniert - aber 0 von 15 ist kein Rauschen, und das ist nichts, was man ungetestet auf Druckmaterial loslässt.
diff --git a/CTR_REWRITE_2026-07-27.md b/CTR_REWRITE_2026-07-27.md
new file mode 100644
index 0000000..d9ab4b7
--- /dev/null
+++ b/CTR_REWRITE_2026-07-27.md
@@ -0,0 +1,71 @@
+# SERP-CTR Rewrite — 27. Juli 2026
+
+Grundlage: GSC-Export 28 Tage (8.944 Impressionen, 52 Klicks, 0,58% CTR, Ø-Position 33,0)
+Methodik: `direct-response-copywriting` + Positioning Decision vom 26.07.2026 (Direction 1 als Positionierung, Direction 2 als Proof-Layer)
+
+## Ehrliche Einordnung des 5–10%-Ziels
+
+Site-weite CTR von 5–10% ist bei Ø-Position 33 **nicht durch Copy erreichbar**. CTR ist primär eine Funktion der Position:
+
+| Position | Erwartete CTR |
+|---|---|
+| 1 | 28–40% |
+| 3 | 10–11% |
+| 5 | 5–6% |
+| 10 | 2–3% |
+| 15 | 1–2% |
+| 30+ | <0,3% |
+
+Die zwei größten Impression-Seiten (`/dynamic-qr-code-generator` 1.694 Imp @ Pos 33,5 und `/qr-code-tracking` 1.023 Imp @ Pos 34,0) haben 0% CTR. Das ist bei Position 33 **normal** — 31% aller Impressionen der Site liegen auf Seite 4 der SERP. Kein Title der Welt repariert das; das ist ein Ranking-Thema.
+
+**Was Copy realistisch leisten kann:**
+- Seiten auf Pos 1–15 auf ihren Positions-Benchmark heben → aus 52 Klicks werden ca. 90–120
+- Die großen Volumenseiten so vorbereiten, dass sie beim Aufstieg auf Seite 1 sofort auf Benchmark klicken statt bei 2%
+
+5–10% site-weit wird erst realistisch, wenn die Ø-Position unter ~10 liegt. Dann trägt diese Copy den Unterschied.
+
+## Die drei strukturellen Fehler, die behoben wurden
+
+**1. Deutsche Halbsätze in englischen Descriptions.** 97% der Impressionen kommen aus USA/Indien/englischsprachigen Märkten. Trotzdem stand in den Tool-Descriptions „Teams QR Code erstellen", „Zoom QR Code erstellen", „TikTok QR Code erstellen", „Erstelle Bitcoin & Ethereum QR Codes", „Standort teilen leicht gemacht". Für einen englischen Sucher liest sich das wie maschinell übersetzter Spam. Das ist die wahrscheinlichste Ursache für 0,61% CTR auf Pos 10 (`barbershops`) und 0,65% auf Pos 17 (`zoom`).
+
+**2. `| QR Master`-Suffix und 65+ Zeichen lange Titles.** Google schneidet bei ~580px ab. `Free Microsoft Teams QR Code Generator | Join Meetings | QR Master` (66 Zeichen) wurde in der SERP abgeschnitten — der Nutzen verschwand im „…". Alle neuen Titles liegen bei 42–55 Zeichen.
+
+**3. Null Pattern Interruption.** Jedes Ergebnis in dieser SERP sagt „Free QR Code Generator". Die alten Descriptions sagten „Instant and free", „Free & Easy" — die vagesten möglichen Claims. Neu: konkrete, falsifizierbare Spezifika (3 dynamische Codes gratis, 1.000 Codes aus einem Spreadsheet, 42 Zeichen Wallet-Adresse, EAN-13/UPC-A/Code 128).
+
+## Wichtigste Änderungen
+
+| Seite | Imp | CTR | Pos | Kernänderung |
+|---|---|---|---|---|
+| `/` | 1.046 | 4,88% | 4,6 | Title: Mechanismus statt Kategorie. Description: 3 verifizierte Zahlen statt Feature-Liste. H1: „The Link Doesn't Have to Stay Wrong Once It's Printed" |
+| `/qr-code-tracking` | 1.023 | 0% | 34,0 | Title war „QR Code Tracking: Track QR Code Scans" — redundant, kein Nutzenversprechen. Neu: „See Which Placement Drove the Scan" (= Main-Headline-Idee aus Offer Brief §14) |
+| `/dynamic-qr-code-generator` | 1.694 | 0% | 33,5 | Title war schon gut, blieb. Description auf Schmerz + Zahlen umgestellt |
+| `/tools/google-review-qr-code` | 566 | 0,18% | 24,8 | Description führt jetzt mit dem Moment: „while the customer is still standing there" |
+| `/tools/teams-qr-code` | 277 | 3,25% | 9,6 | Title von 66 auf 48 Zeichen, Deutsch raus |
+| `/qr-code-for/barbershops` | 163 | 0,61% | 10,0 | metaTitle war 68 Zeichen → Fallback griff auf generisches „QR Codes for Barbershops \| QR Master". Jetzt 44 Zeichen mit Nutzen |
+| `/learn` | 226 | 0,88% | 11,3 | Zielt auf „qr mastery" (130 Imp @ Pos 5,1). Description führt jetzt mit den Problemen statt mit „Learning Hub" |
+| `/bulk-qr-code-generator` | 296 | 2,36% | 23,1 | Description nennt offen „Output is static — not dynamic or trackable" (Belief 5 / Proof-Layer) |
+
+Zusätzlich überarbeitet: crypto, facebook, url, vcard, instagram, tiktok, twitter, zoom, geolocation, barcode-generator, custom-qr-code-generator, alternatives/beaconstac, blog/microsoft-teams-qr-code.
+
+## Zwei Korrekturen an bestehenden Claims
+
+- **`/alternatives/beaconstac`**: Erster Entwurf enthielt „Not €99+" über Uniqode. Der Offer Brief verifiziert Uniqodes Preis **nicht** (nur „G2-Muster: teuer für kleine Teams"). Behauptung entfernt — genannt werden nur die eigenen, verifizierten Preise €0/€9/€29.
+- **Pricing-Card**: „Bulk QR Creation (up to 1,000)" → „(up to 1,000, static output)" in `en.json`. Das war Immediate Next Action #1 aus dem Offer Brief (A2) und die größte Transparenz-Lücke auf der Seite.
+
+## Was als Nächstes mehr bringt als weitere Copy
+
+1. **Ranking, nicht CTR.** 31% der Impressionen liegen auf Position 30+. Der Hebel dort ist Content/Links, nicht der Title.
+2. **Die Kündigungs-Policy (Offer Brief A3).** ToS §4 sagt „may affect the availability of dynamic QR codes". Für genau diese Zielgruppe ist das das Erkennungszeichen der schlechten Anbieter. Blockiert den gesamten Proof-Layer.
+3. **10–15 unabhängige Reviews auf G2/Capterra.** Laut Offer Brief §8 höher priorisiert als jede Copy-Iteration.
+
+## Messung
+
+In 4 Wochen im GSC vergleichen — **pro Seite und positionsbereinigt**, nicht site-weit:
+
+- `/` — Ziel 7–9% (von 4,88%, bei stabiler Pos ~4,6)
+- `/learn` — Ziel 1,8–2,5% (von 0,88%)
+- `/qr-code-for/barbershops` — Ziel 2,0–2,5% (von 0,61%)
+- `/tools/zoom-qr-code` — Ziel 1,2% (von 0,65%)
+- `/tools/teams-qr-code` — Ziel 4–5% (von 3,25%)
+
+Wenn die Position sich gleichzeitig verändert, ist der Vergleich der Rohwerte wertlos — dann `gsc-ctr-by-position` verwenden.
diff --git a/PLAN_CRO_UND_RETENTION_2026-07-27.md b/PLAN_CRO_UND_RETENTION_2026-07-27.md
new file mode 100644
index 0000000..4702f19
--- /dev/null
+++ b/PLAN_CRO_UND_RETENTION_2026-07-27.md
@@ -0,0 +1,421 @@
+# Umsetzungsplan - CRO-Momente, Upgrade-Wege und Retention-Mails
+
+Stand 27. Juli 2026. Alle Datei- und Zeilenangaben gegen den aktuellen Stand verifiziert.
+
+**Getroffene Entscheidungen:**
+- Das Dynamic-Limit zählt künftig nur noch Codes mit `status = ACTIVE`. Pausieren gibt einen Slot frei.
+- Der Upgrade-Weg im Limit-Moment läuft per Direkt-Checkout aus dem Modal. Kein Redirect auf `/pricing`.
+
+---
+
+## Phase 1 - Der Limit-Moment in `/create`
+
+### 1.1 Limit-Query auf ACTIVE umstellen
+
+**Datei:** `src/app/(main)/api/qrs/route.ts`, Zeile 117-121
+
+```ts
+const dynamicQRCount = await db.qRCode.count({
+ where: { userId, type: 'DYNAMIC', status: 'ACTIVE' },
+});
+```
+
+Dieselbe Änderung in `src/app/(main)/api/user/stats/route.ts`, sonst zeigt das Dashboard eine andere Zahl als die API durchlässt.
+
+**Nebenwirkung, die vorher klar sein muss:** Free-Nutzer, die heute pausierte Codes haben, bekommen dadurch rückwirkend Slots frei. Das ist eine Lockerung, keine Verschärfung - es nimmt niemandem etwas weg. Vor dem Deploy einmal zählen, wie viele Nutzer betroffen sind (SQL unten in Abschnitt „Prüf-Queries").
+
+### 1.2 Das Limit-Modal
+
+**Neue Datei:** `src/components/app/DynamicLimitModal.tsx`
+
+Der 403 aus `/api/qrs` liefert bereits `currentCount`, `limit` und `plan` mit. Es braucht keinen zusätzlichen Request.
+
+**Datei:** `src/app/(main)/(app)/create/page.tsx`, Zeile 456-459 - der Redirect entfällt ersatzlos:
+
+```ts
+if (response.status === 403 && responseData.error === 'Limit reached') {
+ setLimitInfo({ current: responseData.currentCount, limit: responseData.limit, plan: responseData.plan });
+ setLimitModalOpen(true);
+ return;
+}
+```
+
+Der Formular-State bleibt dadurch erhalten. Das ist der eigentliche Fix - alles andere ist Ausgestaltung.
+
+**Copy des Modals:**
+
+> ### Dein vierter Code ist fertig. Er braucht nur noch einen Platz.
+>
+> Du nutzt alle 3 dynamischen Codes deines kostenlosen Plans. Dieser hier ist gebaut und wartet - du kannst ihn behalten oder einen bestehenden freigeben.
+>
+> **[ Diesen Code mit Pro speichern - 9 € / Monat ]**
+> [ Einen bestehenden Code pausieren ]
+> [ Stattdessen als statischen Code herunterladen ]
+>
+> *Deine 3 aktiven Codes laufen weiter, egal wie du dich entscheidest.*
+
+Die Zahlen (`vierter`, `3`) kommen aus `limitInfo`, damit das Modal auch für Pro bei 51 stimmt.
+
+**Warum diese drei Optionen:**
+
+Der Hauptbutton verkauft keinen Plan, sondern rettet einen konkreten Code, den der Nutzer gerade in der Hand hat. Option 2 ist die ehrliche Alternative innerhalb des Free-Plans - sie kostet ein paar Conversions und kauft dafür Belief 5 aus dem Necessary-Beliefs-Doc. Option 3 ist der Ausweg ohne Verlust: ein statischer Code löst das Problem zu einem guten Teil, kostet nichts, und die Zusage „läuft nie ab" ist verifiziert. Der Schlusssatz ist Risk Reversal genau an der Stelle, an der die Kategorie ihren schlechtesten Ruf hat.
+
+### 1.3 Direkt-Checkout aus dem Modal
+
+Der Pro-Button ruft `/api/stripe/checkout` direkt auf, mit `priceId`, `plan: 'PRO'` und `userEmail`.
+
+**Datei:** `src/app/(main)/api/stripe/checkout/route.ts`, Zeile 63. Die `success_url` ist aktuell fest auf `/dashboard?success=true`. Sie muss eine optionale `returnPath` aus dem Request-Body akzeptieren, damit der Nutzer nach dem Kauf dorthin zurückkommt, wo er war.
+
+Vor dem Öffnen von Stripe wird der Formularzustand nach `localStorage` geschrieben (`qrm_pending_qr`, mit Zeitstempel). Beim Zurückkommen auf `/create?restored=1` liest die Seite ihn aus, füllt das Formular und zeigt: *„Willkommen zurück. Dein Code steht noch genau so da - jetzt mit Platz."* Danach den Key löschen. Einträge älter als 24 Stunden werden verworfen.
+
+Kein Draft in der Datenbank. Der Zustand ist ohnehin nur im Browser relevant, und eine Draft-Tabelle wäre Infrastruktur für ein Problem, das `localStorage` löst.
+
+### 1.4 Option „Code pausieren" im Modal
+
+Zeigt die aktiven dynamischen Codes des Nutzers mit Titel und Scan-Zahl der letzten 30 Tage. Ein Klick setzt `status = PAUSED` über das bestehende `PATCH /api/qrs/[id]`, danach wird der ursprüngliche POST automatisch wiederholt.
+
+Die Scan-Zahl daneben ist wichtig: sie macht die Entscheidung informiert statt willkürlich. Wer sieht, dass ein Code seit vier Wochen null Scans hat, pausiert ihn ohne schlechtes Gefühl - und wer sieht, dass alle drei laufen, versteht ohne Verkaufstext, warum Pro sinnvoll ist. Das ist Pointing statt Talking.
+
+---
+
+## Phase 2 - `/bulk-creation`
+
+**Datei:** `src/app/(main)/(app)/bulk-creation/page.tsx`, Zeile 232-247
+
+Die Schleife hat kein `else` zu `if (res.ok)`. Fehlgeschlagene Zeilen verschwinden still, und danach meldet ein grüner Toast `${results.length} dynamische QR-Codes erstellt!` - eine Zahl, die kleiner sein kann als das Hochgeladene, ohne jeden Hinweis.
+
+**Fix:**
+
+```ts
+const failed: { row: number; title: string; reason: string }[] = [];
+// im else-Zweig: failed.push({ row: i + 1, title, reason: (await res.json()).error })
+```
+
+Danach, wenn `failed.length > 0`, statt des Erfolgs-Toasts ein Ergebnis-Panel:
+
+> **180 von 200 Codes erstellt.**
+> 20 Zeilen konnten nicht angelegt werden, weil dein Kontingent an dynamischen Codes erschöpft ist. Hier sind sie - du kannst sie als statische Codes erzeugen oder dein Kontingent erhöhen.
+>
+> [ Fehlende Zeilen als CSV ] [ Kontingent erhöhen ]
+
+Zusätzlich in derselben Datei:
+
+- `remainingDynamic` nach dem Lauf vom Server neu laden statt nur lokal herunterzuzählen (Zeile 251). Das ist die Ursache der Race Condition.
+- Die Toasts sind auf Deutsch (`'Du hast keine dynamischen QR-Codes mehr übrig...'`, Zeile 215 und 221), während die restliche App-Oberfläche Englisch ist. Auf Englisch umstellen.
+
+---
+
+## Phase 3 - Upgrade-Wege aus der App
+
+`/pricing` liegt in der `(marketing)`-Route-Group. Wer im Dashboard auf Upgrade klickt, verliert die Sidebar und landet in der Marketing-Site. Zusätzlich liest `PricingClient.tsx` `searchParams` überhaupt nicht - das `?reason=limit_reached`, das `/create` heute anhängt, wird vollständig ignoriert.
+
+Nach der getroffenen Entscheidung läuft der Limit-Fall künftig über das Modal, damit ist der wichtigste Fall gelöst. Es bleiben drei In-App-Links auf `/pricing`:
+
+| Datei | Zeile | Kontext |
+|---|---|---|
+| `dashboard/page.tsx` | 340 | Upgrade-Badge im Header |
+| `create/page.tsx` | 977 | Hinweis „Upgrade to PRO to customize colors" |
+| `create/page.tsx` | 1106 | Hinweis „Upgrade to PRO to add logos" |
+
+Die beiden Hinweise in `/create` sollten dasselbe Modal öffnen wie der Limit-Fall, nur mit anderem Aufhänger („Dein Logo gehört in diesen Code"). Der Nutzer ist mitten im Gestalten - ihn dafür aus der Seite zu werfen ist derselbe Fehler wie beim Limit, nur weniger sichtbar.
+
+Der Dashboard-Badge kann auf `/pricing` zeigen bleiben. Dort ist der Nutzer nicht mitten in einer Aufgabe, es gibt nichts zu verlieren. Nur ein `?from=dashboard` anhängen und in `PricingClient` einen Zurück-Link rendern, damit der Weg zurück nicht über den Browser-Button läuft.
+
+---
+
+## Phase 4 - Post-Download-Popup auf den Tool-Seiten
+
+**Datei:** `src/components/marketing/PostDownloadPopup.tsx`
+
+Timing und die Ablehn-Option `No thanks, keep it static` bleiben unverändert - beides ist richtig gebaut.
+
+Ersetzt werden Headline und Bullet-Liste:
+
+> ### Dieser Code zeigt jetzt für immer auf diese URL.
+>
+> Bei einem dauerhaften Link ist das genau richtig. Falls sich das Ziel je ändert, brauchst du einen neuen Code und neues Druckmaterial.
+>
+> Ein kostenloses Konto gibt dir 3 dynamische Codes: gleiches Bild, Ziel jederzeit änderbar, jeder Scan gezählt.
+>
+> **[ Kostenloses Konto anlegen - keine Karte ]**
+> *Nein danke, statisch reicht*
+
+Die vier Bullets entfallen. Drei gleichrangige Vorteile sind schwächer als ein Satz, der den einen benennt - in einer Liste aus vier gleich formatierten Punkten ist keiner davon wichtig.
+
+Neues Prop `variant`, das nur die erste Zeile austauscht:
+
+| Tool | Erste Zeile |
+|---|---|
+| Google Review | Dieser Code zeigt jetzt für immer auf dieses Google-Profil. |
+| WiFi | Dieser Code enthält jetzt dauerhaft dieses WLAN-Passwort. |
+| vCard | Dieser Code enthält jetzt dauerhaft diese Kontaktdaten. |
+| Crypto | Dieser Code enthält jetzt dauerhaft diese Wallet-Adresse. |
+| Standard | Dieser Code zeigt jetzt für immer auf diese URL. |
+
+Die WiFi-Variante ist die stärkste, weil sie einen Umstand benennt, den fast niemand vorher bedenkt: Wer das Passwort ändert, hat wertloses Druckmaterial.
+
+Betroffen sind die 10+ Generator-Komponenten, die `PostDownloadPopup` einbinden - dort jeweils nur das `variant`-Prop ergänzen.
+
+**Zusätzlich:** `shouldShowDownloadPopup()` prüft einen einzigen `localStorage`-Key. Wer das Popup einmal gesehen hat, sieht es auf keiner anderen Tool-Seite je wieder - auch nicht Monate später in einem anderen Kontext. Vorschlag: Key mit Zeitstempel, Wiedervorlage nach 30 Tagen.
+
+---
+
+## Phase 5 - Retention-Mails
+
+**Datei:** `src/lib/email.ts` und `src/app/(main)/api/cron/retention-emails/route.ts`
+
+### 5.1 Tag-7-Mail: Trigger vom Kalender aufs Verhalten
+
+Aktuell feuert sie bei `createdAt < 7 Tage` und `qrCount > 0`. Ein Nutzer mit einem einzigen Code bekommt „You're 2 away from the free limit" - eine Verkaufsmail über ein Limit, das ihn nicht drückt, abgeschickt unter deinem Namen.
+
+Neue Logik:
+
+| Zustand | Auslöser | Mail |
+|---|---|---|
+| aktive dynamische Codes = Limit | sobald erreicht | Limit-Mail, Marker `limitReachedNudgeSentAt` |
+| 1-2 von 3 belegt, Tag 7 | Tag 7 | keine Upgrade-Mail |
+| erster Scan liegt vor | 1 Tag danach | Erster-Scan-Mail, Marker `firstScanNudgeSentAt` |
+
+In der Vergleichstabelle der Limit-Mail entfällt die Zeile `CSV export: Free ✓ / Pro ✓`. Eine Zeile, in der beide Spalten identisch sind, gehört nicht in eine Upgrade-Tabelle - sie verwässert die drei, die einen Unterschied zeigen.
+
+### 5.2 Neue Mail: erster Scan
+
+`User.firstScanAt` existiert bereits und wird in `src/app/(main)/r/[slug]/route.ts` (Zeile 150-153 und 263-266) gesetzt. Es fehlt nur ein Versand-Marker.
+
+Betreff: **Dein Code wurde gerade zum ersten Mal gescannt**
+
+> Um {Uhrzeit}, auf einem {Gerät}, aus {Land}. Dein Code „{Titel}" ist im Einsatz.
+>
+> Ab jetzt zählt jeder weitere Scan mit. In ein paar Tagen siehst du, wann die meisten kommen - und ob sich der Ort lohnt, an dem du den Code platziert hast.
+>
+> [ Scans ansehen ]
+
+Kein Verkaufsargument. Diese Mail hat einen Anlass, der nicht konstruiert ist, und ist der Moment, in dem die Positionierung zum ersten Mal einlöst. Sie ist die einzige in der Sequenz, deren Anlass nicht vom Kalender kommt.
+
+### 5.3 Tag-30-Mail auf Scan-Daten umbauen
+
+Zwei Probleme mit der jetzigen Fassung:
+
+Der Satz *„The one thing I hear most from Pro users who switched after a few weeks: they wish they'd added their brand sooner"* behauptet ein Muster aus Kundengesprächen. Wenn es die gab: echtes Zitat rein. Wenn nicht: **streichen** - das ist ein erfundenes Testimonial in indirekter Rede und verstößt gegen die Beweisregel im Product Context. Bei einer Zielgruppe, die Bewertungsportale liest, ist das die teuerste Art von Satz.
+
+Und Branding ist der schwächere von zwei verfügbaren Aufhängern. Nach 30 Tagen hat der Nutzer Scan-Daten. Neuer Aufbau:
+
+> Deine Codes wurden diesen Monat {n}-mal gescannt, {Vorname}.
+>
+> Die meisten davon {Wochentag}s. Was du noch nicht sehen kannst: von welchen Geräten sie kamen und aus welchen Orten - und damit, welche deiner Platzierungen die Scans wirklich gebracht hat.
+>
+> [ Vollständige Auswertung freischalten ]
+
+Damit ist der Kaufgrund aus dem hergeleitet, was der Nutzer selbst erlebt hat. Deckt sich mit Offer Brief §10, Option 1 - auf Analytics-Tiefe metern statt auf Code-Anzahl -, und ist die erste Stelle, an der man das testen kann, ohne das Pricing anzufassen.
+
+### 5.4 Tag-3-Mail: kleine Korrektur
+
+Betreff `You haven't made one yet` kann als Vorwurf gelesen werden - im Body löst die Headline es auf, im Posteingang steht der Betreff allein. Alternative gleicher Länge: **`Your 3 free codes are still sitting there`**.
+
+---
+
+## Phase 6 - QR-Design nach Plan gestaffelt
+
+Neue Staffelung: Farben ab Free, Formen ab Pro, alles ab Business.
+
+### 6.0 Der technische Blocker, der vorher geklärt sein muss
+
+`/create` rendert über `QRCodeSVG` aus `qrcode.react` (Zeile 6 und 1233). **Diese Bibliothek kann ausschließlich quadratische Module.** Es gibt heute überhaupt keine Formauswahl im Produkt - nicht weil sie gesperrt wäre, sondern weil der Renderer sie nicht kann.
+
+Für Formen muss der Renderer also getauscht werden. Es gibt zwei Kandidaten, und sie decken unterschiedlich viel ab:
+
+| Renderer | Kann | Kann nicht |
+|---|---|---|
+| `qr-code-styling` (liegt bereits als Dependency in `package.json`, Zeile 64, ungenutzt) | square, dots, rounded, extra-rounded, classy, classy-rounded, Eye-Styles getrennt, Verläufe, Logo | star, plus, hexagon, diamond, mosaic, liquid |
+| Canvas-Renderer aus `qr-design-showcase.html` | alle 11 Formen inklusive star, plus, hexagon, diamond, mosaic, liquid, Rahmen mit Label, Logo-Formen | ist eigener Code, muss portiert und gepflegt werden |
+
+Das trifft sich gut mit der gewünschten Staffelung: **Pro läuft komplett über `qr-code-styling`** - vier Formen, geringer Aufwand, Bibliothek ist schon da. **Business braucht den portierten Showcase-Renderer** für die exotischen Formen. Damit ist der Mehraufwand genau dort, wo auch der höhere Preis ist.
+
+### 6.1 Free - Farben
+
+**Datei:** `src/app/(main)/(app)/create/page.tsx`, Zeile 167
+
+```ts
+const canCustomizeColors = true; // war: PRO || BUSINESS
+const canUseShapes = userPlan === 'PRO' || userPlan === 'BUSINESS';
+const canUseFullDesign = userPlan === 'BUSINESS';
+const canUseLogo = userPlan === 'PRO' || userPlan === 'BUSINESS';
+```
+
+Damit fallen die beiden Zwangsüberschreibungen in Zeile 408-409 weg, die heute für Free-Nutzer stumpf `#000000` und `#FFFFFF` einsetzen. Der Upgrade-Hinweis-Block ab Zeile 972 („Upgrade to PRO to customize colors, add logos, and brand your QR codes") entfällt komplett - er stimmt danach nicht mehr.
+
+Der Logo-Block ab Zeile 1104 bleibt, wird aber auf `canUseLogo` umgestellt statt auf `canCustomizeColors`, und öffnet künftig das Modal aus Phase 3 statt auf `/pricing` zu verlinken.
+
+**Der Kontrast-Check bleibt aktiv.** `calculateContrast` gibt es bereits (Zeile 221). Sobald Farben für alle offen sind, wird er wichtiger, nicht unwichtiger - Free-Nutzer sind die Gruppe mit der geringsten Erfahrung darin, was noch scannbar ist. Bei zu geringem Kontrast eine Warnung, kein Verbot: *„Dieser Kontrast ist grenzwertig. Auf gedrucktem Material scannen helle Codes auf hellem Grund oft nicht."*
+
+### 6.2 Pro - vier Modulformen
+
+Umsetzung über `qr-code-styling`. Bewusst nur vier, nicht sechs:
+
+| Auswahl | `dotsOptions.type` | Wofür |
+|---|---|---|
+| Eckig (klassisch) | `square` | Standard, höchste Scan-Sicherheit |
+| Abgerundet | `rounded` | weicher, ohne Lesbarkeit zu kosten |
+| Punkte | `dots` | modern, deutlich sichtbarer Unterschied |
+| Fließend | `classy-rounded` | markantester der vier |
+
+Dazu die Eye-Styles (`cornersSquareOptions`, `cornersDotOptions`) - eckig, abgerundet, Kreis. Die Augen machen optisch mehr aus als die Module selbst und kosten nichts extra, weil dieselbe Bibliothek sie mitbringt.
+
+Vier Optionen statt aller sechs, weil die Auswahl selbst ein Verkaufsargument ist: Pro fühlt sich vollständig an, Business hat sichtbar mehr. Sechs fast identische Varianten wirken dagegen wie eine lange Liste ohne Unterschied.
+
+### 6.3 Business - vollständiger Designer
+
+Portierung des Renderers aus `qr-design-showcase.html` in eine Komponente `src/components/generator/AdvancedQRRenderer.tsx`. Was damit dazukommt:
+
+- **Modulformen:** diamond, star, hexagon, plus, mosaic, liquid zusätzlich zu den vier aus Pro
+- **Eye-Frames getrennt:** eckig, abgerundet, Kreis, Blatt, Flower/Squircle, Hexagon
+- **Eye-Balls getrennt:** eckig, abgerundet, Kreis, Diamant, Stern, Hexagon
+- **Farbverläufe:** linear und radial statt einfarbig
+- **Rahmen mit Label:** weiße Karte mit Schatten, Label oben oder unten (z. B. „Speisekarte", „Jetzt bewerten")
+- **Logo-Formen:** Punkt, Quadrat, Herz, Blitz, WLAN-Symbol
+- **Fehlerkorrektur-Stufe** frei wählbar (L/M/Q/H)
+
+**Erweiterungsvorschläge über die Showcase hinaus**, weil beim Business-Kunden die Wiederholbarkeit zählt und nicht die einzelne Spielerei:
+
+1. **Design-Vorlagen speichern.** Ein einmal gebautes Design als benanntes Preset sichern und auf neue Codes anwenden. Das ist für eine Agentur mit mehreren Kunden der eigentliche Wert - nicht die Sternform, sondern dass Kunde A immer gleich aussieht.
+2. **Preset auf den Bulk-Flow anwenden.** Ein Preset auswählen und alle 500 Codes eines Uploads tragen es. Das verbindet Phase 2 mit diesem Feature und ist das erste Argument für Business, das nicht nur eine Zahl ist.
+3. **Print-Vorschau in Originalgröße.** Der Code in 2×2 cm, 4×4 cm und 10×10 cm dargestellt, mit Hinweis ab wann es kritisch wird. Passt zu den Druckangaben, die im Offer Brief bereits als verifizierter Fakt geführt werden.
+
+### 6.4 Scanbarkeit - der Teil, der nicht optional ist
+
+Exotische Formen füllen weniger Fläche pro Modul. Star, plus und mosaic reduzieren die Kontrastfläche spürbar, und in Kombination mit einem Logo in der Mitte und einer kleinen Druckgröße kippt die Lesbarkeit. Die Showcase weist bei „H (30%)" schon auf „empfohlen bei Logo" hin - das gehört ins Produkt übernommen, und zwar strenger:
+
+- Bei star, plus, mosaic oder liquid automatisch auf Fehlerkorrektur **H** hochsetzen und das sichtbar begründen
+- Bei Logo plus exotischer Form eine Warnung mit Mindestdruckgröße
+- Ein **Test-Scan-Hinweis** vor dem Download: *„Scanne den Code einmal mit deinem eigenen Handy, bevor du ihn in Druck gibst."*
+
+Das ist kein Beiwerk, sondern der Proof-Layer aus der Positionierung an einer Stelle, an der es konkret wird. Ein Anbieter, der vor seinem eigenen Feature warnt, wenn es kritisch wird, belegt Belief 5 besser als jede Aussage über Transparenz.
+
+### 6.5 Was die Umstellung beim Pricing kostet
+
+Ehrlich gesagt: Farben waren laut Offer Brief §10 einer von genau **drei** Gründen, heute auf Pro zu wechseln (die anderen zwei: mehr als 3 aktive Codes, Device- und Location-Breakdown). Diesen Grund gibt man mit dieser Änderung auf.
+
+Das ist meiner Einschätzung nach trotzdem richtig, aber aus einem anderen Grund als „großzügiger sein":
+
+Farben sind kein guter Upgrade-Anlass, weil man sie schon im kostenlosen Zustand *sehen* will, um zu verstehen, ob das Produkt etwas taugt. Ein Free-Nutzer, der nur schwarze Codes bauen kann, hält das Produkt für ein Basiswerkzeug - und bewertet es entsprechend, auch in Vergleichen. Formen sind der bessere Verkäufer, weil der Unterschied größer aussieht und man ihn in der Vorschau zeigen kann, bevor man ihn freischaltet: der Nutzer wählt „Punkte", sieht das Ergebnis live, und erst der Download fragt nach Pro.
+
+**Folgeänderungen an der Preis-Copy** in `src/i18n/en.json` und `src/app/(main)/(marketing)/pricing/PricingClient.tsx`:
+
+| Plan | vorher | nachher |
+|---|---|---|
+| Free | „Standard QR design templates" | „Deine Farben - Vorder- und Hintergrund frei wählbar" |
+| Pro | „Custom branding (colors & logos)" | „4 Modulformen, eigene Eye-Styles und dein Logo im Code" |
+| Business | (kein Design-Punkt) | „Voller Designer: 10 Formen, Verläufe, Rahmen mit Label, speicherbare Design-Vorlagen" |
+
+Dieselbe Anpassung auf `/custom-qr-code-generator`, dessen Meta-Description aktuell „Put your logo and brand colors into the code itself" sagt - das bleibt korrekt, weil Logo weiterhin Pro ist, aber die Seite sollte künftig zeigen, was auf welcher Stufe geht.
+
+### 6.6 Keine Datenbankänderung nötig
+
+`QRCode.style` ist bereits `Json` (siehe `prisma/schema.prisma`). Alle neuen Felder - `dotType`, `eyeFrameType`, `eyeBallType`, `gradient`, `frameLabel`, `logoShape`, `ecLevel` - passen ohne Schemaänderung hinein. Bestehende Codes haben die Felder schlicht nicht und fallen auf die Defaults zurück.
+
+**Ausnahme:** Wenn die speicherbaren Design-Vorlagen aus 6.3 kommen sollen, braucht es eine eigene Tabelle. SQL dafür steht unten, ist aber optional und kann später nachgezogen werden.
+
+---
+
+## SQL - alle nötigen Datenbankänderungen
+
+Nach der Policy in `CLAUDE.md`: keine Prisma-Migrationen, nur direkte Statements.
+
+```sql
+-- Marker für die neuen verhaltensbasierten Retention-Mails
+ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "limitReachedNudgeSentAt" TIMESTAMP(3);
+ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "firstScanNudgeSentAt" TIMESTAMP(3);
+
+-- Index für den Cron: sucht Nutzer mit erstem Scan, die die Mail noch nicht haben
+CREATE INDEX IF NOT EXISTS "User_firstScanAt_firstScanNudgeSentAt_idx"
+ ON "User" ("firstScanAt", "firstScanNudgeSentAt");
+
+-- Index für die neue Limit-Query (zählt nur noch ACTIVE)
+CREATE INDEX IF NOT EXISTS "QRCode_userId_type_status_idx"
+ ON "QRCode" ("userId", "type", "status");
+```
+
+Für Phase 6 ist **nichts** davon nötig - `QRCode.style` ist bereits `Json`. Optional, nur falls die speicherbaren Design-Vorlagen aus 6.3 gebaut werden:
+
+```sql
+CREATE TABLE IF NOT EXISTS "QRDesignPreset" (
+ "id" TEXT PRIMARY KEY,
+ "userId" TEXT NOT NULL REFERENCES "User"("id") ON DELETE CASCADE,
+ "name" TEXT NOT NULL,
+ "style" JSONB NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT now(),
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT now()
+);
+
+CREATE INDEX IF NOT EXISTS "QRDesignPreset_userId_idx" ON "QRDesignPreset" ("userId");
+CREATE UNIQUE INDEX IF NOT EXISTS "QRDesignPreset_userId_name_key" ON "QRDesignPreset" ("userId", "name");
+```
+
+Ausführen über `npm run docker:db` oder:
+
+```bash
+docker-compose exec db psql -U postgres -d qrmaster -c 'ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "limitReachedNudgeSentAt" TIMESTAMP(3);'
+```
+
+Danach `prisma/schema.prisma` im Block `// Retention email tracking` ergänzen:
+
+```prisma
+ limitReachedNudgeSentAt DateTime?
+ firstScanNudgeSentAt DateTime?
+```
+
+und beim Model `QRCode` den Index:
+
+```prisma
+ @@index([userId, type, status])
+```
+
+Abschließend `npx prisma generate` - kein `migrate`.
+
+### Prüf-Queries vor dem Deploy
+
+```sql
+-- Wie viele Free-Nutzer bekommen durch die ACTIVE-Umstellung Slots frei?
+SELECT COUNT(DISTINCT u.id)
+FROM "User" u
+JOIN "QRCode" q ON q."userId" = u.id
+WHERE u.plan = 'FREE' AND q.type = 'DYNAMIC' AND q.status = 'PAUSED';
+
+-- Wie viele Nutzer bekämen die Erster-Scan-Mail beim ersten Cron-Lauf?
+SELECT COUNT(*) FROM "User"
+WHERE "firstScanAt" IS NOT NULL AND "firstScanNudgeSentAt" IS NULL;
+```
+
+Die zweite Zahl ist wichtig: Beim ersten Lauf würde die neue Mail an **alle** Bestandsnutzer mit Scan-Historie gehen - auch an solche, deren erster Scan Monate zurückliegt. Das wäre kein Anlass mehr, sondern Spam. Der Cron braucht deshalb ein Zeitfenster, etwa `firstScanAt > now() - interval '7 days'`, oder man setzt die Spalte bei Bestandsnutzern einmalig vor:
+
+```sql
+UPDATE "User" SET "firstScanNudgeSentAt" = now()
+WHERE "firstScanAt" IS NOT NULL AND "firstScanAt" < now() - interval '7 days';
+```
+
+---
+
+## Reihenfolge
+
+| # | Was | Aufwand | DB |
+|---|---|---|---|
+| 1 | Limit-Query auf ACTIVE, beide Endpoints | klein | Index |
+| 2 | Limit-Modal ohne Redirect | mittel | nein |
+| 3 | Direkt-Checkout plus `returnPath` und localStorage-Wiederherstellung | mittel | nein |
+| 4 | `bulk-creation`: Fehlerbehandlung und Ergebnis-Panel | klein | nein |
+| 5 | Popup-Copy plus `variant` pro Tool-Seite | klein | nein |
+| 6 | Retention: Trigger umstellen, Tabellen-Zeile raus, Betreff Tag 3 | klein | 1 Spalte |
+| 7 | Erster-Scan-Mail | mittel | 1 Spalte |
+| 8 | Tag-30 auf Scan-Daten, unbelegten Satz klären | klein | nein |
+| 9 | Farben ab Free freischalten, Pricing-Copy nachziehen | klein | nein |
+| 10 | Pro-Formen über `qr-code-styling`, Renderer-Tausch in `/create` | mittel | nein |
+| 11 | Business-Designer, Showcase-Renderer portieren | groß | nein |
+| 12 | Design-Vorlagen speichern und auf Bulk anwenden | mittel | Tabelle |
+
+1 bis 3 gehören zusammen und sollten gemeinsam live gehen. 4 und 5 sind unabhängig und können jederzeit dazwischen. 6 bis 8 brauchen die SQL-Statements und den einmaligen `UPDATE` für Bestandsnutzer.
+
+**9 ist der schnellste Gewinn im ganzen Dokument** - eine Zeile (`canCustomizeColors = true`) plus Copy-Anpassung, und das Produkt wirkt für jeden neuen Nutzer sofort weniger nach Basiswerkzeug. Sie sollte nicht auf 10 warten.
+
+10 ist der Renderer-Tausch und damit der Punkt, an dem `/create` einmal gründlich getestet werden muss - Vorschau, Download PNG, Download SVG, Logo-Overlay und die Frame-Optionen hängen alle daran.
+
+11 und 12 sind der eigentliche Business-Ausbau. 12 ist dabei wertvoller als 11: eine Agentur kauft nicht die Sternform, sondern dass Kunde A auf 500 Codes gleich aussieht.
diff --git a/RETENTION_UND_CRO_ANALYSE_2026-07-27.md b/RETENTION_UND_CRO_ANALYSE_2026-07-27.md
new file mode 100644
index 0000000..6952fc8
--- /dev/null
+++ b/RETENTION_UND_CRO_ANALYSE_2026-07-27.md
@@ -0,0 +1,169 @@
+# Retention-Mails & CRO-Momente - Analyse
+
+Stand 27. Juli 2026. Grundlage: `src/lib/email.ts`, `src/app/(main)/api/cron/retention-emails/route.ts`, `src/components/marketing/PostDownloadPopup.tsx`, `src/app/(main)/(app)/create/page.tsx` sowie Offer Brief §10 und der Necessary-Beliefs-Doc.
+
+---
+
+## Teil 1 - Der 3/3-Moment: es gibt kein Popup
+
+Das Wichtigste zuerst, weil es kein Copy-Problem ist.
+
+Wenn ein Free-Nutzer den vierten dynamischen Code anlegen will, passiert laut `create/page.tsx` Zeile 456-458 Folgendes:
+
+```
+if (response.status === 403 && responseData.error === 'Limit reached') {
+ showToast(responseData.message || 'You have reached your plan limit.', 'error');
+ router.push('/pricing?reason=limit_reached');
+}
+```
+
+Drei Dinge gehen hier schief, in aufsteigender Schwere:
+
+**1. Roter Fehler-Toast.** Der Nutzer hat nichts falsch gemacht. Er hat das Produkt so benutzt, wie es gedacht ist, und bekommt dafür eine Fehlermeldung im gleichen Stil wie eine kaputte URL. Das ist keine Upgrade-Aufforderung, das ist eine Zurechtweisung.
+
+**2. Die Arbeit ist weg.** Der Nutzer hat Ziel-URL, Typ, Farben, vielleicht ein Logo eingegeben. Beim Redirect auf `/pricing` ist all das verloren. Wer danach upgradet, muss von vorne anfangen. Das ist der teuerste denkbare Zeitpunkt für Datenverlust: exakt in dem Moment, in dem die Kaufabsicht am höchsten ist.
+
+**3. Der Redirect ist ein Rauswurf.** Der Nutzer wollte einen QR-Code bauen und landet auf einer Preistabelle ohne Bezug zu dem, was er gerade tat. Kein Weg zurück, kein „so sieht dein Code aus, er ist nur noch nicht gespeichert".
+
+Aus dem Offer Brief §7: der Engpass ist **Perceived Likelihood**, nicht Preis und nicht Effort. Dieser Ablauf senkt genau die - er zeigt, dass das Produkt in einem kritischen Moment die eigene Arbeit wegwirft.
+
+### Was stattdessen hin muss
+
+Ein Modal, das **über** dem fertigen Code aufgeht, nicht statt ihm. Der Code bleibt im Hintergrund sichtbar. Vorschlag:
+
+> **Dein vierter Code ist fertig. Er braucht nur noch einen Platz.**
+>
+> Du nutzt alle 3 dynamischen Codes des Free-Plans. Dieser hier ist gebaut und wartet - du kannst ihn behalten oder einen bestehenden freigeben.
+>
+> [ Diesen Code mit Pro speichern - 9 € / Monat ]
+> [ Einen bestehenden Code ersetzen ]
+> [ Stattdessen als statischen Code herunterladen - kostenlos, läuft nie ab ]
+>
+> Deine 3 aktiven Codes laufen weiter, egal wie du dich entscheidest.
+
+Warum diese drei Optionen:
+
+- **Option 1** ist der Kauf, aber formuliert als „diesen konkreten Code retten", nicht als „Plan kaufen". Der Nutzer kauft eine Sache, die er gerade in der Hand hat.
+- **Option 2** ist die ehrliche Alternative innerhalb des Free-Plans. Sie kostet ein paar Conversions und kauft dafür Glaubwürdigkeit - Belief 5 aus dem Necessary-Beliefs-Doc: der sicherste Anbieter ist der, der offenlegt, was er kann und was nicht.
+- **Option 3** ist der Ausweg ohne Verlust. Ein statischer Code löst das Problem des Nutzers zu 60 % und kostet nichts, und die Zusage „läuft nie ab" ist verifiziert. Wer sie nimmt, ist nicht verloren - er hat gerade erlebt, dass das Produkt ihm nichts wegnimmt.
+
+Der letzte Satz ist Risk Reversal an genau der Stelle, wo die Kategorie ihren schlechtesten Ruf hat: das Abschalten bereits gedruckter Codes.
+
+---
+
+## Teil 2 - Die Retention-Mails
+
+Die Copy ist besser als der Durchschnitt. Absenderpersona ist da (Timo, Founder, Reply geht an einen Menschen), keine Ausrufezeichen-Orgie, konkrete Zahlen. Was fehlt, liegt eine Ebene tiefer.
+
+### Mail 1 - Tag 3, Aktivierung
+
+Betreff: `You haven't made one yet`
+Headline: `You haven't made one yet, {Name}. 3 days since signup · 0 QR codes created`
+
+Das ist die stärkste der drei. Der Betreff ist ein Pattern Interrupt in einem Posteingang voller „Tips & Tricks", und die Zeile `Your 3 free dynamic QR codes are still there. Unused.` ist Pointing statt Talking.
+
+Zwei Schwächen:
+
+**Der Betreff kann als Vorwurf gelesen werden.** „You haven't made one yet" mit Doppeldeutigkeit - ist das eine Beobachtung oder eine Rüge? Die Headline im Body löst es auf, der Betreff allein nicht. Alternative, gleiche Länge, ohne Vorwurfsrisiko: `Your 3 free codes are still sitting there`.
+
+**Es fehlt der Anlass.** Die Mail erklärt, *wie* man einen Code macht (drei Schritte), aber nicht, *wofür der Empfänger sich damals angemeldet hat*. Wenn beim Signup die Quelle bekannt ist - kam er über `/tools/google-review-qr-code`, über die Restaurant-Seite, über Bulk? -, dann gehört Schritt 01 personalisiert. „Paste your URL" ist die generischste denkbare Aufforderung an jemanden, der wegen Google-Reviews kam.
+
+### Mail 2 - Tag 7, Upgrade
+
+Betreff: `You're at the free limit` bzw. `You're 2 away from the free limit`
+Headline: `{n} of 3 free codes used, {Name}.`
+
+Hier ist der Trigger das Problem, nicht die Worte.
+
+**Die Mail feuert am Tag 7, unabhängig vom Verhalten.** Die Bedingung im Cron ist `createdAt < 7 Tage` und `qrCount > 0` und `plan = FREE`. Ein Nutzer mit **einem** Code bekommt eine Mail mit dem Betreff „You're 2 away from the free limit" - über ein Limit, das ihn nicht drückt und vielleicht nie drücken wird. Das ist eine Upgrade-Aufforderung an jemanden ohne Schmerz. Solche Mails trainieren Empfänger darauf, den Absender zu ignorieren, und der Absender ist hier der Gründer persönlich - das ist teurer Kredit, den man nicht für eine Fehlzündung ausgeben sollte.
+
+**Empfehlung: den Trigger vom Kalender aufs Verhalten umstellen.**
+
+| Zustand | Auslöser | Inhalt |
+|---|---|---|
+| 3 von 3 belegt | sobald erreicht, nicht Tag 7 | Limit-Mail wie jetzt, aber sofort im Moment der Relevanz |
+| 1-2 von 3 belegt | Tag 7 | keine Upgrade-Mail. Stattdessen: was die Scan-Daten des ersten Codes zeigen |
+| 3 von 3 und erster Code hat Scans | 3 Tage nach Limit | Upgrade mit den eigenen Zahlen des Nutzers als Beweis |
+
+Das dritte Szenario ist die eigentlich fehlende Mail. Nichts überzeugt einen Marketing-Manager so wie sein eigener erster Datenpunkt: „Dein Code auf dem Flyer hatte 47 Scans, die meisten dienstags zwischen 11 und 14 Uhr." Das ist Pointing in Reinform, und es kostet nichts außer der Query.
+
+**Und ein inhaltlicher Fehler.** Die Vergleichstabelle listet `CSV export: Free ✓ / Pro ✓`. Eine Zeile, in der beide Spalten identisch sind, ist in einer Upgrade-Tabelle wertlos - sie füllt Platz und verwässert die drei Zeilen, die tatsächlich einen Unterschied zeigen. Streichen.
+
+### Mail 3 - Tag 30
+
+Betreff: `{Name}, a month of QR codes - one upgrade worth making`
+Argument: Pro-Nutzer bereuen, das Branding nicht früher hinzugefügt zu haben.
+
+**Das Argument ist gut gebaut und nicht belegt.** Der Satz `The one thing I hear most from Pro users who switched after a few weeks: they wish they'd added their brand sooner` behauptet ein Muster aus Kundengesprächen. Wenn diese Gespräche stattgefunden haben: rein damit, am besten mit einem echten Zitat. Wenn nicht, ist das ein erfundenes Testimonial in indirekter Rede und verstößt gegen die Beweisregel aus dem Product Context („keine erfundenen Testimonials"). Bei einer Zielgruppe, die Bewertungsportale liest, ist das die teuerste Art von Satz.
+
+**Der Aufhänger ist außerdem der schwächere von zwei verfügbaren.** Branding ist ein Ästhetik-Argument. Nach 30 Tagen mit mehreren Codes hat der Nutzer etwas viel Stärkeres in der Hand: Scan-Daten. Der Tag-30-Anlass sollte der erste echte Report sein, nicht ein Logo-Feature. „Deine Codes wurden diesen Monat X-mal gescannt. Hier ist, was du noch nicht sehen kannst" - und dann Device- und Location-Breakdown als das, was Pro freischaltet. Das ist derselbe Kaufgrund, aber hergeleitet aus dem, was der Nutzer selbst erlebt hat.
+
+Das deckt sich mit Offer Brief §10, Option 1: auf Analytics-Tiefe metern statt auf Code-Anzahl. Diese Mail wäre die erste Stelle, an der man das testen kann, ohne das Pricing anzufassen.
+
+### Übergreifend: es fehlt die Mail nach dem ersten Scan
+
+Die Sequenz ist Tag 3, Tag 7, Tag 30 - drei Kalendertermine. Der wichtigste Moment im Lebenszyklus dieses Produkts kommt in keinem davon vor: **der erste Scan eines Codes.** Das ist der Augenblick, in dem aus einem Tool eine Messung wird und in dem das Versprechen der Positionierung zum ersten Mal einlöst. Eine Mail „Dein Code wurde gerade zum ersten Mal gescannt" hat einen Anlass, der nicht konstruiert ist, und braucht überhaupt kein Verkaufsargument.
+
+---
+
+## Teil 3 - Das Post-Download-Popup auf den Tool-Seiten
+
+Aktuell:
+
+> **Your QR code is downloading!**
+> Want to make it smarter - for free?
+>
+> - Edit the link anytime - QR stays the same
+> - See who scans, when & where
+> - Custom colors, logo & frames
+> - Free plan included - upgrade anytime for more
+>
+> [ Create Free Account ] / *No thanks, keep it static*
+
+### Was funktioniert
+
+Das Timing ist richtig: nach dem Download, nicht davor. Der Nutzer hat bekommen, wofür er kam - das Popup nimmt ihm nichts weg. Und `No thanks, keep it static` ist eine ehrliche Ablehn-Option ohne Beschämung, was in dieser Kategorie selten ist. Nicht anfassen.
+
+### Was nicht funktioniert
+
+**Die Headline sagt nichts.** `Your QR code is downloading!` ist eine Statusmeldung. Der Nutzer sieht den Download in seinem Browser, er braucht keine Bestätigung. Diese Zeile ist die prominenteste Fläche des Popups und verbraucht sie für eine Information, die der Nutzer schon hat.
+
+**Der Kernnutzen ist die dritte Zeile in einer Bullet-Liste.** `Edit the link anytime - QR stays the same` ist das gesamte Argument des Produkts, versteckt zwischen einem Icon und drei gleichgewichtigen Geschwistern. In einer Liste von vier gleich formatierten Punkten ist keiner davon wichtig.
+
+**Es fehlt der Anlass in diesem Moment.** Der Nutzer hat gerade einen **statischen** Code heruntergeladen. Der interessante Satz ist nicht „mach ihn schlauer", sondern die Konsequenz dessen, was er gerade getan hat: dieser Code ist ab jetzt festgelegt. Das ist kein Vorwurf und keine Drohung, das ist eine Tatsache über die Datei in seinem Download-Ordner - und exakt Belief 1 aus dem Necessary-Beliefs-Doc.
+
+**Es ist auf allen Tool-Seiten identisch.** Wer auf `/tools/google-review-qr-code` war, will Bewertungen. Wer auf `/tools/wifi-qr-code` war, will Gäste ins WLAN. Ein Popup, das beiden dasselbe sagt, spricht keinem von beiden.
+
+### Vorschlag
+
+> **Dieser Code zeigt jetzt für immer auf diese URL.**
+>
+> Das ist bei einem dauerhaften Link genau richtig. Falls sich das Ziel je ändert, brauchst du einen neuen Code und neues Druckmaterial.
+>
+> Ein kostenloses Konto gibt dir 3 dynamische Codes: gleiches Bild, Ziel jederzeit änderbar, jeder Scan gezählt.
+>
+> [ Kostenloses Konto anlegen - keine Karte ]
+> *Nein danke, statisch reicht*
+
+Struktur dahinter: P.I.G.-Opening auf eine Tatsache statt auf ein Feature, dann die Zugeständnis-Zeile („bei einem dauerhaften Link genau richtig") - das ist Offer Brief §9, die Konzession als Glaubwürdigkeits-Move -, dann erst der Mechanismus. Die Bullet-Liste entfällt komplett; drei gleichrangige Vorteile sind schwächer als ein Satz, der den einen benennt.
+
+Pro Tool-Seite variiert nur die erste Zeile:
+
+| Tool | Erste Zeile |
+|---|---|
+| Google Review | Dieser Code zeigt jetzt für immer auf dieses Google-Profil. |
+| WiFi | Dieser Code enthält jetzt dauerhaft dieses WLAN-Passwort. |
+| vCard | Dieser Code enthält jetzt dauerhaft diese Kontaktdaten. |
+| URL / Standard | Dieser Code zeigt jetzt für immer auf diese URL. |
+
+Die WiFi-Variante ist die stärkste der vier, weil sie einen Umstand benennt, den fast niemand vorher bedenkt: das Passwort steht in der gedruckten Datei. Wer es ändert, hat wertloses Material.
+
+---
+
+## Reihenfolge nach Wirkung pro Aufwand
+
+1. **Das 3/3-Modal bauen.** Momentan verliert das Produkt an der Stelle mit der höchsten Kaufabsicht die Arbeit des Nutzers. Das ist der einzige Punkt hier, der auch Entwicklungsarbeit ist und nicht nur Text.
+2. **Trigger der Tag-7-Mail auf Verhalten umstellen.** Query-Änderung im Cron, keine neue Infrastruktur.
+3. **Popup-Copy und Headline umstellen**, mit Variante pro Tool-Seite.
+4. **Die „erster Scan"-Mail** ergänzen - der einzige Anlass in der ganzen Sequenz, der nicht vom Kalender kommt.
+5. **Tag 30 auf Scan-Daten umbauen** statt Branding, und den unbelegten Pro-Nutzer-Satz entweder belegen oder streichen.
diff --git a/design-variants/bakeoff.html b/design-variants/bakeoff.html
new file mode 100644
index 0000000..98906f6
--- /dev/null
+++ b/design-variants/bakeoff.html
@@ -0,0 +1,116 @@
+
+
+
+
+
+QR Master — Design Bake-off
+
+
+
+
+
+
+
Design Collection:
+
+ Print-Tech
+ Dither Mono
+ Vast Quiet
+ Data-Texture
+ Classical
+ Quiet Mono
+
+
+
Left = Light · Right = Dark · Keys 1–6
+
+
+
+
+
+
+
diff --git a/design-variants/landing-blueprint.html b/design-variants/landing-blueprint.html
new file mode 100644
index 0000000..dc40c0f
--- /dev/null
+++ b/design-variants/landing-blueprint.html
@@ -0,0 +1,2080 @@
+
+
+
+
+
+QR Master — Blueprint / Print-Tech — Design Variant
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
BLUEPRINT 3.0 Free Dynamic QR Code Generator
+
Free Dynamic QR Code Generator with Tracking
+
+ Create static and dynamic QR codes with editable destinations, scan analytics, custom branding, and bulk creation.
+
+
+ + No credit card required
+ + Free forever static codes
+ + Real-time scan analytics
+ + Custom colors & branding
+
+
+
+ Select type:
+ URL
+ vCard
+ WiFi
+ PDF
+ Event
+ Coupon
+ App Store
+ Call
+
+
+
+
+
+
Fig. 01: Specimen Preview
+
QRM/2026: Rev C
+
+
+
+
+
+
+
+
+
+ https://www.qrmaster.net
+ Live Specimen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
01 Instant Studio
+
Interactive QR Code Generator Studio
+
Type a URL or destination, pick colors, test WCAG contrast, and download SVG or PNG instantly without signup.
+
+
SVG + PNG : High-Res 300 DPI : 100% Free
+
+
+
+
+
+
+ URL or Destination Content
+
+
+
+
+
Color Theme Swatches
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Corner Style
+
+ Square Corners
+ Rounded Corners
+
+
+
+
Render Size
+
+
230 px
+
+
+
+
+ Good contrast
+ WCAG Ratio: 16.2:1
+
+
+
+
+
+ Download SVG
+
+
+
+ Download PNG
+
+
+
+
Save & Track Analytics
+
+
+
+
+
+
+
Real-Time Studio Canvas
+
+
https://www.qrmaster.net
+
Scan with any smartphone camera
+
+
+
+
+
+
+
+
+
+
+
02 AI Innovation
+
The future of QR codes is AI-powered
+
Next-generation intelligence for QR code generation, design optimization, and scan prediction.
+
+
In Active Development
+
+
+
+
+
A1
+
Smart QR Generation
+
+ AI-powered content optimization
+ Intelligent design & color matching
+ Auto vCard creation from LinkedIn
+ SEO-optimized short URLs
+
+
+
+
A2
+
Predictive Analytics
+
+ AI scan rate forecasting
+ Anomaly & fraud detection
+ Natural language analytics queries
+ Automated performance alerts
+
+
+
+
A3
+
Smart Content Hub
+
+ Automated campaign tagging
+ Smart bulk CSV generation
+ AI fallback destination routing
+ Dynamic content personalization
+
+
+
+
A4
+
Creative Studio AI
+
+ AI custom artistic QR designs
+ Auto-generated marketing copy
+ Automated A/B split testing
+ Print-ready layout checks
+
+
+
+
+
+
Get early access when AI features launch
+
+
Join 1,400+ creators on the VIP access list
+
+
+ You are on the list. We will notify you first!
+
+
+
+
+
+
+
+
+
+
+
+
+
04 Workflows
+
Popular QR Master Workflows
+
High-intent solutions for editable QR codes, spreadsheet batches, barcodes, meeting links, and local business campaigns.
+
+
+
+
+
+
+
+
+
+
05 Industry Guides
+
QR Codes by Industry
+
Explore how modern teams use dynamic QR codes to track offline engagement and convert physical traffic.
+
+
+
+
+
+
+
+
+
+
+
+
06 Social Proof
+
Trusted by Businesses Worldwide
+
Real stories from restaurant owners, marketers, and designers using QR Master.
+
+
4.9/5 Rating : 12 Verified Reviews
+
+
+
+
+
+
+
+
+
+
+
+
07 Feature Comparison
+
Why Dynamic QR Codes Save You Money
+
Stop re-printing physical materials every time a URL changes. Update destination links instantly and track every scan.
+
+
Static = Fixed Forever : Dynamic = Editable Forever
+
+
+
+
+
+
Edit Destination URL Anytime
+
Fixed Forever
+
YES (Instant)
+
+
+
Real-Time Scan AnalyticsTime, device type, location
+
Not Available
+
YES
+
+
+
Custom Branding & Logo Overlay
+
Basic Only
+
YES
+
+
+
Bulk Creation from Excel/CSV
+
Manual Only
+
YES
+
+
+
No Account Required to CreateDynamic account signup takes under 60 seconds
+
YES
+
Free Account
+
+
+
+
+
+
+
+
+
+
+
08 Interactive ROI Calculator
+
Are you burning budget on static reprints?
+
Estimate your annual print savings by switching from static printed codes to dynamic QR codes.
+
+
+ Total Print Items (e.g. Menus, Flyers, Cards): 2,500
+
+
+
+
+ Annual URL/Destination Updates: 3x per year
+
+
+
+
Calculate Custom Campaign ROI
+
+
+
+
+
+ Estimated Annual Savings
+ €1,125
+ *Based on average €0.15 print cost per unit, industry estimate
+
+
+
+
+
+
+
+
+
+ 09 Platform Highlights
+
Everything You Need for Professional QR Campaigns
+
+
+
+
F/01
+
Real-Time Scan Analytics
+
Track total scans, device breakdown, geographic locations, and time trends in an intuitive dashboard.
+
+
+
F/02
+
Full Brand Customization
+
Customize colors, corner styles, and add your company logo for seamless visual brand alignment.
+
+
+
F/03
+
Unlimited Static QR Codes
+
Generate as many static QR codes as your project needs. Free forever with zero limits or expiration.
+
+
+
+
+
+
+
+
+
+
+
10 Transparent Pricing
+
Choose the Perfect Plan
+
Simple pricing with no hidden fees. Upgrade or downgrade anytime.
+
+
All Plans Include Unlimited Static Codes
+
+
+
+
+ Monthly Billing
+
+ Yearly Billing
+ Save 16%
+
+
+
+
+
+
Free Plan
+
€0 Forever
+
+ 3 active dynamic QR codes (8 types)
+ Unlimited static QR codes
+ Basic scan tracking
+ Standard styling presets
+ High-res SVG & PNG export
+
+
Get Started Free
+
No credit card required
+
+
+
+
Most Popular
+
Pro Plan
+
€9 per month
+
+ 50 dynamic QR codes
+ Unlimited static QR codes
+ Advanced scan analytics
+ Custom branding & logo overlay
+ CSV scan reports export
+
+
Start 14-Day Free Trial
+
No credit card required
+
+
+
+
Business Plan
+
€29 per month
+
+ 500 dynamic QR codes
+ Unlimited static QR codes
+ Everything in Pro
+ Bulk QR Generator (up to 1,000)
+ Priority support
+
+
Get Started
+
+
+
+
Enterprise
+
Custom
+
+ Unlimited dynamic QR codes
+ Unlimited static QR codes
+ Custom API integration
+ Dedicated Account Manager
+ SLA & custom contract
+
+
Contact Sales
+
+
+
All plans include high-resolution vector export and full privacy compliance.
+
+
+
+
+
+
+
+ 11 FAQ
+
Frequently Asked Questions
+
+
+
+
+
+ Do I need an account to create QR codes?
+ +
+
+
No account is required to generate static QR codes. Dynamic QR codes with scan tracking and editing capabilities require a free account.
+
+
+
+ What is the difference between static and dynamic QR codes?
+ +
+
+
Static QR codes hold fixed data embedded directly in the code matrix. Dynamic QR codes route through a short URL so you can edit the destination anytime without reprinting.
+
+
+
+ Will static QR codes work forever?
+ +
+
+
Yes. Static QR codes contain raw data (like plain text or URLs) and never expire as long as the destination URL remains active.
+
+
+
+ Which format should I download for printing?
+ +
+
+
For commercial print (flyers, signs, packaging), SVG is recommended because vector graphics scale losslessly to any size.
+
+
+
+ Is QR Master GDPR compliant?
+ +
+
+
Yes. Scan analytics process aggregated technical metadata (country, device type) without storing personally identifiable user data.
+
+
+
+
+
+
+
+
+
+
+
12 GET STARTED
+
Your next QR code is 30 seconds away.
+
Static codes free forever. Dynamic codes with analytics starting at €0.
+
Create a QR Code Free
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/design-variants/landing-classical.html b/design-variants/landing-classical.html
new file mode 100644
index 0000000..e4c96f3
--- /dev/null
+++ b/design-variants/landing-classical.html
@@ -0,0 +1,2088 @@
+
+
+
+
+
+QR Master — Classical — Design Variant
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
EST. 2026 · PLATE I Free Dynamic QR Code Generator
+
Free Dynamic QR Code Generator with Tracking
+
Create static and dynamic QR codes with editable destinations, scan analytics, custom branding, and bulk creation.
+
+
+
+
+
+
+
+
+ https://www.qrmaster.net
+ Live Specimen
+
+
+
+ Select type:
+ URL
+ vCard
+ WiFi
+ PDF
+ Event
+ Coupon
+ App Store
+ Call
+
+
+ + No credit card required
+ + Free forever static codes
+ + Real-time scan analytics
+ + Custom colors & branding
+
+
+
+
+
+
+
+
+
+
+
+
+
01 Instant Studio
+
Interactive QR Code Generator Studio
+
Type a URL or destination, pick colors, test WCAG contrast, and download SVG or PNG instantly without signup.
+
+
SVG + PNG : High-Res 300 DPI : 100% Free
+
+
+
+
+
+
+ URL or Destination Content
+
+
+
+
+
Color Theme Swatches
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Corner Style
+
+ Square Corners
+ Rounded Corners
+
+
+
+
Render Size
+
+
230 px
+
+
+
+
+ Good contrast
+ WCAG Ratio: 16.2:1
+
+
+
+
+
+ Download SVG
+
+
+
+ Download PNG
+
+
+
+
Save & Track Analytics
+
+
+
+
+
+
+
Real-Time Studio Canvas
+
+
https://www.qrmaster.net
+
Scan with any smartphone camera
+
+
+
+
+
+
+
+
+
+
+
02 AI Innovation
+
The future of QR codes is AI-powered
+
Next-generation intelligence for QR code generation, design optimization, and scan prediction.
+
+
In Active Development
+
+
+
+
+
A1
+
Smart QR Generation
+
+ AI-powered content optimization
+ Intelligent design & color matching
+ Auto vCard creation from LinkedIn
+ SEO-optimized short URLs
+
+
+
+
A2
+
Predictive Analytics
+
+ AI scan rate forecasting
+ Anomaly & fraud detection
+ Natural language analytics queries
+ Automated performance alerts
+
+
+
+
A3
+
Smart Content Hub
+
+ Automated campaign tagging
+ Smart bulk CSV generation
+ AI fallback destination routing
+ Dynamic content personalization
+
+
+
+
A4
+
Creative Studio AI
+
+ AI custom artistic QR designs
+ Auto-generated marketing copy
+ Automated A/B split testing
+ Print-ready layout checks
+
+
+
+
+
+
Get early access when AI features launch
+
+
Join 1,400+ creators on the VIP access list
+
+
+ You are on the list. We will notify you first!
+
+
+
+
+
+
+
+
+
+
+
+
+
04 Workflows
+
Popular QR Master Workflows
+
High-intent solutions for editable QR codes, spreadsheet batches, barcodes, meeting links, and local business campaigns.
+
+
+
+
+
+
+
+
+
+
05 Industry Guides
+
QR Codes by Industry
+
Explore how modern teams use dynamic QR codes to track offline engagement and convert physical traffic.
+
+
+
+
+
+
+
+
+
+
+
+
06 Social Proof
+
Trusted by Businesses Worldwide
+
Real stories from restaurant owners, marketers, and designers using QR Master.
+
+
4.9/5 Rating : 12 Verified Reviews
+
+
+
+
+
+
+
+
+
+
+
+
07 Feature Comparison
+
Why Dynamic QR Codes Save You Money
+
Stop re-printing physical materials every time a URL changes. Update destination links instantly and track every scan.
+
+
Static = Fixed Forever : Dynamic = Editable Forever
+
+
+
+
+
+
Edit Destination URL Anytime
+
Fixed Forever
+
YES (Instant)
+
+
+
Real-Time Scan AnalyticsTime, device type, location
+
Not Available
+
YES
+
+
+
Custom Branding & Logo Overlay
+
Basic Only
+
YES
+
+
+
Bulk Creation from Excel/CSV
+
Manual Only
+
YES
+
+
+
No Account Required to CreateDynamic account signup takes under 60 seconds
+
YES
+
Free Account
+
+
+
+
+
+
+
+
+
+
+
08 Interactive ROI Calculator
+
Are you burning budget on static reprints?
+
Estimate your annual print savings by switching from static printed codes to dynamic QR codes.
+
+
+ Total Print Items (e.g. Menus, Flyers, Cards): 2,500
+
+
+
+
+ Annual URL/Destination Updates: 3x per year
+
+
+
+
Calculate Custom Campaign ROI
+
+
+
+
+
+ Estimated Annual Savings
+ €1,125
+ *Based on average €0.15 print cost per unit, industry estimate
+
+
+
+
+
+
+
+
+
+ 09 Platform Highlights
+
Everything You Need for Professional QR Campaigns
+
+
+
+
F/01
+
Real-Time Scan Analytics
+
Track total scans, device breakdown, geographic locations, and time trends in an intuitive dashboard.
+
+
+
F/02
+
Full Brand Customization
+
Customize colors, corner styles, and add your company logo for seamless visual brand alignment.
+
+
+
F/03
+
Unlimited Static QR Codes
+
Generate as many static QR codes as your project needs. Free forever with zero limits or expiration.
+
+
+
+
+
+
+
+
+
+
+
10 Transparent Pricing
+
Choose the Perfect Plan
+
Simple pricing with no hidden fees. Upgrade or downgrade anytime.
+
+
All Plans Include Unlimited Static Codes
+
+
+
+
+ Monthly Billing
+
+ Yearly Billing
+ Save 16%
+
+
+
+
+
+
Free Plan
+
€0 Forever
+
+ 3 active dynamic QR codes (8 types)
+ Unlimited static QR codes
+ Basic scan tracking
+ Standard styling presets
+ High-res SVG & PNG export
+
+
Get Started Free
+
No credit card required
+
+
+
+
Most Popular
+
Pro Plan
+
€9 per month
+
+ 50 dynamic QR codes
+ Unlimited static QR codes
+ Advanced scan analytics
+ Custom branding & logo overlay
+ CSV scan reports export
+
+
Start 14-Day Free Trial
+
No credit card required
+
+
+
+
Business Plan
+
€29 per month
+
+ 500 dynamic QR codes
+ Unlimited static QR codes
+ Everything in Pro
+ Bulk QR Generator (up to 1,000)
+ Priority support
+
+
Get Started
+
+
+
+
Enterprise
+
Custom
+
+ Unlimited dynamic QR codes
+ Unlimited static QR codes
+ Custom API integration
+ Dedicated Account Manager
+ SLA & custom contract
+
+
Contact Sales
+
+
+
All plans include high-resolution vector export and full privacy compliance.
+
+
+
+
+
+
+
+ 11 FAQ
+
Frequently Asked Questions
+
+
+
+
+
+ Do I need an account to create QR codes?
+ +
+
+
No account is required to generate static QR codes. Dynamic QR codes with scan tracking and editing capabilities require a free account.
+
+
+
+ What is the difference between static and dynamic QR codes?
+ +
+
+
Static QR codes hold fixed data embedded directly in the code matrix. Dynamic QR codes route through a short URL so you can edit the destination anytime without reprinting.
+
+
+
+ Will static QR codes work forever?
+ +
+
+
Yes. Static QR codes contain raw data (like plain text or URLs) and never expire as long as the destination URL remains active.
+
+
+
+ Which format should I download for printing?
+ +
+
+
For commercial print (flyers, signs, packaging), SVG is recommended because vector graphics scale losslessly to any size.
+
+
+
+ Is QR Master GDPR compliant?
+ +
+
+
Yes. Scan analytics process aggregated technical metadata (country, device type) without storing personally identifiable user data.
+
+
+
+
+
+
+
+
+
+
+
12 GET STARTED
+
Your next QR code is 30 seconds away.
+
Static codes free forever. Dynamic codes with analytics starting at €0.
+
Create a QR Code Free
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/design-variants/landing-data-texture.html b/design-variants/landing-data-texture.html
new file mode 100644
index 0000000..888a334
--- /dev/null
+++ b/design-variants/landing-data-texture.html
@@ -0,0 +1,2092 @@
+
+
+
+
+
+QR Master — Data-Texture — Design Variant
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
SYS.STATUS :: LIVE Free Dynamic QR Code Generator
+
Free Dynamic QR Code Generator with Tracking
+
Create static and dynamic QR codes with editable destinations, scan analytics, custom branding, and bulk creation.
+
+
+
+
LIVE SPECIMEN
+
+
https://www.qrmaster.net
+
+ URL
+ vCard
+ WiFi
+ PDF
+ Event
+ Coupon
+ App Store
+ Call
+
+
+
+
+ No credit card required
+
+ Free forever static codes
+
+ Real-time scan analytics
+
+ Custom colors & branding
+
+
+
+
+
+
+
+
+
+
+
+
+
01 Instant Studio
+
Interactive QR Code Generator Studio
+
Type a URL or destination, pick colors, test WCAG contrast, and download SVG or PNG instantly without signup.
+
+
SVG + PNG : High-Res 300 DPI : 100% Free
+
+
+
+
+
+
+ URL or Destination Content
+
+
+
+
+
Color Theme Swatches
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Corner Style
+
+ Square Corners
+ Rounded Corners
+
+
+
+
Render Size
+
+
230 px
+
+
+
+
+ Good contrast
+ WCAG Ratio: 16.2:1
+
+
+
+
+
+ Download SVG
+
+
+
+ Download PNG
+
+
+
+
Save & Track Analytics
+
+
+
+
+
+
+
Real-Time Studio Canvas
+
+
https://www.qrmaster.net
+
Scan with any smartphone camera
+
+
+
+
+
+
+
+
+
+
+
02 AI Innovation
+
The future of QR codes is AI-powered
+
Next-generation intelligence for QR code generation, design optimization, and scan prediction.
+
+
In Active Development
+
+
+
+
+
A1
+
Smart QR Generation
+
+ AI-powered content optimization
+ Intelligent design & color matching
+ Auto vCard creation from LinkedIn
+ SEO-optimized short URLs
+
+
+
+
A2
+
Predictive Analytics
+
+ AI scan rate forecasting
+ Anomaly & fraud detection
+ Natural language analytics queries
+ Automated performance alerts
+
+
+
+
A3
+
Smart Content Hub
+
+ Automated campaign tagging
+ Smart bulk CSV generation
+ AI fallback destination routing
+ Dynamic content personalization
+
+
+
+
A4
+
Creative Studio AI
+
+ AI custom artistic QR designs
+ Auto-generated marketing copy
+ Automated A/B split testing
+ Print-ready layout checks
+
+
+
+
+
+
Get early access when AI features launch
+
+
Join 1,400+ creators on the VIP access list
+
+
+ You are on the list. We will notify you first!
+
+
+
+
+
+
+
+
+
+
+
+
+
04 Workflows
+
Popular QR Master Workflows
+
High-intent solutions for editable QR codes, spreadsheet batches, barcodes, meeting links, and local business campaigns.
+
+
+
+
+
+
+
+
+
+
05 Industry Guides
+
QR Codes by Industry
+
Explore how modern teams use dynamic QR codes to track offline engagement and convert physical traffic.
+
+
+
+
+
+
+
+
+
+
+
+
06 Social Proof
+
Trusted by Businesses Worldwide
+
Real stories from restaurant owners, marketers, and designers using QR Master.
+
+
4.9/5 Rating : 12 Verified Reviews
+
+
+
+
+
+
+
+
+
+
+
+
07 Feature Comparison
+
Why Dynamic QR Codes Save You Money
+
Stop re-printing physical materials every time a URL changes. Update destination links instantly and track every scan.
+
+
Static = Fixed Forever : Dynamic = Editable Forever
+
+
+
+
+
+
Edit Destination URL Anytime
+
Fixed Forever
+
YES (Instant)
+
+
+
Real-Time Scan AnalyticsTime, device type, location
+
Not Available
+
YES
+
+
+
Custom Branding & Logo Overlay
+
Basic Only
+
YES
+
+
+
Bulk Creation from Excel/CSV
+
Manual Only
+
YES
+
+
+
No Account Required to CreateDynamic account signup takes under 60 seconds
+
YES
+
Free Account
+
+
+
+
+
+
+
+
+
+
+
08 Interactive ROI Calculator
+
Are you burning budget on static reprints?
+
Estimate your annual print savings by switching from static printed codes to dynamic QR codes.
+
+
+ Total Print Items (e.g. Menus, Flyers, Cards): 2,500
+
+
+
+
+ Annual URL/Destination Updates: 3x per year
+
+
+
+
Calculate Custom Campaign ROI
+
+
+
+
+
+ Estimated Annual Savings
+ €1,125
+ *Based on average €0.15 print cost per unit, industry estimate
+
+
+
+
+
+
+
+
+
+ 09 Platform Highlights
+
Everything You Need for Professional QR Campaigns
+
+
+
+
F/01
+
Real-Time Scan Analytics
+
Track total scans, device breakdown, geographic locations, and time trends in an intuitive dashboard.
+
+
+
F/02
+
Full Brand Customization
+
Customize colors, corner styles, and add your company logo for seamless visual brand alignment.
+
+
+
F/03
+
Unlimited Static QR Codes
+
Generate as many static QR codes as your project needs. Free forever with zero limits or expiration.
+
+
+
+
+
+
+
+
+
+
+
10 Transparent Pricing
+
Choose the Perfect Plan
+
Simple pricing with no hidden fees. Upgrade or downgrade anytime.
+
+
All Plans Include Unlimited Static Codes
+
+
+
+
+ Monthly Billing
+
+ Yearly Billing
+ Save 16%
+
+
+
+
+
+
Free Plan
+
€0 Forever
+
+ 3 active dynamic QR codes (8 types)
+ Unlimited static QR codes
+ Basic scan tracking
+ Standard styling presets
+ High-res SVG & PNG export
+
+
Get Started Free
+
No credit card required
+
+
+
+
Most Popular
+
Pro Plan
+
€9 per month
+
+ 50 dynamic QR codes
+ Unlimited static QR codes
+ Advanced scan analytics
+ Custom branding & logo overlay
+ CSV scan reports export
+
+
Start 14-Day Free Trial
+
No credit card required
+
+
+
+
Business Plan
+
€29 per month
+
+ 500 dynamic QR codes
+ Unlimited static QR codes
+ Everything in Pro
+ Bulk QR Generator (up to 1,000)
+ Priority support
+
+
Get Started
+
+
+
+
Enterprise
+
Custom
+
+ Unlimited dynamic QR codes
+ Unlimited static QR codes
+ Custom API integration
+ Dedicated Account Manager
+ SLA & custom contract
+
+
Contact Sales
+
+
+
All plans include high-resolution vector export and full privacy compliance.
+
+
+
+
+
+
+
+ 11 FAQ
+
Frequently Asked Questions
+
+
+
+
+
+ Do I need an account to create QR codes?
+ +
+
+
No account is required to generate static QR codes. Dynamic QR codes with scan tracking and editing capabilities require a free account.
+
+
+
+ What is the difference between static and dynamic QR codes?
+ +
+
+
Static QR codes hold fixed data embedded directly in the code matrix. Dynamic QR codes route through a short URL so you can edit the destination anytime without reprinting.
+
+
+
+ Will static QR codes work forever?
+ +
+
+
Yes. Static QR codes contain raw data (like plain text or URLs) and never expire as long as the destination URL remains active.
+
+
+
+ Which format should I download for printing?
+ +
+
+
For commercial print (flyers, signs, packaging), SVG is recommended because vector graphics scale losslessly to any size.
+
+
+
+ Is QR Master GDPR compliant?
+ +
+
+
Yes. Scan analytics process aggregated technical metadata (country, device type) without storing personally identifiable user data.
+
+
+
+
+
+
+
+
+
+
+
12 GET STARTED
+
Your next QR code is 30 seconds away.
+
Static codes free forever. Dynamic codes with analytics starting at €0.
+
Create a QR Code Free
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/design-variants/landing-dither-mono.html b/design-variants/landing-dither-mono.html
new file mode 100644
index 0000000..ec25f51
--- /dev/null
+++ b/design-variants/landing-dither-mono.html
@@ -0,0 +1,2093 @@
+
+
+
+
+
+QR Master — Dither Mono — Design Variant
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
HALFTONE · 85 LPI Free Dynamic QR Code Generator
+
Free Dynamic QR Code Generator with Tracking
+
Create static and dynamic QR codes with editable destinations, scan analytics, custom branding, and bulk creation.
+
+
+
+
+
+ https://www.qrmaster.net
+
+
+ + No credit card required
+ + Free forever static codes
+ + Real-time scan analytics
+ + Custom colors & branding
+
+
+ Select type:
+ URL
+ vCard
+ WiFi
+ PDF
+ Event
+ Coupon
+ App Store
+ Call
+
+
+
+
+
+
+
+
+
+
+
+
+
+
01 Instant Studio
+
Interactive QR Code Generator Studio
+
Type a URL or destination, pick colors, test WCAG contrast, and download SVG or PNG instantly without signup.
+
+
SVG + PNG : High-Res 300 DPI : 100% Free
+
+
+
+
+
+
+ URL or Destination Content
+
+
+
+
+
Color Theme Swatches
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Corner Style
+
+ Square Corners
+ Rounded Corners
+
+
+
+
Render Size
+
+
230 px
+
+
+
+
+ Good contrast
+ WCAG Ratio: 16.2:1
+
+
+
+
+
+ Download SVG
+
+
+
+ Download PNG
+
+
+
+
Save & Track Analytics
+
+
+
+
+
+
+
Real-Time Studio Canvas
+
+
https://www.qrmaster.net
+
Scan with any smartphone camera
+
+
+
+
+
+
+
+
+
+
+
02 AI Innovation
+
The future of QR codes is AI-powered
+
Next-generation intelligence for QR code generation, design optimization, and scan prediction.
+
+
In Active Development
+
+
+
+
+
A1
+
Smart QR Generation
+
+ AI-powered content optimization
+ Intelligent design & color matching
+ Auto vCard creation from LinkedIn
+ SEO-optimized short URLs
+
+
+
+
A2
+
Predictive Analytics
+
+ AI scan rate forecasting
+ Anomaly & fraud detection
+ Natural language analytics queries
+ Automated performance alerts
+
+
+
+
A3
+
Smart Content Hub
+
+ Automated campaign tagging
+ Smart bulk CSV generation
+ AI fallback destination routing
+ Dynamic content personalization
+
+
+
+
A4
+
Creative Studio AI
+
+ AI custom artistic QR designs
+ Auto-generated marketing copy
+ Automated A/B split testing
+ Print-ready layout checks
+
+
+
+
+
+
Get early access when AI features launch
+
+
Join 1,400+ creators on the VIP access list
+
+
+ You are on the list. We will notify you first!
+
+
+
+
+
+
+
+
+
+
+
+
+
04 Workflows
+
Popular QR Master Workflows
+
High-intent solutions for editable QR codes, spreadsheet batches, barcodes, meeting links, and local business campaigns.
+
+
+
+
+
+
+
+
+
+
05 Industry Guides
+
QR Codes by Industry
+
Explore how modern teams use dynamic QR codes to track offline engagement and convert physical traffic.
+
+
+
+
+
+
+
+
+
+
+
+
06 Social Proof
+
Trusted by Businesses Worldwide
+
Real stories from restaurant owners, marketers, and designers using QR Master.
+
+
4.9/5 Rating : 12 Verified Reviews
+
+
+
+
+
+
+
+
+
+
+
+
07 Feature Comparison
+
Why Dynamic QR Codes Save You Money
+
Stop re-printing physical materials every time a URL changes. Update destination links instantly and track every scan.
+
+
Static = Fixed Forever : Dynamic = Editable Forever
+
+
+
+
+
+
Edit Destination URL Anytime
+
Fixed Forever
+
YES (Instant)
+
+
+
Real-Time Scan AnalyticsTime, device type, location
+
Not Available
+
YES
+
+
+
Custom Branding & Logo Overlay
+
Basic Only
+
YES
+
+
+
Bulk Creation from Excel/CSV
+
Manual Only
+
YES
+
+
+
No Account Required to CreateDynamic account signup takes under 60 seconds
+
YES
+
Free Account
+
+
+
+
+
+
+
+
+
+
+
08 Interactive ROI Calculator
+
Are you burning budget on static reprints?
+
Estimate your annual print savings by switching from static printed codes to dynamic QR codes.
+
+
+ Total Print Items (e.g. Menus, Flyers, Cards): 2,500
+
+
+
+
+ Annual URL/Destination Updates: 3x per year
+
+
+
+
Calculate Custom Campaign ROI
+
+
+
+
+
+ Estimated Annual Savings
+ €1,125
+ *Based on average €0.15 print cost per unit, industry estimate
+
+
+
+
+
+
+
+
+
+ 09 Platform Highlights
+
Everything You Need for Professional QR Campaigns
+
+
+
+
F/01
+
Real-Time Scan Analytics
+
Track total scans, device breakdown, geographic locations, and time trends in an intuitive dashboard.
+
+
+
F/02
+
Full Brand Customization
+
Customize colors, corner styles, and add your company logo for seamless visual brand alignment.
+
+
+
F/03
+
Unlimited Static QR Codes
+
Generate as many static QR codes as your project needs. Free forever with zero limits or expiration.
+
+
+
+
+
+
+
+
+
+
+
10 Transparent Pricing
+
Choose the Perfect Plan
+
Simple pricing with no hidden fees. Upgrade or downgrade anytime.
+
+
All Plans Include Unlimited Static Codes
+
+
+
+
+ Monthly Billing
+
+ Yearly Billing
+ Save 16%
+
+
+
+
+
+
Free Plan
+
€0 Forever
+
+ 3 active dynamic QR codes (8 types)
+ Unlimited static QR codes
+ Basic scan tracking
+ Standard styling presets
+ High-res SVG & PNG export
+
+
Get Started Free
+
No credit card required
+
+
+
+
Most Popular
+
Pro Plan
+
€9 per month
+
+ 50 dynamic QR codes
+ Unlimited static QR codes
+ Advanced scan analytics
+ Custom branding & logo overlay
+ CSV scan reports export
+
+
Start 14-Day Free Trial
+
No credit card required
+
+
+
+
Business Plan
+
€29 per month
+
+ 500 dynamic QR codes
+ Unlimited static QR codes
+ Everything in Pro
+ Bulk QR Generator (up to 1,000)
+ Priority support
+
+
Get Started
+
+
+
+
Enterprise
+
Custom
+
+ Unlimited dynamic QR codes
+ Unlimited static QR codes
+ Custom API integration
+ Dedicated Account Manager
+ SLA & custom contract
+
+
Contact Sales
+
+
+
All plans include high-resolution vector export and full privacy compliance.
+
+
+
+
+
+
+
+ 11 FAQ
+
Frequently Asked Questions
+
+
+
+
+
+ Do I need an account to create QR codes?
+ +
+
+
No account is required to generate static QR codes. Dynamic QR codes with scan tracking and editing capabilities require a free account.
+
+
+
+ What is the difference between static and dynamic QR codes?
+ +
+
+
Static QR codes hold fixed data embedded directly in the code matrix. Dynamic QR codes route through a short URL so you can edit the destination anytime without reprinting.
+
+
+
+ Will static QR codes work forever?
+ +
+
+
Yes. Static QR codes contain raw data (like plain text or URLs) and never expire as long as the destination URL remains active.
+
+
+
+ Which format should I download for printing?
+ +
+
+
For commercial print (flyers, signs, packaging), SVG is recommended because vector graphics scale losslessly to any size.
+
+
+
+ Is QR Master GDPR compliant?
+ +
+
+
Yes. Scan analytics process aggregated technical metadata (country, device type) without storing personally identifiable user data.
+
+
+
+
+
+
+
+
+
+
+
12 GET STARTED
+
Your next QR code is 30 seconds away.
+
Static codes free forever. Dynamic codes with analytics starting at €0.
+
Create a QR Code Free
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/design-variants/landing-quiet-mono.html b/design-variants/landing-quiet-mono.html
new file mode 100644
index 0000000..7abb633
--- /dev/null
+++ b/design-variants/landing-quiet-mono.html
@@ -0,0 +1,2319 @@
+
+
+
+
+
+QR Master — Quiet Mono — Design Variant
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
01 Instant Studio
+
Interactive QR Code Generator Studio
+
Type a URL or destination, pick colors, test WCAG contrast, and download SVG or PNG instantly without signup.
+
+
SVG + PNG : High-Res 300 DPI : 100% Free
+
+
+
+
+
+
+ URL or Destination Content
+
+
+
+
+
Color Theme Swatches
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Corner Style
+
+ Square Corners
+ Rounded Corners
+
+
+
+
Render Size
+
+
230 px
+
+
+
+
+ Good contrast
+ WCAG Ratio: 16.2:1
+
+
+
+
+
+ Download SVG
+
+
+
+ Download PNG
+
+
+
+
Save & Track Analytics
+
+
+
+
+
+
+
Real-Time Studio Canvas
+
+
https://www.qrmaster.net
+
Scan with any smartphone camera
+
+
+
+
+
+
+
+
+
+
+
02 AI Innovation
+
The future of QR codes is AI-powered
+
Next-generation intelligence for QR code generation, design optimization, and scan prediction.
+
+
In Active Development
+
+
+
+
+
A1
+
Smart QR Generation
+
+ AI-powered content optimization
+ Intelligent design & color matching
+ Auto vCard creation from LinkedIn
+ SEO-optimized short URLs
+
+
+
+
A2
+
Predictive Analytics
+
+ AI scan rate forecasting
+ Anomaly & fraud detection
+ Natural language analytics queries
+ Automated performance alerts
+
+
+
+
A3
+
Smart Content Hub
+
+ Automated campaign tagging
+ Smart bulk CSV generation
+ AI fallback destination routing
+ Dynamic content personalization
+
+
+
+
A4
+
Creative Studio AI
+
+ AI custom artistic QR designs
+ Auto-generated marketing copy
+ Automated A/B split testing
+ Print-ready layout checks
+
+
+
+
+
+
Get early access when AI features launch
+
+
Join 1,400+ creators on the VIP access list
+
+
+ You are on the list. We will notify you first!
+
+
+
+
+
+
+
+
+
+
+
+
+
04 Workflows
+
Popular QR Master Workflows
+
High-intent solutions for editable QR codes, spreadsheet batches, barcodes, meeting links, and local business campaigns.
+
+
+
+
+
+
+
+
+
+
05 Industry Guides
+
QR Codes by Industry
+
Explore how modern teams use dynamic QR codes to track offline engagement and convert physical traffic.
+
+
+
+
+
+
+
+
+
+
+
+
06 Social Proof
+
Trusted by Businesses Worldwide
+
Real stories from restaurant owners, marketers, and designers using QR Master.
+
+
4.9/5 Rating : 12 Verified Reviews
+
+
+
+
+
+
+
+
+
+
+
+
07 Feature Comparison
+
Why Dynamic QR Codes Save You Money
+
Stop re-printing physical materials every time a URL changes. Update destination links instantly and track every scan.
+
+
Static = Fixed Forever : Dynamic = Editable Forever
+
+
+
+
+
+
Edit Destination URL Anytime
+
Fixed Forever
+
YES (Instant)
+
+
+
Real-Time Scan AnalyticsTime, device type, location
+
Not Available
+
YES
+
+
+
Custom Branding & Logo Overlay
+
Basic Only
+
YES
+
+
+
Bulk Creation from Excel/CSV
+
Manual Only
+
YES
+
+
+
No Account Required to CreateDynamic account signup takes under 60 seconds
+
YES
+
Free Account
+
+
+
+
+
+
+
+
+
+
+
08 Interactive ROI Calculator
+
Are you burning budget on static reprints?
+
Estimate your annual print savings by switching from static printed codes to dynamic QR codes.
+
+
+ Total Print Items (e.g. Menus, Flyers, Cards): 2,500
+
+
+
+
+ Annual URL/Destination Updates: 3x per year
+
+
+
+
Calculate Custom Campaign ROI
+
+
+
+
+
+ Estimated Annual Savings
+ €1,125
+ *Based on average €0.15 print cost per unit, industry estimate
+
+
+
+
+
+
+
+
+
+ 09 Platform Highlights
+
Everything You Need for Professional QR Campaigns
+
+
+
+
F/01
+
Real-Time Scan Analytics
+
Track total scans, device breakdown, geographic locations, and time trends in an intuitive dashboard.
+
+
+
F/02
+
Full Brand Customization
+
Customize colors, corner styles, and add your company logo for seamless visual brand alignment.
+
+
+
F/03
+
Unlimited Static QR Codes
+
Generate as many static QR codes as your project needs. Free forever with zero limits or expiration.
+
+
+
+
+
+
+
+
+
+
+
10 Transparent Pricing
+
Choose the Perfect Plan
+
Simple pricing with no hidden fees. Upgrade or downgrade anytime.
+
+
All Plans Include Unlimited Static Codes
+
+
+
+
+ Monthly Billing
+
+ Yearly Billing
+ Save 16%
+
+
+
+
+
+
Free Plan
+
€0 Forever
+
+ 3 active dynamic QR codes (8 types)
+ Unlimited static QR codes
+ Basic scan tracking
+ Standard styling presets
+ High-res SVG & PNG export
+
+
Get Started Free
+
No credit card required
+
+
+
+
Most Popular
+
Pro Plan
+
€9 per month
+
+ 50 dynamic QR codes
+ Unlimited static QR codes
+ Advanced scan analytics
+ Custom branding & logo overlay
+ CSV scan reports export
+
+
Start 14-Day Free Trial
+
No credit card required
+
+
+
+
Business Plan
+
€29 per month
+
+ 500 dynamic QR codes
+ Unlimited static QR codes
+ Everything in Pro
+ Bulk QR Generator (up to 1,000)
+ Priority support
+
+
Get Started
+
+
+
+
Enterprise
+
Custom
+
+ Unlimited dynamic QR codes
+ Unlimited static QR codes
+ Custom API integration
+ Dedicated Account Manager
+ SLA & custom contract
+
+
Contact Sales
+
+
+
All plans include high-resolution vector export and full privacy compliance.
+
+
+
+
+
+
+
+ 11 FAQ
+
Frequently Asked Questions
+
+
+
+
+
+ Do I need an account to create QR codes?
+ +
+
+
No account is required to generate static QR codes. Dynamic QR codes with scan tracking and editing capabilities require a free account.
+
+
+
+ What is the difference between static and dynamic QR codes?
+ +
+
+
Static QR codes hold fixed data embedded directly in the code matrix. Dynamic QR codes route through a short URL so you can edit the destination anytime without reprinting.
+
+
+
+ Will static QR codes work forever?
+ +
+
+
Yes. Static QR codes contain raw data (like plain text or URLs) and never expire as long as the destination URL remains active.
+
+
+
+ Which format should I download for printing?
+ +
+
+
For commercial print (flyers, signs, packaging), SVG is recommended because vector graphics scale losslessly to any size.
+
+
+
+ Is QR Master GDPR compliant?
+ +
+
+
Yes. Scan analytics process aggregated technical metadata (country, device type) without storing personally identifiable user data.
+
+
+
+
+
+
+
+
+
+
+
12 GET STARTED
+
Your next QR code is 30 seconds away.
+
Static codes free forever. Dynamic codes with analytics starting at €0.
+
Create a QR Code Free
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/design-variants/landing-vast-quiet.html b/design-variants/landing-vast-quiet.html
new file mode 100644
index 0000000..2414a1d
--- /dev/null
+++ b/design-variants/landing-vast-quiet.html
@@ -0,0 +1,2085 @@
+
+
+
+
+
+QR Master — Vast Quiet — Design Variant
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
01 Instant Studio
+
Interactive QR Code Generator Studio
+
Type a URL or destination, pick colors, test WCAG contrast, and download SVG or PNG instantly without signup.
+
+
SVG + PNG : High-Res 300 DPI : 100% Free
+
+
+
+
+
+
+ URL or Destination Content
+
+
+
+
+
Color Theme Swatches
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Corner Style
+
+ Square Corners
+ Rounded Corners
+
+
+
+
Render Size
+
+
230 px
+
+
+
+
+ Good contrast
+ WCAG Ratio: 16.2:1
+
+
+
+
+
+ Download SVG
+
+
+
+ Download PNG
+
+
+
+
Save & Track Analytics
+
+
+
+
+
+
+
Real-Time Studio Canvas
+
+
https://www.qrmaster.net
+
Scan with any smartphone camera
+
+
+
+
+
+
+
+
+
+
+
02 AI Innovation
+
The future of QR codes is AI-powered
+
Next-generation intelligence for QR code generation, design optimization, and scan prediction.
+
+
In Active Development
+
+
+
+
+
A1
+
Smart QR Generation
+
+ AI-powered content optimization
+ Intelligent design & color matching
+ Auto vCard creation from LinkedIn
+ SEO-optimized short URLs
+
+
+
+
A2
+
Predictive Analytics
+
+ AI scan rate forecasting
+ Anomaly & fraud detection
+ Natural language analytics queries
+ Automated performance alerts
+
+
+
+
A3
+
Smart Content Hub
+
+ Automated campaign tagging
+ Smart bulk CSV generation
+ AI fallback destination routing
+ Dynamic content personalization
+
+
+
+
A4
+
Creative Studio AI
+
+ AI custom artistic QR designs
+ Auto-generated marketing copy
+ Automated A/B split testing
+ Print-ready layout checks
+
+
+
+
+
+
Get early access when AI features launch
+
+
Join 1,400+ creators on the VIP access list
+
+
+ You are on the list. We will notify you first!
+
+
+
+
+
+
+
+
+
+
+
+
+
04 Workflows
+
Popular QR Master Workflows
+
High-intent solutions for editable QR codes, spreadsheet batches, barcodes, meeting links, and local business campaigns.
+
+
+
+
+
+
+
+
+
+
05 Industry Guides
+
QR Codes by Industry
+
Explore how modern teams use dynamic QR codes to track offline engagement and convert physical traffic.
+
+
+
+
+
+
+
+
+
+
+
+
06 Social Proof
+
Trusted by Businesses Worldwide
+
Real stories from restaurant owners, marketers, and designers using QR Master.
+
+
4.9/5 Rating : 12 Verified Reviews
+
+
+
+
+
+
+
+
+
+
+
+
07 Feature Comparison
+
Why Dynamic QR Codes Save You Money
+
Stop re-printing physical materials every time a URL changes. Update destination links instantly and track every scan.
+
+
Static = Fixed Forever : Dynamic = Editable Forever
+
+
+
+
+
+
Edit Destination URL Anytime
+
Fixed Forever
+
YES (Instant)
+
+
+
Real-Time Scan AnalyticsTime, device type, location
+
Not Available
+
YES
+
+
+
Custom Branding & Logo Overlay
+
Basic Only
+
YES
+
+
+
Bulk Creation from Excel/CSV
+
Manual Only
+
YES
+
+
+
No Account Required to CreateDynamic account signup takes under 60 seconds
+
YES
+
Free Account
+
+
+
+
+
+
+
+
+
+
+
08 Interactive ROI Calculator
+
Are you burning budget on static reprints?
+
Estimate your annual print savings by switching from static printed codes to dynamic QR codes.
+
+
+ Total Print Items (e.g. Menus, Flyers, Cards): 2,500
+
+
+
+
+ Annual URL/Destination Updates: 3x per year
+
+
+
+
Calculate Custom Campaign ROI
+
+
+
+
+
+ Estimated Annual Savings
+ €1,125
+ *Based on average €0.15 print cost per unit, industry estimate
+
+
+
+
+
+
+
+
+
+ 09 Platform Highlights
+
Everything You Need for Professional QR Campaigns
+
+
+
+
F/01
+
Real-Time Scan Analytics
+
Track total scans, device breakdown, geographic locations, and time trends in an intuitive dashboard.
+
+
+
F/02
+
Full Brand Customization
+
Customize colors, corner styles, and add your company logo for seamless visual brand alignment.
+
+
+
F/03
+
Unlimited Static QR Codes
+
Generate as many static QR codes as your project needs. Free forever with zero limits or expiration.
+
+
+
+
+
+
+
+
+
+
+
10 Transparent Pricing
+
Choose the Perfect Plan
+
Simple pricing with no hidden fees. Upgrade or downgrade anytime.
+
+
All Plans Include Unlimited Static Codes
+
+
+
+
+ Monthly Billing
+
+ Yearly Billing
+ Save 16%
+
+
+
+
+
+
Free Plan
+
€0 Forever
+
+ 3 active dynamic QR codes (8 types)
+ Unlimited static QR codes
+ Basic scan tracking
+ Standard styling presets
+ High-res SVG & PNG export
+
+
Get Started Free
+
No credit card required
+
+
+
+
Most Popular
+
Pro Plan
+
€9 per month
+
+ 50 dynamic QR codes
+ Unlimited static QR codes
+ Advanced scan analytics
+ Custom branding & logo overlay
+ CSV scan reports export
+
+
Start 14-Day Free Trial
+
No credit card required
+
+
+
+
Business Plan
+
€29 per month
+
+ 500 dynamic QR codes
+ Unlimited static QR codes
+ Everything in Pro
+ Bulk QR Generator (up to 1,000)
+ Priority support
+
+
Get Started
+
+
+
+
Enterprise
+
Custom
+
+ Unlimited dynamic QR codes
+ Unlimited static QR codes
+ Custom API integration
+ Dedicated Account Manager
+ SLA & custom contract
+
+
Contact Sales
+
+
+
All plans include high-resolution vector export and full privacy compliance.
+
+
+
+
+
+
+
+ 11 FAQ
+
Frequently Asked Questions
+
+
+
+
+
+ Do I need an account to create QR codes?
+ +
+
+
No account is required to generate static QR codes. Dynamic QR codes with scan tracking and editing capabilities require a free account.
+
+
+
+ What is the difference between static and dynamic QR codes?
+ +
+
+
Static QR codes hold fixed data embedded directly in the code matrix. Dynamic QR codes route through a short URL so you can edit the destination anytime without reprinting.
+
+
+
+ Will static QR codes work forever?
+ +
+
+
Yes. Static QR codes contain raw data (like plain text or URLs) and never expire as long as the destination URL remains active.
+
+
+
+ Which format should I download for printing?
+ +
+
+
For commercial print (flyers, signs, packaging), SVG is recommended because vector graphics scale losslessly to any size.
+
+
+
+ Is QR Master GDPR compliant?
+ +
+
+
Yes. Scan analytics process aggregated technical metadata (country, device type) without storing personally identifiable user data.
+
+
+
+
+
+
+
+
+
+
+
12 GET STARTED
+
Your next QR code is 30 seconds away.
+
Static codes free forever. Dynamic codes with analytics starting at €0.
+
Create a QR Code Free
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/landing-design-v2.html b/landing-design-v2.html
new file mode 100644
index 0000000..b769843
--- /dev/null
+++ b/landing-design-v2.html
@@ -0,0 +1,2082 @@
+
+
+
+
+
+QR Master: Free Dynamic QR Code Generator with Tracking
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
BLUEPRINT 3.0 Free Dynamic QR Code Generator
+
Free Dynamic QR Code Generator with Tracking
+
+ Create static and dynamic QR codes with editable destinations, scan analytics, custom branding, and bulk creation.
+
+
+ + No credit card required
+ + Free forever static codes
+ + Real-time scan analytics
+ + Custom colors & branding
+
+
+
+ Select type:
+ URL
+ vCard
+ WiFi
+ PDF
+ Event
+ Coupon
+ App Store
+ Call
+
+
+
+
+
+
Fig. 01: Specimen Preview
+
QRM/2026: Rev C
+
+
+
+
+
+
+
+
+
+ https://www.qrmaster.net
+ Live Specimen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
01 Instant Studio
+
Interactive QR Code Generator Studio
+
Type a URL or destination, pick colors, test WCAG contrast, and download SVG or PNG instantly without signup.
+
+
SVG + PNG : High-Res 300 DPI : 100% Free
+
+
+
+
+
+
+ URL or Destination Content
+
+
+
+
+
Color Theme Swatches
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Corner Style
+
+ Square Corners
+ Rounded Corners
+
+
+
+
Render Size
+
+
230 px
+
+
+
+
+ Good contrast
+ WCAG Ratio: 16.2:1
+
+
+
+
+
+ Download SVG
+
+
+
+ Download PNG
+
+
+
+
Save & Track Analytics
+
+
+
+
+
+
+
Real-Time Studio Canvas
+
+
https://www.qrmaster.net
+
Scan with any smartphone camera
+
+
+
+
+
+
+
+
+
+
+
02 AI Innovation
+
The future of QR codes is AI-powered
+
Next-generation intelligence for QR code generation, design optimization, and scan prediction.
+
+
In Active Development
+
+
+
+
+
A1
+
Smart QR Generation
+
+ AI-powered content optimization
+ Intelligent design & color matching
+ Auto vCard creation from LinkedIn
+ SEO-optimized short URLs
+
+
+
+
A2
+
Predictive Analytics
+
+ AI scan rate forecasting
+ Anomaly & fraud detection
+ Natural language analytics queries
+ Automated performance alerts
+
+
+
+
A3
+
Smart Content Hub
+
+ Automated campaign tagging
+ Smart bulk CSV generation
+ AI fallback destination routing
+ Dynamic content personalization
+
+
+
+
A4
+
Creative Studio AI
+
+ AI custom artistic QR designs
+ Auto-generated marketing copy
+ Automated A/B split testing
+ Print-ready layout checks
+
+
+
+
+
+
Get early access when AI features launch
+
+
Join 1,400+ creators on the VIP access list
+
+
+ You are on the list. We will notify you first!
+
+
+
+
+
+
+
+
+
+
+
+
+
04 Workflows
+
Popular QR Master Workflows
+
High-intent solutions for editable QR codes, spreadsheet batches, barcodes, meeting links, and local business campaigns.
+
+
+
+
+
+
+
+
+
+
05 Industry Guides
+
QR Codes by Industry
+
Explore how modern teams use dynamic QR codes to track offline engagement and convert physical traffic.
+
+
+
+
+
+
+
+
+
+
+
+
06 Social Proof
+
Trusted by Businesses Worldwide
+
Real stories from restaurant owners, marketers, and designers using QR Master.
+
+
4.9/5 Rating : 12 Verified Reviews
+
+
+
+
+
+
+
+
+
+
+
+
07 Feature Comparison
+
Why Dynamic QR Codes Save You Money
+
Stop re-printing physical materials every time a URL changes. Update destination links instantly and track every scan.
+
+
Static = Fixed Forever : Dynamic = Editable Forever
+
+
+
+
+
+
Edit Destination URL Anytime
+
Fixed Forever
+
YES (Instant)
+
+
+
Real-Time Scan AnalyticsTime, device type, location
+
Not Available
+
YES
+
+
+
Custom Branding & Logo Overlay
+
Basic Only
+
YES
+
+
+
Bulk Creation from Excel/CSV
+
Manual Only
+
YES
+
+
+
No Account Required to CreateDynamic account signup takes under 60 seconds
+
YES
+
Free Account
+
+
+
+
+
+
+
+
+
+
+
08 Interactive ROI Calculator
+
Are you burning budget on static reprints?
+
Estimate your annual print savings by switching from static printed codes to dynamic QR codes.
+
+
+ Total Print Items (e.g. Menus, Flyers, Cards): 2,500
+
+
+
+
+ Annual URL/Destination Updates: 3x per year
+
+
+
+
Calculate Custom Campaign ROI
+
+
+
+
+
+ Estimated Annual Savings
+ €1,125
+ *Based on average €0.15 print cost per unit, industry estimate
+
+
+
+
+
+
+
+
+
+ 09 Platform Highlights
+
Everything You Need for Professional QR Campaigns
+
+
+
+
F/01
+
Real-Time Scan Analytics
+
Track total scans, device breakdown, geographic locations, and time trends in an intuitive dashboard.
+
+
+
F/02
+
Full Brand Customization
+
Customize colors, corner styles, and add your company logo for seamless visual brand alignment.
+
+
+
F/03
+
Unlimited Static QR Codes
+
Generate as many static QR codes as your project needs. Free forever with zero limits or expiration.
+
+
+
+
+
+
+
+
+
+
+
10 Transparent Pricing
+
Choose the Perfect Plan
+
Simple pricing with no hidden fees. Upgrade or downgrade anytime.
+
+
All Plans Include Unlimited Static Codes
+
+
+
+
+ Monthly Billing
+
+ Yearly Billing
+ Save 16%
+
+
+
+
+
+
Free Plan
+
€0 Forever
+
+ 3 active dynamic QR codes (8 types)
+ Unlimited static QR codes
+ Basic scan tracking
+ Standard styling presets
+ High-res SVG & PNG export
+
+
Get Started Free
+
No credit card required
+
+
+
+
Most Popular
+
Pro Plan
+
€9 per month
+
+ 50 dynamic QR codes
+ Unlimited static QR codes
+ Advanced scan analytics
+ Custom branding & logo overlay
+ CSV scan reports export
+
+
Start 14-Day Free Trial
+
No credit card required
+
+
+
+
Business Plan
+
€29 per month
+
+ 500 dynamic QR codes
+ Unlimited static QR codes
+ Everything in Pro
+ Bulk QR Generator (up to 1,000)
+ Priority support
+
+
Get Started
+
+
+
+
Enterprise
+
Custom
+
+ Unlimited dynamic QR codes
+ Unlimited static QR codes
+ Custom API integration
+ Dedicated Account Manager
+ SLA & custom contract
+
+
Contact Sales
+
+
+
All plans include high-resolution vector export and full privacy compliance.
+
+
+
+
+
+
+
+ 11 FAQ
+
Frequently Asked Questions
+
+
+
+
+
+ Do I need an account to create QR codes?
+ +
+
+
No account is required to generate static QR codes. Dynamic QR codes with scan tracking and editing capabilities require a free account.
+
+
+
+ What is the difference between static and dynamic QR codes?
+ +
+
+
Static QR codes hold fixed data embedded directly in the code matrix. Dynamic QR codes route through a short URL so you can edit the destination anytime without reprinting.
+
+
+
+ Will static QR codes work forever?
+ +
+
+
Yes. Static QR codes contain raw data (like plain text or URLs) and never expire as long as the destination URL remains active.
+
+
+
+ Which format should I download for printing?
+ +
+
+
For commercial print (flyers, signs, packaging), SVG is recommended because vector graphics scale losslessly to any size.
+
+
+
+ Is QR Master GDPR compliant?
+ +
+
+
Yes. Scan analytics process aggregated technical metadata (country, device type) without storing personally identifiable user data.
+
+
+
+
+
+
+
+
+
+
+
12 GET STARTED
+
Your next QR code is 30 seconds away.
+
Static codes free forever. Dynamic codes with analytics starting at €0.
+
Create a QR Code Free
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 3f4006c..7625d65 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -33,9 +33,11 @@ model User {
resetPasswordExpires DateTime?
// Retention email tracking
- activationNudgeSentAt DateTime?
- upgradeNudgeSentAt DateTime?
- thirtyDayNudgeSentAt DateTime?
+ activationNudgeSentAt DateTime?
+ upgradeNudgeSentAt DateTime?
+ thirtyDayNudgeSentAt DateTime?
+ limitReachedNudgeSentAt DateTime?
+ firstScanNudgeSentAt DateTime?
// RevOps attribution
signupSource String?
@@ -69,18 +71,19 @@ model User {
onboardingCompletedAt DateTime?
// RevOps scoring
- fitScore Int @default(0)
- intentScore Int @default(0)
- leadScore Int @default(0)
- lifecycleStage String @default("cold")
+ fitScore Int @default(0)
+ intentScore Int @default(0)
+ leadScore Int @default(0)
+ lifecycleStage String @default("cold")
lastQualifiedAt DateTime?
lastScoredAt DateTime?
- qrCodes QRCode[]
- integrations Integration[]
- accounts Account[]
- sessions Session[]
- lifecycleLogs UserLifecycleLog[]
+ qrCodes QRCode[]
+ designPresets QRDesignPreset[]
+ integrations Integration[]
+ accounts Account[]
+ sessions Session[]
+ lifecycleLogs UserLifecycleLog[]
}
enum Plan {
@@ -142,6 +145,7 @@ model QRCode {
scans QRScan[]
@@index([userId, createdAt])
+ @@index([userId, type, status])
}
enum QRType {
@@ -170,19 +174,19 @@ enum QRStatus {
}
model QRScan {
- id String @id @default(cuid())
- qrId String
- ts DateTime @default(now())
- ipHash String
- userAgent String?
- device String?
- os String?
- country String?
- referrer String?
- utmSource String?
- utmMedium String?
- utmCampaign String?
- isUnique Boolean @default(false)
+ id String @id @default(cuid())
+ qrId String
+ ts DateTime @default(now())
+ ipHash String
+ userAgent String?
+ device String?
+ os String?
+ country String?
+ referrer String?
+ utmSource String?
+ utmMedium String?
+ utmCampaign String?
+ isUnique Boolean @default(false)
qr QRCode @relation(fields: [qrId], references: [id], onDelete: Cascade)
@@ -222,16 +226,32 @@ model SocialAsset {
createdAt DateTime @default(now())
}
+/// Saved design presets. The point for an agency is not the star shape, it is
+/// that client A looks identical across 500 codes.
+model QRDesignPreset {
+ id String @id @default(cuid())
+ userId String
+ name String
+ style Json
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ @@unique([userId, name])
+ @@index([userId])
+}
+
model UserLifecycleLog {
- id String @id @default(cuid())
- userId String
- fromStage String?
- toStage String
- fitScore Int @default(0)
- intentScore Int @default(0)
- leadScore Int @default(0)
- reason String?
- createdAt DateTime @default(now())
+ id String @id @default(cuid())
+ userId String
+ fromStage String?
+ toStage String
+ fitScore Int @default(0)
+ intentScore Int @default(0)
+ leadScore Int @default(0)
+ reason String?
+ createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
diff --git a/public/favicon.ico b/public/favicon.ico
index 5a198d0..ede6ea8 100644
Binary files a/public/favicon.ico and b/public/favicon.ico differ
diff --git a/public/favicon.svg b/public/favicon.svg
index b049bca..ea58114 100644
--- a/public/favicon.svg
+++ b/public/favicon.svg
@@ -1,32 +1,3 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/public/favicon_sticker.png b/public/favicon_sticker.png
new file mode 100644
index 0000000..a0c6625
Binary files /dev/null and b/public/favicon_sticker.png differ
diff --git a/public/logo.svg b/public/logo.svg
index 97d3f3f..4e81872 100644
--- a/public/logo.svg
+++ b/public/logo.svg
@@ -1,32 +1,3 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/qr-design-showcase.html b/qr-design-showcase.html
new file mode 100644
index 0000000..f21d0bd
--- /dev/null
+++ b/qr-design-showcase.html
@@ -0,0 +1,1044 @@
+
+
+
+
+
+QR Code Design Showcase
+
+
+
+
+
+
+ QR Code Design Showcase
+ Ein Baukasten für QR-Code-Designs — Modulformen, Ecken-Stile, Farbverläufe, Rahmen, Logos & mehr. Klick auf eine Karte, um sie in den Editor zu laden.
+
+
+
+
+
+
+
Inhalt
+
+ Inhalt / Link
+
+
+
+ Fehlerkorrektur
+
+ L (7%)
+ M (15%)
+ Q (25%)
+ H (30%) — empfohlen bei Logo
+
+
+
+
+
+
Formen
+
+
+ Modul-Form
+
+ Eckig (klassisch)
+ Abgerundet
+ Punkte / Dots
+ Classy (fließend)
+ Classy Rounded
+ Liquid / Blob
+ Diamant
+ Sterne
+ Hexagon
+ Plus / Kreuz
+ Mosaic (gemischt)
+
+
+
+ Ecken-Rahmen (Eyes)
+
+ Eckig
+ Abgerundet
+ Kreis
+ Blatt
+ Flower / Squircle
+ Hexagon
+
+
+
+
+ Ecken-Kern (Eye Ball)
+
+ Eckig
+ Abgerundet
+ Kreis
+ Diamant
+ Stern
+ Hexagon
+
+
+
+
+
+
Farben
+
+ Farb-Modus
+
+ Einfarbig
+ Verlauf (linear)
+ Verlauf (radial)
+
+
+
+
+ Verlaufswinkel 45°
+
+
+
+
+ Eigene Farbe für die Ecken (Eyes)
+
+
+
+ Hintergrund
+
+
+
+
+
+
Rahmen & Label
+
+ Rahmen-Stil
+
+ Kein Rahmen
+ Weiße Karte mit Schatten
+ Karte + Label unten
+ Karte + Label oben
+
+
+
+
+
+
+
Logo in der Mitte
+
+ Logo-Symbol
+
+ Kein Logo
+ Punkt
+ Quadrat
+ Herz
+ Blitz
+ WLAN-Symbol
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PNG herunterladen
+ 🎲 Zufalls-Design
+
+
+
+
+Design-Galerie Varianten — anklicken zum Übernehmen
+
+
+QR Master · Design Showcase · reine Frontend-Demo, alle Codes sind scanbar
+
+
+
+
diff --git a/qr-master-design.html b/qr-master-design.html
new file mode 100644
index 0000000..e531f2a
--- /dev/null
+++ b/qr-master-design.html
@@ -0,0 +1,1782 @@
+
+
+
+
+
+QR Master - Free Dynamic QR Code Generator
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Free QR code generator
+
Codes that keep working after you print them.
+
Create a QR code in seconds, then edit where it points anytime. No account needed to start.
+
+
+
+
+
+
+
+
+
+
+ Live preview
+
+
https://www.qrmaster.net
+
+
+ URL
+ vCard
+ WiFi
+ Event
+ Call
+
+
+
+
+
+
+
+
+
+
+
+
+
Build your first code here.
+
Type a destination, pick colors, check WCAG contrast, and download SVG or PNG. No signup, no watermark.
+
+
+
+
+ URL or destination content
+
+
+
+
Color theme
+
+
+
+
+
+
+
+
+
+
+
+ Corner style
+
+ Square corners
+ Rounded corners
+
+
+
+
Render size
+
+
230 px
+
+
+
+ Good contrast
+ WCAG ratio: 16.2:1
+
+
+
+
+ Download SVG
+
+
+
+ Download PNG
+
+
+
Save and track scans
+
+
+
+
+
+
https://www.qrmaster.net
+
Scan with any smartphone camera
+
+
+
+
+
+
+
+
+
+
What you get for free.
+
+
+
+
Edit the destination anytime, without reprinting anything.
+
Dynamic codes route through a short link you control, so the printed code never has to change.
+
+
+
3
+
Dynamic codes on the free plan
+
+
+
18
+
Free specialized generators
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Popular ways to use QR Master.
+
Real workflows from restaurants, hotels, agencies, and event teams already running on dynamic codes.
+
+
+
+
+
+
+
+
+
+
+
+
See what static reprints are costing you .
+
Estimate your annual print savings by switching from static printed codes to dynamic ones.
+
+ Total print items, such as menus, flyers, or cards: 2,500
+
+
+
+ Annual destination updates: 3x per year
+
+
+
Calculate your campaign
+
+
+
+
+ Estimated annual savings
+ €1,125
+ Based on an average €0.15 print cost per unit
+
+
+
+
+
+
+
+
+
+
Why switch to dynamic.
+
Stop reprinting physical materials every time a destination changes.
+
+
+
+
+
Edit the destination anytimeNo reprint required
+
Fixed forever
+
Instant
+
+
+
Scan analyticsTime, device, location
+
Not available
+
Included
+
+
+
Custom branding and logo overlay
+
Basic only
+
Full support
+
+
+
Bulk creation from a spreadsheet
+
Manual only
+
Included
+
+
+
+
+
+
+
+
+
+
+
What people are actually saying.
+
+
+
+
+
+
+
+
+
+
Simple pricing
+
Choose the plan that fits.
+
No hidden fees. Upgrade or downgrade whenever you need to.
+
+
+
+ Monthly
+
+ Yearly
+ Save 16%
+
+
+
+
+
Free
+
€0 Forever
+
+ 3 active dynamic codes, 8 types
+ Unlimited static codes
+ Basic scan tracking
+ High-res SVG and PNG export
+
+
Get started free
+
+
+
Most popular
+
Pro
+
€9 per month
+
+ 50 dynamic codes
+ Advanced scan analytics
+ Custom branding and logo overlay
+ CSV scan report export
+
+
Start a 14-day trial
+
+
+
Business
+
€29 per month
+
+ 500 dynamic codes
+ Everything in Pro
+ Bulk generator, up to 1,000 codes
+ Priority support
+
+
Get started
+
+
+
Enterprise
+
Custom
+
+ Unlimited dynamic codes
+ Custom API integration
+ Dedicated account manager
+ SLA and custom contract
+
+
Contact sales
+
+
+
+
+
+
+
+
+
+
Common questions.
+
+
+
+
Do I need an account to create a code?+
+
No. Static codes can be created without an account. Dynamic codes, which support editing and scan tracking, need a free account.
+
+
+
What is the difference between static and dynamic?+
+
A static code holds its destination directly in the pattern. A dynamic code routes through a short link, so you can change where it points without changing the printed code.
+
+
+
Do static codes expire?+
+
No. A static code holds plain data and keeps working for as long as the destination itself stays active.
+
+
+
Which file format should I use for printing?+
+
SVG, for commercial print. It scales to any size without losing sharpness.
+
+
+
Is QR Master GDPR compliant?+
+
Yes. Scan analytics use aggregated technical metadata, such as country and device type, and never store personal data.
+
+
+
+
+
+
+
+
+
Your first code is thirty seconds away.
+
Static codes are free forever. Dynamic codes with analytics start at €0.
+
Create a QR code
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reports/seo-improver/2026-07-26/dataforseo-drei-domains.md b/reports/seo-improver/2026-07-26/dataforseo-drei-domains.md
new file mode 100644
index 0000000..ec4d05d
--- /dev/null
+++ b/reports/seo-improver/2026-07-26/dataforseo-drei-domains.md
@@ -0,0 +1,165 @@
+# DataForSEO SEO-Analyse – QRMaster, GreenLens Pro, Entscheidomat
+
+**Datum:** 2026-07-26
+**Quelle:** DataForSEO API v3, Google Labs / Ranked Keywords / Competitors Domain / Domain Intersection
+
+## Untersuchungsumfang
+
+| Domain | Markt / Sprache | Ranked Keywords | Wettbewerberdaten |
+|---|---|---:|---|
+| qrmaster.net | Deutschland / Deutsch | 3 | erfolgreich |
+| qrmaster.net | USA / Englisch | 100 | erfolgreich |
+| greenlenspro.com | USA / Englisch | 100 | erfolgreich |
+| entscheidomat.com | Deutschland / Deutsch | 0 | keine Rankingdaten |
+
+Die Werte sind eine DataForSEO-Momentaufnahme und kein Ersatz für Google Search Console. Rankingdaten wurden als organische Google-Daten abgerufen.
+
+## 1. QRMaster
+
+### Deutschland / Deutsch
+
+Aktuell gemessene Rankings:
+
+- `qr codes tracking` – Position 97, Suchvolumen 210, `/qr-code-tracking`
+- `qr-code karten` – Position 52, Suchvolumen 110, `/de/qr-codes-fuer-visitenkarten`
+- `dynamic qr code generator` – Position 38, Suchvolumen 90, `/dynamic-qr-code-generator`
+
+Wichtig: Die Rankingdaten enthalten kaum deutschsprachige Kernbegriffe. Das spricht eher für ein Content-/Lokalisierungsproblem als für ein reines Technikproblem.
+
+### USA / Englisch
+
+Stärkste aktuell gefundene Themen:
+
+- `generate a barcode free` – Volumen 40.500, Position 98, `/tools/barcode-generator`
+- `visiting card with qr code` – 12.100, Position 107, `/use-cases/business-card-qr-codes`
+- `barcode design online` – 9.900, Position 104, `/tools/barcode-generator`
+- `create a qr code for a url` / `create qr code from url` – je 8.100, Position 93/91
+- `qr code generator for url` – 8.100, Position 68
+- `bitly qr` – 2.900, Position 28, `/alternatives/bitly`
+- `flowcode qr` – 2.400, Position 31, `/alternatives/flowcode`
+- `vcard qr generator` – 1.300, Position 31, `/tools/vcard-qr-code`
+- `vcard qr code generator` – 1.300, Position 89
+
+### Wichtigste Wettbewerber
+
+**Deutschland:** qrcode-generator.de, me-qr.com, pageloot.com, qrcodechimp.com, qrcode-tiger.com, qrcodekit.com, the-qrcode-generator.com, qrfy.com, qrcode-monkey.com.
+
+**USA:** qr-code-generator.com, me-qr.com, qrcodechimp.com, the-qrcode-generator.com, qrstuff.com, uniqode.com, scanova.io, viralqr.com.
+
+### Keyword-Gaps / Chancen
+
+Die Domain-Intersection-Abfragen zeigen, dass Wettbewerber unter anderem für folgende relevante Themen ranken, während QRMaster in den abgefragten SERPs nicht gleichzeitig auftaucht:
+
+- `qr code generator`, `generate a qr code`, `qr code builder`
+- `qr codes erstellen`, `qr-code erstellen`, `qr-code erzeugen`
+- `qr code scanner`, `qr code scannen`
+- `qr codes erstellen kostenlos`
+- `dynamic qr code`, `dynamic qr code generator`
+- `qr code tracking`, `qr code analytics`
+- `vcard qr code generator`
+
+### Empfehlung
+
+1. Deutsche, lokalisierte Toolseiten für `QR-Code-Generator`, `dynamischer QR-Code`, `QR-Code-Tracking`, `QR-Code-Scanner` und `QR-Code-Visitenkarte` priorisieren.
+2. Die US-Startseite nicht primär über Barcode- und Wettbewerberbegriffe wachsen lassen; stattdessen klare englische Landingpages für `qr code generator`, `dynamic qr code generator`, `qr code tracking`, `vcard qr code generator` und `bulk qr code generator` stärken.
+3. Jede Toolseite mit direkter Definition, Use Cases, Funktionsvergleich statisch/dynamisch, Tracking-Erklärung, FAQ und starken internen Links ausstatten.
+4. Die bereits auffälligen `/alternatives/bitly`- und `/alternatives/flowcode`-Seiten zu belastbaren Vergleichsseiten mit echten Produktunterschieden ausbauen.
+
+## 2. GreenLens Pro
+
+### USA / Englisch
+
+GreenLens rankt bereits für ein relevantes Pflanzenfoto-/Identifikationsthema, überwiegend über eine Seite:
+
+- `google lens plant identification app` – Volumen 8.100, Position 32, `/plant-identifier-app`
+- `identify a plant by picture` – 8.100, Position 79, `/identify-plant-photo`
+- `identify plant from photo` – 8.100, Position 80
+- `identify plants by picture` – 8.100, Position 55
+- `plant id from photo` – 8.100, Position 55
+- `plant identifier with photo` – 8.100, Position 51
+- `flower picture identifier` – 1.300, Position 74
+- `what plant is this by picture` – 1.000, Position 93
+- `plant finder by photo` – 590, Position 58
+
+### Wichtigste Wettbewerber
+
+picturethisai.com, plantnet.org, plant.id, plantsnap.com, apple.com, myplantin.com, lensapp.io sowie Google, YouTube und Reddit als SERP-/Marken-Ökosystem.
+
+### Keyword-Gaps / Chancen
+
+Die Wettbewerber decken unter anderem ab:
+
+- `plant identification`, `plant identifier`, `plant id`
+- `what type of plant is this`
+- `flower identifier by picture`
+- `plant disease identifier`
+- konkrete Pflanzen- und Symptombegriffe
+
+Die Domain-Intersection-Daten enthalten außerdem viele botanische Informationsbegriffe. Diese sollten nicht ungefiltert als SEO-Ziele übernommen werden: Für GreenLens sind Identifikation, Diagnose und nächste Pflegeschritte wertvoller als reine Pflanzenlexikon-Queries.
+
+### Empfehlung
+
+1. `/identify-plant-photo` als zentrale Money-/Tool-Seite auf Positionen 1–20 entwickeln: klare Antwort, Upload-/Scan-Nutzen, Beispiele, FAQ, App-CTA.
+2. Separat clusterbare Seiten für `plant identifier`, `flower identifier`, `plant disease identifier`, `brown leaves`, `yellow leaves`, `overwatering` und `root rot` aufbauen.
+3. Jede Diagnose-Seite muss sichtbar den nächsten Handlungsschritt erklären; nicht nur Pflanzenwissen liefern.
+4. Interne Links zwischen Identifikation → Diagnose → Pflegeplan → App setzen.
+5. Google-Lens-Vergleich als Trust-/Alternative-Seite nutzen, aber nicht die gesamte Informationsarchitektur auf Wettbewerberbegriffe stützen.
+
+## 3. Entscheidomat
+
+### Deutschland / Deutsch
+
+DataForSEO meldet aktuell **keine Ranked Keywords** für `entscheidomat.com` im deutschen Google-Markt. Das bedeutet nicht automatisch, dass die Website technisch nicht indexiert ist; es bedeutet, dass DataForSEO in diesem Markt keine verwertbaren Ranking-Keywords geliefert hat.
+
+### Wettbewerbs- und Gap-Signale
+
+Vergleichsdaten mit wheelofnames.com, random.org und zufallsgenerator.de zeigen relevante Suchthemen:
+
+- `glücksrad` – Volumen 246.000
+- `zufallsgenerator` – 165.000
+- `würfel` – 90.500
+- `google drehscheibe` – 40.500
+- `wheel of names` – 33.100
+- `spin the wheel` – 33.100
+- `coin flip` – 40.500, englischer/Internationaler Suchbegriff
+
+Ein Teil der Daten von random.org ist für Entscheidomat nicht passend, zum Beispiel Passwortgenerator- und englische Random-Number-Begriffe. Diese wurden als Kontamination verworfen.
+
+### Empfehlung
+
+1. Jede der sieben Funktionen als eigenständige, crawlbare SEO-Landingpage führen: `glücksrad`, `zufallszahl-generator`, `würfel-online`, `münze-werfen`, `ja-nein-generator`, `namen-auslosen`, `magic-8-ball`.
+2. Pro Seite einen sichtbaren Definition-Block direkt unter dem H1 ergänzen.
+3. Suchintention und Tool direkt verbinden: sofort nutzbares Tool plus Erklärung, Beispiele, FAQ und interne Links.
+4. Die Startseite auf `Zufallsgenerator` und `Entscheidungshilfe` fokussieren; nicht alle sieben Begriffe gleich stark in Title/H1 stapeln.
+5. Für `glücksrad`, `zufallsgenerator` und `würfel online` zuerst spezifische SEO- und UX-Landingpages optimieren, weil dort die größte erkennbare Nachfrage liegt.
+
+## Priorisierte Maßnahmen über alle drei Projekte
+
+### Priorität 1 – sofort
+
+- QRMaster: englische und deutsche Kern-Toolseiten auf `qr code generator`, `dynamic qr code`, `qr code tracking` und `QR-Code erstellen` ausrichten.
+- GreenLens: `identify-plant-photo` als zentrale Conversion-/SEO-Seite ausbauen.
+- Entscheidomat: sieben Tools als eindeutige SEO-Seiten mit sichtbarem HTML-Content und sauberem internen Linking absichern.
+
+### Priorität 2 – danach
+
+- Keyword-Mapping erstellen, damit pro Suchintention genau eine kanonische Seite ranken soll.
+- Titles, H1s, Meta-Descriptions und FAQ-Blöcke auf die DataForSEO-Gaps ausrichten.
+- Vergleichsseiten und Use Cases nur dort ausbauen, wo sie einen echten Nutzwert und interne Conversion-Verbindung haben.
+
+### Priorität 3 – Monitoring
+
+- Nach den Änderungen monatlich dieselben DataForSEO-Abfragen wiederholen.
+- Zusätzlich Google Search Console je Domain auswerten; insbesondere bei Entscheidomat ist die Nullmessung ohne GSC nicht ausreichend.
+- Rankings nicht isoliert bewerten: URL, Suchintention, Klicks, Impressions und Conversion gemeinsam messen.
+
+## Technischer Nachweis
+
+Die Live-Abfragen liefen mit HTTP 200 und DataForSEO-Task-Status 20000. Erfolgreich verarbeitet wurden:
+
+- 4 Ranked-Keyword-Abfragen
+- 4 Competitor-Domain-Abfragen
+- 12 Domain-Intersection-Abfragen für Wettbewerber-Gaps
+- 4 Keyword-Metrik-Abfragen für Seed-Cluster
+
+Die Seed-Volume-Antworten wurden von der ersten Auswertung nicht in die Ergebnisliste übernommen, deshalb stammen die im Bericht genannten Volumina aus Ranked-Keyword- und Domain-Intersection-Daten. Die vier Seed-Requests selbst wurden technisch erfolgreich angenommen, werden aber nicht als separate Primärquelle für die genannten Zahlen verwendet.
diff --git a/sql/2026-07-27_APPLY.sql b/sql/2026-07-27_APPLY.sql
new file mode 100644
index 0000000..f1bf784
--- /dev/null
+++ b/sql/2026-07-27_APPLY.sql
@@ -0,0 +1,38 @@
+-- QR Master, Deploy 27.07.2026 - nur die Statements, die ausgefuehrt werden muessen.
+-- Alle idempotent, ein zweiter Lauf schadet nicht.
+-- Danach zwingend: npx prisma generate
+
+BEGIN;
+
+CREATE INDEX IF NOT EXISTS "QRCode_userId_type_status_idx"
+ ON "QRCode" ("userId", "type", "status");
+
+ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "limitReachedNudgeSentAt" TIMESTAMP(3);
+ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "firstScanNudgeSentAt" TIMESTAMP(3);
+
+CREATE INDEX IF NOT EXISTS "User_firstScanAt_firstScanNudgeSentAt_idx"
+ ON "User" ("firstScanAt", "firstScanNudgeSentAt");
+
+-- Schuetzt Bestandsnutzer: ohne diese Zeile geht die "erster Scan"-Mail beim
+-- ersten Cron-Lauf an jeden Nutzer mit Scan-Historie.
+UPDATE "User"
+SET "firstScanNudgeSentAt" = now()
+WHERE "firstScanAt" IS NOT NULL
+ AND "firstScanAt" < now() - interval '7 days';
+
+CREATE TABLE IF NOT EXISTS "QRDesignPreset" (
+ "id" TEXT PRIMARY KEY,
+ "userId" TEXT NOT NULL REFERENCES "User"("id") ON DELETE CASCADE,
+ "name" TEXT NOT NULL,
+ "style" JSONB NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT now(),
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT now()
+);
+
+CREATE INDEX IF NOT EXISTS "QRDesignPreset_userId_idx"
+ ON "QRDesignPreset" ("userId");
+
+CREATE UNIQUE INDEX IF NOT EXISTS "QRDesignPreset_userId_name_key"
+ ON "QRDesignPreset" ("userId", "name");
+
+COMMIT;
diff --git a/sql/2026-07-27_cro_retention_design.txt b/sql/2026-07-27_cro_retention_design.txt
new file mode 100644
index 0000000..854a851
--- /dev/null
+++ b/sql/2026-07-27_cro_retention_design.txt
@@ -0,0 +1,280 @@
+================================================================================
+QR MASTER - SQL-BEFEHLE ZUM UMSETZUNGSPLAN VOM 27.07.2026
+Gehoert zu: PLAN_CRO_UND_RETENTION_2026-07-27.md
+================================================================================
+
+POLICY (aus CLAUDE.md): Keine Prisma-Migrationen. Alle Schemaaenderungen werden
+als rohes SQL direkt gegen die laufende PostgreSQL-Instanz ausgefuehrt. Danach
+prisma/schema.prisma von Hand angleichen und "npx prisma generate" laufen
+lassen - niemals "npx prisma migrate".
+
+AUSFUEHREN ueber:
+ npm run docker:db
+oder einzeln:
+ docker-compose exec db psql -U postgres -d qrmaster -c ""
+
+REIHENFOLGE: Block 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6. Alle Bloecke sind Pflicht.
+Block 5 (prisma generate) darf nicht vergessen werden.
+
+
+================================================================================
+BLOCK 0 - VORHER PRUEFEN (liest nur, aendert nichts)
+================================================================================
+
+-- 0.1 Wie viele FREE-Nutzer bekommen durch die Umstellung auf "nur ACTIVE
+-- zaehlen" rueckwirkend Slots frei? Das ist eine Lockerung, nimmt also
+-- niemandem etwas weg - aber die Zahl sollte man kennen.
+SELECT COUNT(DISTINCT u.id) AS betroffene_free_nutzer
+FROM "User" u
+JOIN "QRCode" q ON q."userId" = u.id
+WHERE u.plan = 'FREE'
+ AND q.type = 'DYNAMIC'
+ AND q.status = 'PAUSED';
+
+-- 0.2 Wie viele Nutzer wuerden die neue "erster Scan"-Mail beim ersten
+-- Cron-Lauf bekommen? WICHTIG: ohne Block 3.2 ginge sie an ALLE
+-- Bestandsnutzer mit Scan-Historie, auch an solche, deren erster Scan
+-- Monate zurueckliegt. Das waere kein Anlass mehr, sondern eine
+-- Massenmail unter deinem Namen.
+SELECT COUNT(*) AS wuerden_mail_bekommen
+FROM "User"
+WHERE "firstScanAt" IS NOT NULL;
+
+-- 0.3 Verteilung der Plaene - Kontext fuer alles Weitere.
+SELECT plan, COUNT(*) AS nutzer
+FROM "User"
+GROUP BY plan
+ORDER BY nutzer DESC;
+
+-- 0.4 Wie viele FREE-Nutzer sitzen aktuell am Limit? Das ist die Zielgruppe
+-- des neuen Limit-Modals aus Phase 1 und der Limit-Mail aus Phase 5.1.
+SELECT COUNT(*) AS free_nutzer_am_limit
+FROM (
+ SELECT u.id
+ FROM "User" u
+ JOIN "QRCode" q ON q."userId" = u.id
+ WHERE u.plan = 'FREE' AND q.type = 'DYNAMIC' AND q.status = 'ACTIVE'
+ GROUP BY u.id
+ HAVING COUNT(q.id) >= 3
+) t;
+
+
+================================================================================
+BLOCK 1 - INDEX FUER DIE NEUE LIMIT-QUERY (Phase 1.1)
+================================================================================
+
+-- Die Zaehl-Query in src/app/(main)/api/qrs/route.ts bekommt zusaetzlich
+-- status = 'ACTIVE'. Dieser Index deckt die neue Bedingung ab.
+-- Dieselbe Aenderung gilt fuer src/app/(main)/api/user/stats/route.ts.
+
+CREATE INDEX IF NOT EXISTS "QRCode_userId_type_status_idx"
+ ON "QRCode" ("userId", "type", "status");
+
+
+================================================================================
+BLOCK 2 - MARKER-SPALTEN FUER DIE NEUEN RETENTION-MAILS (Phase 5)
+================================================================================
+
+-- limitReachedNudgeSentAt: Marker fuer die verhaltensbasierte Limit-Mail.
+-- Ersetzt die kalenderbasierte Tag-7-Mail, die heute auch an Nutzer geht,
+-- die das Limit gar nicht erreicht haben.
+-- firstScanNudgeSentAt: Marker fuer die neue "erster Scan"-Mail.
+-- Die Erkennung selbst braucht nichts Neues - User."firstScanAt" existiert
+-- bereits und wird in src/app/(main)/r/[slug]/route.ts gesetzt.
+
+ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "limitReachedNudgeSentAt" TIMESTAMP(3);
+ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "firstScanNudgeSentAt" TIMESTAMP(3);
+
+-- Index fuer den Cron-Job, der Nutzer mit erstem Scan ohne Versandmarker sucht.
+CREATE INDEX IF NOT EXISTS "User_firstScanAt_firstScanNudgeSentAt_idx"
+ ON "User" ("firstScanAt", "firstScanNudgeSentAt");
+
+
+================================================================================
+BLOCK 3 - BESTANDSDATEN VORBEREITEN (unbedingt VOR dem ersten Cron-Lauf)
+================================================================================
+
+-- 3.1 Nutzer, die schon am Limit sitzen, haben die Limit-Mail nie bekommen
+-- koennen - es gab sie nicht. Ob sie sie nachtraeglich bekommen sollen,
+-- ist eine Entscheidung:
+--
+-- Variante A - sie sollen die Mail bekommen: dieses Statement NICHT
+-- ausfuehren. Der erste Cron-Lauf schickt sie an alle, die am Limit sind.
+-- Bei vielen Bestandsnutzern ist das ein Versand-Peak.
+--
+-- Variante B - nur Neufaelle ab jetzt: Statement ausfuehren, dann bekommen
+-- bestehende Limit-Faelle keine Mail und die Sequenz startet sauber.
+
+-- Variante B (auskommentiert - bewusst entscheiden und dann aktivieren):
+-- UPDATE "User" u
+-- SET "limitReachedNudgeSentAt" = now()
+-- WHERE u.plan = 'FREE'
+-- AND (SELECT COUNT(*) FROM "QRCode" q
+-- WHERE q."userId" = u.id AND q.type = 'DYNAMIC' AND q.status = 'ACTIVE') >= 3;
+
+-- 3.2 PFLICHT: Bestandsnutzer, deren erster Scan laenger als 7 Tage her ist,
+-- als "bereits benachrichtigt" markieren. Ohne dieses Statement geht die
+-- "erster Scan"-Mail beim ersten Lauf an die gesamte Bestandsbasis - mit
+-- einem Anlass, der Monate zurueckliegt.
+UPDATE "User"
+SET "firstScanNudgeSentAt" = now()
+WHERE "firstScanAt" IS NOT NULL
+ AND "firstScanAt" < now() - interval '7 days';
+
+-- 3.3 Kontrolle nach 3.2 - sollte eine kleine, plausible Zahl sein.
+SELECT COUNT(*) AS offene_erster_scan_mails
+FROM "User"
+WHERE "firstScanAt" IS NOT NULL
+ AND "firstScanNudgeSentAt" IS NULL;
+
+
+================================================================================
+BLOCK 4 - PFLICHT: DESIGN-VORLAGEN
+================================================================================
+
+-- Nicht mehr optional: die Design-Presets sind gebaut. Ohne diese Tabelle
+-- laufen GET/POST/DELETE /api/design-presets und die Preset-Auswahl im
+-- Bulk-Flow in einen Prisma-Fehler.
+--
+-- Die Formen selbst brauchen weiterhin keine Schemaaenderung: QRCode."style"
+-- ist JSON, moduleShape, eyeFrameShape, eyeBallShape, gradientMode und
+-- gradientTo liegen dort ohne Migration drin.
+
+CREATE TABLE IF NOT EXISTS "QRDesignPreset" (
+ "id" TEXT PRIMARY KEY,
+ "userId" TEXT NOT NULL REFERENCES "User"("id") ON DELETE CASCADE,
+ "name" TEXT NOT NULL,
+ "style" JSONB NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT now(),
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT now()
+);
+
+CREATE INDEX IF NOT EXISTS "QRDesignPreset_userId_idx"
+ ON "QRDesignPreset" ("userId");
+
+CREATE UNIQUE INDEX IF NOT EXISTS "QRDesignPreset_userId_name_key"
+ ON "QRDesignPreset" ("userId", "name");
+
+
+================================================================================
+BLOCK 5 - PRISMA-SCHEMA VON HAND ANGLEICHEN (kein SQL, aber Pflichtschritt)
+================================================================================
+
+In prisma/schema.prisma, model User, im Block "// Retention email tracking"
+ergaenzen:
+
+ limitReachedNudgeSentAt DateTime?
+ firstScanNudgeSentAt DateTime?
+
+In model QRCode ergaenzen:
+
+ @@index([userId, type, status])
+
+Ausserdem (Block 4 ist Pflicht) - beides ist im Repo bereits eingetragen,
+diese Angabe dient nur der Kontrolle:
+
+ model QRDesignPreset {
+ id String @id @default(cuid())
+ userId String
+ name String
+ style Json
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ @@unique([userId, name])
+ @@index([userId])
+ }
+
+und in model User die Gegenseite:
+
+ designPresets QRDesignPreset[]
+
+Danach:
+
+ npx prisma generate
+
+NICHT "npx prisma migrate" - das wuerde gegen die Policy in CLAUDE.md verstossen.
+
+
+================================================================================
+BLOCK 6 - VERIFIKATION NACH DEM DEPLOY
+================================================================================
+
+-- 6.1 Sind alle neuen Spalten da?
+SELECT column_name, data_type
+FROM information_schema.columns
+WHERE table_name = 'User'
+ AND column_name IN ('limitReachedNudgeSentAt', 'firstScanNudgeSentAt',
+ 'activationNudgeSentAt', 'upgradeNudgeSentAt',
+ 'thirtyDayNudgeSentAt', 'firstScanAt')
+ORDER BY column_name;
+
+-- 6.2 Sind alle neuen Indizes da?
+SELECT indexname
+FROM pg_indexes
+WHERE tablename IN ('User', 'QRCode', 'QRDesignPreset')
+ AND indexname IN ('QRCode_userId_type_status_idx',
+ 'User_firstScanAt_firstScanNudgeSentAt_idx',
+ 'QRDesignPreset_userId_idx',
+ 'QRDesignPreset_userId_name_key')
+ORDER BY indexname;
+
+-- 6.3 Nutzt die neue Limit-Query den Index? Sollte "Index Scan" oder
+-- "Index Only Scan" zeigen, keinen "Seq Scan".
+EXPLAIN ANALYZE
+SELECT COUNT(*) FROM "QRCode"
+WHERE "userId" = (SELECT id FROM "User" LIMIT 1)
+ AND type = 'DYNAMIC'
+ AND status = 'ACTIVE';
+
+-- 6.4 Wie viele Mails stehen im naechsten Cron-Lauf an? Vor dem ersten
+-- scharfen Lauf pruefen, damit es keine Ueberraschung gibt.
+SELECT
+ (SELECT COUNT(*) FROM "User"
+ WHERE "firstScanAt" IS NOT NULL AND "firstScanNudgeSentAt" IS NULL)
+ AS erster_scan_mails,
+ (SELECT COUNT(*) FROM "User" u WHERE u.plan = 'FREE'
+ AND u."limitReachedNudgeSentAt" IS NULL
+ AND (SELECT COUNT(*) FROM "QRCode" q
+ WHERE q."userId" = u.id AND q.type = 'DYNAMIC' AND q.status = 'ACTIVE') >= 3)
+ AS limit_mails,
+ (SELECT COUNT(*) FROM "User"
+ WHERE "activationNudgeSentAt" IS NULL
+ AND "createdAt" < now() - interval '3 days')
+ AS aktivierungs_mails;
+
+
+================================================================================
+ROLLBACK - falls etwas zurueckgedreht werden muss
+================================================================================
+
+-- Die Spalten sind additiv und nullable, ein Rollback ist normalerweise nicht
+-- noetig. Falls doch: Datenverlust bei den Versandmarkern beachten - danach
+-- koennten Nutzer Mails ein zweites Mal bekommen.
+
+-- ALTER TABLE "User" DROP COLUMN IF EXISTS "limitReachedNudgeSentAt";
+-- ALTER TABLE "User" DROP COLUMN IF EXISTS "firstScanNudgeSentAt";
+-- DROP INDEX IF EXISTS "User_firstScanAt_firstScanNudgeSentAt_idx";
+-- DROP INDEX IF EXISTS "QRCode_userId_type_status_idx";
+-- DROP TABLE IF EXISTS "QRDesignPreset";
+
+
+================================================================================
+ZUSAMMENFASSUNG
+================================================================================
+
+Pflicht: 2 Spalten, 2 Indizes, 1 UPDATE fuer Bestandsdaten (Block 3.2)
+Pflicht: 1 Tabelle mit 2 Indizes (Design-Presets, Block 4)
+Formen: brauchen kein SQL - QRCode."style" ist bereits JSON
+Phasen 1-4: brauchen kein SQL ausser dem Index aus Block 1
+
+NACH dem SQL zwingend: npx prisma generate
+Ohne generate kennt der Prisma-Client das Modell QRDesignPreset nicht und
+/api/design-presets wirft zur Laufzeit.
+
+Der einzige Schritt mit echtem Risiko ist Block 3.2. Wird er vergessen, geht
+die "erster Scan"-Mail an die gesamte Bestandsbasis.
+
+================================================================================
diff --git a/src/app/(main)/(app)/AppLayout.tsx b/src/app/(main)/(app)/AppLayout.tsx
index e54f07b..701f8ed 100644
--- a/src/app/(main)/(app)/AppLayout.tsx
+++ b/src/app/(main)/(app)/AppLayout.tsx
@@ -130,7 +130,7 @@ export default function AppLayout({
},
{
name: t('nav.pricing'),
- href: '/pricing',
+ href: '/upgrade',
icon: (
@@ -164,11 +164,11 @@ export default function AppLayout({
className={`fixed top-0 left-0 z-50 h-full w-64 bg-white border-r border-gray-200 transform transition-transform lg:translate-x-0 ${sidebarOpen ? 'translate-x-0' : '-translate-x-full'
}`}
>
-
-
-
-
QR Master
-
+
+
+
+
QR Master
+
setSidebarOpen(false)}
@@ -200,10 +200,10 @@ export default function AppLayout({
{/* Main content */}
-
- {/* Top bar */}
-
-
+
+ {/* Top bar */}
+
+
setSidebarOpen(true)}
@@ -213,24 +213,24 @@ export default function AppLayout({
-
- {/* User Menu */}
-
-
-
- {getUserInitials()}
-
-
-
- {getDisplayName()}
-
-
-
-
-
+
+ {/* User Menu */}
+
+
+
+ {getUserInitials()}
+
+
+
+ {getDisplayName()}
+
+
+
+
+
}
>
@@ -242,13 +242,13 @@ export default function AppLayout({
{/* Page content */}
-
- {children}
-
+
+ {children}
+
{/* Footer */}
);
-}
+}
diff --git a/src/app/(main)/(app)/bulk-creation/page.tsx b/src/app/(main)/(app)/bulk-creation/page.tsx
index b1b97c2..b71687a 100644
--- a/src/app/(main)/(app)/bulk-creation/page.tsx
+++ b/src/app/(main)/(app)/bulk-creation/page.tsx
@@ -1,6 +1,7 @@
'use client';
import React, { useState, useCallback } from 'react';
+import Link from 'next/link';
import { useDropzone } from 'react-dropzone';
import Papa from 'papaparse';
import ExcelJS from 'exceljs';
@@ -8,7 +9,8 @@ import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { Select } from '@/components/ui/Select';
-import { QRCodeSVG } from 'qrcode.react';
+import { QRCodeSVG } from 'qrcode.react';
+import { renderStyledQRSvg } from '@/lib/render-qr-svg';
import { showToast } from '@/components/ui/Toast';
import { useTranslation } from '@/hooks/useTranslation';
import { useCsrf } from '@/hooks/useCsrf';
@@ -39,6 +41,38 @@ export default function BulkCreationPage() {
const [userPlan, setUserPlan] = useState
('FREE');
const [isDynamic, setIsDynamic] = useState(false);
const [remainingDynamic, setRemainingDynamic] = useState(0);
+ // Rows the API refused. Previously these vanished silently and the success
+ // toast reported a smaller number with no explanation - the worst kind of
+ // failure for someone who is about to send a batch to print.
+ const [failedRows, setFailedRows] = useState<{ row: number; title: string; reason: string }[]>([]);
+ // A saved design applied to the whole batch. This is the reason presets exist:
+ // 500 codes that all look like the same client, from one upload.
+ const [presets, setPresets] = useState<{ id: string; name: string; style: any }[]>([]);
+ const [presetId, setPresetId] = useState('');
+
+ // Reload the remaining dynamic quota from the server. Counting down locally
+ // drifts as soon as anything is created in another tab, which is how rows
+ // ended up being refused mid-batch in the first place.
+ const refreshQuota = async () => {
+ try {
+ const statsRes = await fetch('/api/user/stats');
+ if (statsRes.ok) {
+ const stats = await statsRes.json();
+ setRemainingDynamic((stats.dynamicLimit || 0) - (stats.dynamicUsed || 0));
+ }
+ } catch (error) {
+ console.error('Error refreshing quota:', error);
+ }
+ };
+
+ React.useEffect(() => {
+ fetch('/api/design-presets')
+ .then((r) => (r.ok ? r.json() : []))
+ .then((d) => Array.isArray(d) && setPresets(d))
+ .catch(() => {});
+ }, []);
+
+ const activeStyle = () => presets.find((p) => p.id === presetId)?.style ?? null;
// Check user plan and dynamic quota on mount
React.useEffect(() => {
@@ -177,15 +211,15 @@ export default function BulkCreationPage() {
// Use qrcode library to generate SVG
const QRCode = require('qrcode');
- const qrSvg = await QRCode.toString(content, {
- type: 'svg',
- width: 300,
- margin: 2,
- color: {
- dark: '#000000',
- light: '#FFFFFF',
- },
- });
+ const style = activeStyle();
+ const qrSvg = style
+ ? renderStyledQRSvg(String(content), style, 300)
+ : await QRCode.toString(content, {
+ type: 'svg',
+ width: 300,
+ margin: 2,
+ color: { dark: '#000000', light: '#FFFFFF' },
+ });
qrCodes.push({
title: String(title),
@@ -212,20 +246,35 @@ export default function BulkCreationPage() {
const toProcess = remainingDynamic > 0 ? data.slice(0, remainingDynamic) : [];
if (toProcess.length === 0) {
- showToast('Du hast keine dynamischen QR-Codes mehr übrig. Bitte upgrade deinen Plan.', 'error');
+ showToast('No dynamic QR codes left on your plan. Free a slot or upgrade to continue.', 'error');
setLoading(false);
return;
}
if (data.length > remainingDynamic) {
- showToast(`Nur ${remainingDynamic} dynamische Codes verfügbar. Es werden nur die ersten ${remainingDynamic} Zeilen verarbeitet.`, 'warning');
+ showToast(
+ `Only ${remainingDynamic} dynamic codes left. The first ${remainingDynamic} rows will be processed - the rest are listed after the run.`,
+ 'warning'
+ );
}
try {
const QRCode = require('qrcode');
const results: GeneratedQR[] = [];
+ const failures: { row: number; title: string; reason: string }[] = [];
- for (const row of toProcess) {
+ // Rows the plan could not cover are counted from the start, so the summary
+ // reflects the whole upload and not just the slice we attempted.
+ data.slice(toProcess.length).forEach((row, i) => {
+ failures.push({
+ row: toProcess.length + i + 1,
+ title: String(row[mapping.title as keyof typeof row] || 'Untitled'),
+ reason: 'No dynamic code slots left on your plan',
+ });
+ });
+
+ for (let i = 0; i < toProcess.length; i++) {
+ const row = toProcess[i];
const title = String(row[mapping.title as keyof typeof row] || 'Untitled');
const url = String(row[mapping.content as keyof typeof row] || 'https://example.com');
@@ -242,23 +291,61 @@ export default function BulkCreationPage() {
if (res.ok) {
const qr = await res.json();
const redirectUrl = `${window.location.origin}/r/${qr.slug}`;
- const svg = await QRCode.toString(redirectUrl, { type: 'svg', width: 300, margin: 2 });
+ const style = activeStyle();
+ const svg = style
+ ? renderStyledQRSvg(redirectUrl, style, 300)
+ : await QRCode.toString(redirectUrl, { type: 'svg', width: 300, margin: 2 });
results.push({ title, content: url, svg, slug: qr.slug, redirectUrl });
+ } else {
+ const err = await res.json().catch(() => null);
+ failures.push({
+ row: i + 1,
+ title,
+ reason: err?.error === 'Limit reached'
+ ? 'No dynamic code slots left on your plan'
+ : err?.error || `Request failed (${res.status})`,
+ });
}
}
setGeneratedQRs(results);
- setRemainingDynamic(prev => Math.max(0, prev - results.length));
+ setFailedRows(failures);
+ await refreshQuota();
setStep('complete');
- showToast(`${results.length} dynamische QR-Codes erstellt!`, 'success');
+
+ if (failures.length > 0) {
+ showToast(
+ `${results.length} of ${data.length} codes created. ${failures.length} row${failures.length === 1 ? '' : 's'} could not be added - see the list below.`,
+ 'warning'
+ );
+ } else {
+ showToast(`${results.length} dynamic QR codes created.`, 'success');
+ }
} catch (error) {
console.error('Dynamic QR generation error:', error);
- showToast('Fehler beim Erstellen der dynamischen QR-Codes', 'error');
+ showToast('Something went wrong while creating the dynamic QR codes.', 'error');
} finally {
setLoading(false);
}
};
+ // Hands the user back exactly the rows that did not make it, in a format they
+ // can re-upload once they have room. Telling someone what is missing without
+ // giving them the list is only half an apology.
+ const downloadFailedRowsCsv = () => {
+ const header = 'row,title,reason\n';
+ const body = failedRows
+ .map(f => `${f.row},"${f.title.replace(/"/g, '""')}","${f.reason.replace(/"/g, '""')}"`)
+ .join('\n');
+ const blob = new Blob([header + body], { type: 'text/csv;charset=utf-8;' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'qrmaster-missing-rows.csv';
+ a.click();
+ URL.revokeObjectURL(url);
+ };
+
const downloadAllQRCodes = async () => {
const zip = new JSZip();
@@ -370,7 +457,7 @@ export default function BulkCreationPage() {
window.location.href = '/dashboard'}>
Back to Dashboard
- window.location.href = '/pricing'}>
+ window.location.href = '/upgrade?reason=bulk&from=/bulk-creation'}>
Upgrade to Business
@@ -386,6 +473,26 @@ export default function BulkCreationPage() {
{t('bulk.title')}
{t('bulk.subtitle')}
+ {/* Apply a saved design to the whole batch. */}
+ {presets.length > 0 && (
+
+
+ Design preset
+
+
setPresetId(e.target.value)}
+ options={[
+ { value: '', label: 'Plain black and white' },
+ ...presets.map((p) => ({ value: p.id, label: p.name })),
+ ]}
+ />
+
+ Applied to every code in this upload, so the whole batch matches.
+
+
+ )}
+
{/* Static / Dynamic Toggle */}
QR Code Type:
@@ -772,11 +879,54 @@ export default function BulkCreationPage() {
- Generation Complete!
+
+ {failedRows.length > 0
+ ? `${generatedQRs.length} of ${generatedQRs.length + failedRows.length} codes created`
+ : 'Generation complete'}
+
- Successfully generated {generatedQRs.length} static QR codes
+ {failedRows.length > 0
+ ? 'The rows below could not be added. Nothing was silently dropped - here is exactly what is missing.'
+ : `${generatedQRs.length} ${isDynamic ? 'dynamic' : 'static'} QR codes, ready to download.`}
+ {failedRows.length > 0 && (
+
+
+
+ {failedRows.length} row{failedRows.length === 1 ? '' : 's'} not created
+
+
+ Check these before you send anything to print.
+
+
+
+ {failedRows.slice(0, 50).map((f) => (
+
+
+ Row {f.row} {' '}
+ {f.title}
+
+ {f.reason}
+
+ ))}
+
+ {failedRows.length > 50 && (
+
+ and {failedRows.length - 50} more - download the list to see all of them.
+
+ )}
+
+
+ Download missing rows as CSV
+
+
+ Raise my limit
+
+
+
+ )}
+
{generatedQRs.slice(0, 8).map((qr, index) => (
diff --git a/src/app/(main)/(app)/create/page.tsx b/src/app/(main)/(app)/create/page.tsx
index c9f1046..a7de1c0 100644
--- a/src/app/(main)/(app)/create/page.tsx
+++ b/src/app/(main)/(app)/create/page.tsx
@@ -1,9 +1,20 @@
'use client';
-import React, { useState, useEffect, useRef } from 'react';
-import Link from 'next/link';
-import { useRouter, useSearchParams } from 'next/navigation';
-import { QRCodeSVG } from 'qrcode.react';
+import React, { useState, useEffect, useRef } from 'react';
+import { useRouter, useSearchParams } from 'next/navigation';
+import StyledQRCode from '@/components/generator/StyledQRCode';
+import { renderStyledQRSvg } from '@/lib/render-qr-svg';
+import {
+ ModuleShape,
+ EyeFrameShape,
+ EyeBallShape,
+ PRO_MODULE_SHAPES,
+ BUSINESS_MODULE_SHAPES,
+ LOW_COVERAGE_SHAPES,
+ MODULE_SHAPE_LABELS,
+ EYE_FRAME_LABELS,
+ EYE_BALL_LABELS,
+} from '@/lib/qr-shapes';
import { toPng } from 'html-to-image';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';
@@ -11,18 +22,22 @@ import { Select } from '@/components/ui/Select';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { calculateContrast, cn } from '@/lib/utils';
-import { useTranslation } from '@/hooks/useTranslation';
-import { useCsrf } from '@/hooks/useCsrf';
-import { showToast } from '@/components/ui/Toast';
-import { trackEvent } from '@/components/PostHogProvider';
-import { appendRedirectParam, sanitizeRedirectPath } from '@/lib/auth-flow';
-import {
- ONBOARDING_DOWNLOAD_COMPLETE_EVENT,
- ONBOARDING_DOWNLOAD_COMPLETE_KEY,
-} from '@/lib/revops';
-import {
- Globe, User, MapPin, Phone, FileText, Smartphone, Ticket, Star, HelpCircle, Upload, Barcode as BarcodeIcon
-} from 'lucide-react';
+import { useTranslation } from '@/hooks/useTranslation';
+import { useCsrf } from '@/hooks/useCsrf';
+import { showToast } from '@/components/ui/Toast';
+import { trackEvent } from '@/components/PostHogProvider';
+import { appendRedirectParam, sanitizeRedirectPath } from '@/lib/auth-flow';
+import UpgradeModal, {
+ UpgradeReason,
+ ActiveCodeSummary,
+} from '@/components/app/UpgradeModal';
+import {
+ ONBOARDING_DOWNLOAD_COMPLETE_EVENT,
+ ONBOARDING_DOWNLOAD_COMPLETE_KEY,
+} from '@/lib/revops';
+import {
+ Globe, User, MapPin, Phone, FileText, Smartphone, Ticket, Star, HelpCircle, Upload, Barcode as BarcodeIcon
+} from 'lucide-react';
import Barcode from 'react-barcode';
// Tooltip component for form field help
@@ -105,10 +120,10 @@ function addBarcodeCaptionToSvg(svgElement: SVGElement, caption: string): string
return new XMLSerializer().serializeToString(cloned);
}
-export default function CreatePage() {
- const router = useRouter();
- const searchParams = useSearchParams();
- const { t } = useTranslation();
+export default function CreatePage() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const { t } = useTranslation();
const { fetchWithCsrf } = useCsrf();
const [loading, setLoading] = useState(false);
const [uploading, setUploading] = useState(false);
@@ -126,7 +141,21 @@ export default function CreatePage() {
const [backgroundColor, setBackgroundColor] = useState('#FFFFFF');
const [cornerStyle, setCornerStyle] = useState('square');
const [size, setSize] = useState(200);
- const [frameType, setFrameType] = useState('none');
+ const [frameType, setFrameType] = useState('none');
+ const [moduleShape, setModuleShape] = useState
('square');
+ const [eyeFrameShape, setEyeFrameShape] = useState('square');
+ const [eyeBallShape, setEyeBallShape] = useState('square');
+ const [gradientMode, setGradientMode] = useState<'none' | 'linear' | 'radial'>('none');
+ const [gradientTo, setGradientTo] = useState('#7C3AED');
+ const [presets, setPresets] = useState<{ id: string; name: string; style: any }[]>([]);
+ const [presetName, setPresetName] = useState('');
+
+ // Upgrade modal. Replaces the old redirect to /pricing, which destroyed the
+ // form state at the exact moment purchase intent was highest.
+ const [upgradeOpen, setUpgradeOpen] = useState(false);
+ const [upgradeReason, setUpgradeReason] = useState('limit');
+ const [limitInfo, setLimitInfo] = useState<{ current: number; limit: number } | null>(null);
+ const [activeCodes, setActiveCodes] = useState([]);
// Get frame options for current content type
const frameOptions = getFrameOptionsForContentType(contentType);
@@ -152,23 +181,31 @@ export default function CreatePage() {
const [excavate, setExcavate] = useState(true);
// QR preview
- const [qrDataUrl, setQrDataUrl] = useState('');
-
- const markDownloadComplete = () => {
- if (typeof window === 'undefined') {
- return;
- }
-
- localStorage.setItem(ONBOARDING_DOWNLOAD_COMPLETE_KEY, '1');
- window.dispatchEvent(new CustomEvent(ONBOARDING_DOWNLOAD_COMPLETE_EVENT));
- };
+ const [qrDataUrl, setQrDataUrl] = useState('');
- // Check if user can customize colors (PRO+ only)
- const canCustomizeColors = userPlan === 'PRO' || userPlan === 'BUSINESS';
+ const markDownloadComplete = () => {
+ if (typeof window === 'undefined') {
+ return;
+ }
+
+ localStorage.setItem(ONBOARDING_DOWNLOAD_COMPLETE_KEY, '1');
+ window.dispatchEvent(new CustomEvent(ONBOARDING_DOWNLOAD_COMPLETE_EVENT));
+ };
+
+ // Design gating by plan.
+ // Colors are free for everyone - a QR code the user cannot color reads as a
+ // basic utility, and that judgement carries into every comparison they make.
+ const canCustomizeColors = true;
+ // Module shapes and eye styles are the PRO driver that replaced colors.
+ const canUseShapes = userPlan === 'PRO' || userPlan === 'BUSINESS';
+ // Logo stays PRO.
+ const canUseLogo = userPlan === 'PRO' || userPlan === 'BUSINESS';
+ // Gradients, frames with labels, logo shapes and the exotic module shapes.
+ const canUseFullDesign = userPlan === 'BUSINESS';
// Load user plan
- useEffect(() => {
- const fetchUserPlan = async () => {
+ useEffect(() => {
+ const fetchUserPlan = async () => {
try {
const response = await fetch('/api/user/plan');
if (response.ok) {
@@ -179,44 +216,44 @@ export default function CreatePage() {
console.error('Error fetching user plan:', error);
}
};
- fetchUserPlan();
- }, []);
-
- useEffect(() => {
- const queryContentType = searchParams.get('contentType');
- const useCase = searchParams.get('useCase');
- const titleParam = searchParams.get('title');
- const isDynamicParam = searchParams.get('dynamic');
-
- if (queryContentType) {
- setContentType(queryContentType);
- }
-
- if (titleParam) {
- setTitle(titleParam);
- }
-
- if (isDynamicParam) {
- setIsDynamic(isDynamicParam === '1');
- }
-
- if (useCase === 'menu_pdf') {
- setContent((prev: any) => ({ ...prev, fileUrl: prev.fileUrl || '' }));
- } else if (useCase === 'contact_card') {
- setContent((prev: any) => ({
- ...prev,
- firstName: prev.firstName || '',
- lastName: prev.lastName || '',
- }));
- } else if (useCase === 'barcode') {
- setContent((prev: any) => ({
- ...prev,
- format: prev.format || 'CODE128',
- }));
- } else if (queryContentType === 'URL') {
- setContent((prev: any) => ({ ...prev, url: prev.url || '' }));
- }
- }, [searchParams]);
+ fetchUserPlan();
+ }, []);
+
+ useEffect(() => {
+ const queryContentType = searchParams.get('contentType');
+ const useCase = searchParams.get('useCase');
+ const titleParam = searchParams.get('title');
+ const isDynamicParam = searchParams.get('dynamic');
+
+ if (queryContentType) {
+ setContentType(queryContentType);
+ }
+
+ if (titleParam) {
+ setTitle(titleParam);
+ }
+
+ if (isDynamicParam) {
+ setIsDynamic(isDynamicParam === '1');
+ }
+
+ if (useCase === 'menu_pdf') {
+ setContent((prev: any) => ({ ...prev, fileUrl: prev.fileUrl || '' }));
+ } else if (useCase === 'contact_card') {
+ setContent((prev: any) => ({
+ ...prev,
+ firstName: prev.firstName || '',
+ lastName: prev.lastName || '',
+ }));
+ } else if (useCase === 'barcode') {
+ setContent((prev: any) => ({
+ ...prev,
+ format: prev.format || 'CODE128',
+ }));
+ } else if (queryContentType === 'URL') {
+ setContent((prev: any) => ({ ...prev, url: prev.url || '' }));
+ }
+ }, [searchParams]);
const contrast = calculateContrast(foregroundColor, backgroundColor);
const hasGoodContrast = contrast >= 4.5;
@@ -268,8 +305,8 @@ export default function CreatePage() {
}
};
- const qrContent = getQRContent();
- const previewScale = contentType === 'BARCODE' ? 1 : Math.min(1, 240 / Math.max(size, 1));
+ const qrContent = getQRContent();
+ const previewScale = contentType === 'BARCODE' ? 1 : Math.min(1, 240 / Math.max(size, 1));
const getFrameLabel = () => {
const frame = frameOptions.find((f: { id: string; label: string }) => f.id === frameType);
@@ -279,71 +316,94 @@ export default function CreatePage() {
const downloadQR = async (format: 'svg' | 'png') => {
if (!qrRef.current) return;
try {
- if (format === 'png') {
- const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3, backgroundColor: 'transparent' });
- const link = document.createElement('a');
- link.download = `qrcode-${title || 'download'}.png`;
- link.href = dataUrl;
- link.click();
- markDownloadComplete();
- trackEvent('qr_code_downloaded', {
- format: 'png',
- content_type: contentType,
- qr_type: isDynamic ? 'dynamic' : 'static',
- plan: userPlan,
- });
- } else {
- // For SVG, we might still want to use the library or just toPng if SVG export of HTML is not needed
- // Simplest is to check if we can export the SVG element directly but that misses the frame HTML.
- // html-to-image can generate SVG too.
- // But usually for SVG users want the vector. Capturing HTML to SVG is possible but complex.
- // For now, let's just stick to the SVG code export if NO FRAME is selected,
- // otherwise warn or use toPng (as SVG).
- // Actually, the previous implementation was good for pure QR.
- // If frame is selected, we MUST use a raster export (PNG) or complex HTML-to-SVG.
- // Let's rely on toPng for consistency with frames.
+ // Unframed codes are re-rendered from the design rather than captured
+ // from the DOM. The on-screen preview is drawn with margin 0 because the
+ // container supplies the visual padding, but a downloaded file needs the
+ // 4-module quiet zone the spec requires - without it a code printed next
+ // to other artwork often will not scan. This also makes the file from
+ // here byte-identical to the one the dashboard produces.
+ if (format === 'png' && frameType === 'none' && contentType !== 'BARCODE') {
+ const svg = renderStyledQRSvg(qrContent, currentDesign(), 1024);
+ const img = new Image();
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ canvas.width = 1024;
+ canvas.height = 1024;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+ ctx.drawImage(img, 0, 0, 1024, 1024);
+ const link = document.createElement('a');
+ link.download = `qrcode-${title || 'download'}.png`;
+ link.href = canvas.toDataURL('image/png');
+ link.click();
+ markDownloadComplete();
+ trackEvent('qr_code_downloaded', {
+ format: 'png',
+ content_type: contentType,
+ qr_type: isDynamic ? 'dynamic' : 'static',
+ plan: userPlan,
+ });
+ };
+ img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
+ return;
+ }
+
+ if (format === 'png') {
const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3, backgroundColor: 'transparent' });
- // Wait, exporting HTML to valid vector SVG is hard.
- // Let's just offer PNG for frames for now to be safe, or just use the same PNG download for both buttons if frame is active?
- // No, let's try to grab the INNER SVG if no frame, else...
+ const link = document.createElement('a');
+ link.download = `qrcode-${title || 'download'}.png`;
+ link.href = dataUrl;
+ link.click();
+ markDownloadComplete();
+ trackEvent('qr_code_downloaded', {
+ format: 'png',
+ content_type: contentType,
+ qr_type: isDynamic ? 'dynamic' : 'static',
+ plan: userPlan,
+ });
+ } else {
+ // Without a frame the preview already is the finished vector, so the SVG
+ // download is a serialisation of what is on screen. With a frame the
+ // surrounding markup is HTML, which has no faithful vector equivalent -
+ // that case falls back to PNG and says so.
if (frameType === 'none') {
const svgElement = qrRef.current.querySelector('svg');
if (svgElement) {
const svgData = contentType === 'BARCODE'
? addBarcodeCaptionToSvg(svgElement, 'Scan: iPhone -> Barcode Scanner App | Android -> Google Lens')
- : new XMLSerializer().serializeToString(svgElement);
+ : renderStyledQRSvg(qrContent, currentDesign(), 512);
const blob = new Blob([svgData], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = `qrcode-${title || 'download'}.svg`;
- a.click();
- URL.revokeObjectURL(url);
- markDownloadComplete();
- trackEvent('qr_code_downloaded', {
- format: 'svg',
- content_type: contentType,
- qr_type: isDynamic ? 'dynamic' : 'static',
- plan: userPlan,
- });
- }
- } else {
- showToast('SVG download not available with frames yet. Downloading PNG instead.', 'info');
- const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3, backgroundColor: 'transparent' });
- const link = document.createElement('a');
- link.download = `qrcode-${title || 'download'}.png`;
- link.href = dataUrl;
- link.click();
- markDownloadComplete();
- trackEvent('qr_code_downloaded', {
- format: 'png',
- content_type: contentType,
- qr_type: isDynamic ? 'dynamic' : 'static',
- plan: userPlan,
- fallback_from: 'svg_with_frame',
- });
- }
- }
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `qrcode-${title || 'download'}.svg`;
+ a.click();
+ URL.revokeObjectURL(url);
+ markDownloadComplete();
+ trackEvent('qr_code_downloaded', {
+ format: 'svg',
+ content_type: contentType,
+ qr_type: isDynamic ? 'dynamic' : 'static',
+ plan: userPlan,
+ });
+ }
+ } else {
+ showToast('SVG download not available with frames yet. Downloading PNG instead.', 'info');
+ const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3, backgroundColor: 'transparent' });
+ const link = document.createElement('a');
+ link.download = `qrcode-${title || 'download'}.png`;
+ link.href = dataUrl;
+ link.click();
+ markDownloadComplete();
+ trackEvent('qr_code_downloaded', {
+ format: 'png',
+ content_type: contentType,
+ qr_type: isDynamic ? 'dynamic' : 'static',
+ plan: userPlan,
+ fallback_from: 'svg_with_frame',
+ });
+ }
+ }
} catch (err) {
console.error('Error downloading QR code:', err);
showToast('Error downloading QR code', 'error');
@@ -392,6 +452,126 @@ export default function CreatePage() {
}
};
+ // Load the user's active dynamic codes so the limit modal can offer a way out
+ // that does not cost money. Silent failure is fine here - the modal simply
+ // hides the pause option if this does not come back.
+ const loadActiveCodes = async () => {
+ try {
+ const res = await fetch('/api/qrs');
+ if (!res.ok) return;
+ const all = await res.json();
+ setActiveCodes(
+ (Array.isArray(all) ? all : [])
+ .filter((qr: any) => qr.type === 'DYNAMIC' && qr.status === 'ACTIVE')
+ .map((qr: any) => ({
+ id: qr.id,
+ title: qr.title || 'Untitled',
+ scans30d: qr.scans30d ?? 0,
+ }))
+ );
+ } catch {
+ // ignore
+ }
+ };
+
+ const handlePauseCode = async (id: string) => {
+ const res = await fetchWithCsrf(`/api/qrs/${id}`, {
+ method: 'PATCH',
+ body: JSON.stringify({ status: 'PAUSED' }),
+ });
+ if (!res.ok) {
+ const data = await res.json().catch(() => null);
+ throw new Error(data?.error || 'Could not pause that code.');
+ }
+ trackEvent('dynamic_code_paused_for_slot', { qr_id: id });
+ setUpgradeOpen(false);
+ showToast('Slot freed. Saving your code now.', 'success');
+ // The form state was never lost, so the original save just runs again.
+ await handleSubmit({ preventDefault: () => {} } as React.FormEvent);
+ };
+
+ // Last resort that still leaves the user with something usable. A static code
+ // cannot be edited or tracked, but it works forever on every plan - and
+ // saying so here is what makes the rest of the modal credible.
+ const handleDownloadStatic = () => {
+ trackEvent('static_fallback_from_limit', { plan: userPlan });
+ setUpgradeOpen(false);
+ setIsDynamic(false);
+ showToast(
+ 'Switched to a static code. It cannot be edited or tracked, but it never expires.',
+ 'info'
+ );
+ };
+
+ // The logo belongs in the preset. For an agency, "client A looks the same on
+ // all 500 codes" is mostly about the mark in the middle - a preset that
+ // carries the colours but drops the logo solves the smaller half of the job.
+ const currentDesign = () => ({
+ foregroundColor,
+ backgroundColor,
+ moduleShape,
+ eyeFrameShape,
+ eyeBallShape,
+ gradientMode,
+ gradientTo,
+ frameType,
+ logoUrl: canUseLogo ? logoUrl : '',
+ logoSize,
+ });
+
+ const applyDesign = (style: any) => {
+ if (!style) return;
+ if (style.foregroundColor) setForegroundColor(style.foregroundColor);
+ if (style.backgroundColor) setBackgroundColor(style.backgroundColor);
+ if (style.moduleShape) setModuleShape(style.moduleShape);
+ if (style.eyeFrameShape) setEyeFrameShape(style.eyeFrameShape);
+ if (style.eyeBallShape) setEyeBallShape(style.eyeBallShape);
+ if (style.gradientMode) setGradientMode(style.gradientMode);
+ if (style.gradientTo) setGradientTo(style.gradientTo);
+ if (style.frameType) setFrameType(style.frameType);
+ if (typeof style.logoUrl === 'string' && canUseLogo) setLogoUrl(style.logoUrl);
+ if (style.logoSize) setLogoSize(style.logoSize);
+ };
+
+ const loadPresets = async () => {
+ try {
+ const res = await fetch('/api/design-presets');
+ if (res.ok) setPresets(await res.json());
+ } catch {
+ // presets are a convenience, never block the page on them
+ }
+ };
+
+ useEffect(() => {
+ if (canUseFullDesign) void loadPresets();
+ }, [canUseFullDesign]);
+
+ const savePreset = async () => {
+ const name = presetName.trim();
+ if (!name) {
+ showToast('Give the preset a name first.', 'error');
+ return;
+ }
+ const res = await fetchWithCsrf('/api/design-presets', {
+ method: 'POST',
+ body: JSON.stringify({ name, style: currentDesign() }),
+ });
+ if (res.ok) {
+ setPresetName('');
+ await loadPresets();
+ trackEvent('design_preset_saved', { plan: userPlan });
+ showToast(`Preset "${name}" saved.`, 'success');
+ } else {
+ const err = await res.json().catch(() => null);
+ showToast(err?.message || 'Could not save the preset.', 'error');
+ }
+ };
+
+ const deletePreset = async (id: string) => {
+ const res = await fetchWithCsrf(`/api/design-presets?id=${id}`, { method: 'DELETE' });
+ if (res.ok) await loadPresets();
+ };
+
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
@@ -404,12 +584,17 @@ export default function CreatePage() {
isStatic: !isDynamic,
tags: [],
style: {
- // FREE users can only use black/white
- foregroundColor: canCustomizeColors ? foregroundColor : '#000000',
- backgroundColor: canCustomizeColors ? backgroundColor : '#FFFFFF',
+ // Colors are available on every plan, including Free.
+ foregroundColor,
+ backgroundColor,
cornerStyle,
size,
- imageSettings: (canCustomizeColors && logoUrl) ? {
+ moduleShape,
+ eyeFrameShape,
+ eyeBallShape,
+ gradientMode,
+ gradientTo,
+ imageSettings: (canUseLogo && logoUrl) ? {
src: logoUrl,
height: logoSize,
width: logoSize,
@@ -429,38 +614,50 @@ export default function CreatePage() {
const responseData = await response.json();
console.log('RESPONSE DATA:', responseData);
- if (response.ok) {
- trackEvent('qr_code_created', {
- content_type: contentType,
- qr_type: isDynamic ? 'dynamic' : 'static',
- plan: userPlan,
- has_logo: Boolean(logoUrl),
- frame_type: frameType,
- });
-
- showToast(`QR Code "${title}" created successfully!`, 'success');
-
- // Wait a moment so user sees the toast, then redirect
- setTimeout(() => {
- const redirectTarget = sanitizeRedirectPath(searchParams.get('redirect'));
- if (searchParams.get('onboarding') === '1') {
- router.push(appendRedirectParam('/onboarding', redirectTarget, { step: '8' }));
- } else {
- router.push('/dashboard');
- }
- router.refresh();
- }, 1000);
- } else {
- console.error('Error creating QR code:', responseData);
-
- if (response.status === 403 && responseData.error === 'Limit reached') {
- showToast(responseData.message || 'You have reached your plan limit.', 'error');
- router.push('/pricing?reason=limit_reached');
- return;
- }
-
- showToast(responseData.error || 'Error creating QR code', 'error');
- }
+ if (response.ok) {
+ trackEvent('qr_code_created', {
+ content_type: contentType,
+ qr_type: isDynamic ? 'dynamic' : 'static',
+ plan: userPlan,
+ has_logo: Boolean(logoUrl),
+ frame_type: frameType,
+ });
+
+ showToast(`QR Code "${title}" created successfully!`, 'success');
+
+ // Wait a moment so user sees the toast, then redirect
+ setTimeout(() => {
+ const redirectTarget = sanitizeRedirectPath(searchParams.get('redirect'));
+ if (searchParams.get('onboarding') === '1') {
+ router.push(appendRedirectParam('/onboarding', redirectTarget, { step: '8' }));
+ } else {
+ router.push('/dashboard');
+ }
+ router.refresh();
+ }, 1000);
+ } else {
+ console.error('Error creating QR code:', responseData);
+
+ if (response.status === 403 && responseData.error === 'Limit reached') {
+ // Do NOT navigate away. The finished code only exists in this
+ // component's state - a redirect throws away the user's work at the
+ // exact moment they were most willing to pay for it.
+ trackEvent('dynamic_limit_reached', {
+ plan: responseData.plan,
+ current_count: responseData.currentCount,
+ });
+ setLimitInfo({
+ current: responseData.currentCount ?? 3,
+ limit: responseData.limit ?? 3,
+ });
+ setUpgradeReason('limit');
+ void loadActiveCodes();
+ setUpgradeOpen(true);
+ return;
+ }
+
+ showToast(responseData.error || 'Error creating QR code', 'error');
+ }
} catch (error) {
console.error('Error creating QR code:', error);
showToast('Error creating QR code. Please try again.', 'error');
@@ -810,8 +1007,8 @@ export default function CreatePage() {
onChange={(e) => setContent({ ...content, format: e.target.value })}
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
>
- CODE128 — General purpose (recommended)
- CODE39 — Industrial / logistics
+ CODE128 - General purpose (recommended)
+ CODE39 - Industrial / logistics
Only URL-capable formats available. EAN-13, UPC, and ITF-14 encode numbers only and cannot embed a redirect URL.
@@ -834,13 +1031,13 @@ export default function CreatePage() {
onChange={(e) => setContent({ ...content, format: e.target.value })}
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
>
- CODE128 — General purpose (recommended)
- EAN-13 — Retail products (international)
- UPC — Retail products (USA/Canada)
- CODE39 — Industrial / logistics
- ITF-14 — Shipping containers
- MSI — Shelf labeling / inventory
- Pharmacode — Pharmaceutical packaging
+ CODE128 - General purpose (recommended)
+ EAN-13 - Retail products (international)
+ UPC - Retail products (USA/Canada)
+ CODE39 - Industrial / logistics
+ ITF-14 - Shipping containers
+ MSI - Shelf labeling / inventory
+ Pharmacode - Pharmaceutical packaging
>
@@ -963,25 +1160,214 @@ export default function CreatePage() {
{t('create.style')}
- {!canCustomizeColors && (
- PRO Feature
- )}
+ Free on every plan
- {!canCustomizeColors && (
-
-
- Upgrade to PRO to customize colors, add logos, and brand your QR codes.
-
-
-
- Upgrade Now
-
-
-
- )}
+ {/* Module shape. Colors are free; shapes are the Pro driver that
+ replaced them. Locked options stay clickable so the preview
+ shows what is being bought before anyone pays for it. */}
+
+
+ Module shape
+ {!canUseShapes && Pro }
+
+
+ {([...PRO_MODULE_SHAPES, ...BUSINESS_MODULE_SHAPES] as ModuleShape[]).map((shape) => {
+ const isBusinessOnly = BUSINESS_MODULE_SHAPES.includes(shape);
+ const allowed = shape === 'square'
+ || (isBusinessOnly ? canUseFullDesign : canUseShapes);
+ return (
+ {
+ setModuleShape(shape);
+ if (!allowed) {
+ trackEvent('upgrade_prompt_shown', {
+ reason: 'shapes',
+ shape,
+ plan: userPlan,
+ });
+ setUpgradeReason('shapes');
+ setUpgradeOpen(true);
+ }
+ }}
+ className={cn(
+ 'rounded-lg border p-2 text-xs transition-colors',
+ moduleShape === shape
+ ? 'border-primary-500 bg-primary-50 text-primary-700'
+ : 'border-gray-200 text-gray-600 hover:border-gray-300',
+ !allowed && 'opacity-60'
+ )}
+ >
+ {MODULE_SHAPE_LABELS[shape]}
+ {!allowed && (
+
+ {isBusinessOnly ? 'Business' : 'Pro'}
+
+ )}
+
+ );
+ })}
+
+
+
+ {/* Eye styles. Only the combinations that survived decoding are
+ offered - see the note in lib/qr-shapes.ts. */}
+
+ {
+ if (!canUseShapes) {
+ setUpgradeReason('shapes');
+ setUpgradeOpen(true);
+ return;
+ }
+ setEyeFrameShape(e.target.value as EyeFrameShape);
+ }}
+ options={Object.entries(EYE_FRAME_LABELS).map(([value, label]) => ({
+ value,
+ label,
+ }))}
+ />
+ {
+ if (!canUseShapes) {
+ setUpgradeReason('shapes');
+ setUpgradeOpen(true);
+ return;
+ }
+ setEyeBallShape(e.target.value as EyeBallShape);
+ }}
+ options={Object.entries(EYE_BALL_LABELS).map(([value, label]) => ({
+ value,
+ label,
+ }))}
+ />
+
+
+ {/* Gradient. Business only - the renderer takes it as a prop, so
+ this is purely a gating and input concern. */}
+
+
+ Gradient
+ {!canUseFullDesign && Business }
+
+
+ {(['none', 'linear', 'radial'] as const).map((mode) => (
+ {
+ if (mode !== 'none' && !canUseFullDesign) {
+ trackEvent('upgrade_prompt_shown', { reason: 'shapes', feature: 'gradient', plan: userPlan });
+ setUpgradeReason('shapes');
+ setUpgradeOpen(true);
+ return;
+ }
+ setGradientMode(mode);
+ }}
+ className={cn(
+ 'rounded-lg border p-2 text-xs capitalize transition-colors',
+ gradientMode === mode
+ ? 'border-primary-500 bg-primary-50 text-primary-700'
+ : 'border-gray-200 text-gray-600 hover:border-gray-300',
+ mode !== 'none' && !canUseFullDesign && 'opacity-60'
+ )}
+ >
+ {mode === 'none' ? 'Solid' : mode}
+
+ ))}
+
+ {gradientMode !== 'none' && (
+
+ Second colour
+ setGradientTo(e.target.value)}
+ className="h-10 w-12 rounded border border-gray-300"
+ />
+ setGradientTo(e.target.value)}
+ className="flex-1"
+ />
+
+ )}
+
+
+ {/* Scannability. Says what was changed and why, rather than
+ silently raising the error correction behind the user. */}
+ {(LOW_COVERAGE_SHAPES.includes(moduleShape) || logoUrl) && (
+
+
+ {logoUrl
+ ? 'Error correction is set to H because this code carries a logo.'
+ : `"${MODULE_SHAPE_LABELS[moduleShape]}" fills less of each module, so error correction has been raised.`}
+
+
+ Print it at 2 x 2 cm or larger, and scan it once with your own
+ phone before you send it to the printer.
+
+
+ )}
+
+ {/* Saved presets. Business only. Repeatability is the actual
+ product here - the star shape is not what an agency buys. */}
+ {canUseFullDesign && (
+
+
+ Design presets
+
+ {presets.length > 0 && (
+
+ {presets.map((preset) => (
+
+ applyDesign(preset.style)}
+ className="text-gray-700 hover:text-primary-700"
+ >
+ {preset.name}
+
+ deletePreset(preset.id)}
+ className="px-1 text-gray-400 hover:text-red-600"
+ aria-label={`Delete preset ${preset.name}`}
+ >
+ ×
+
+
+ ))}
+
+ )}
+
+ setPresetName(e.target.value)}
+ placeholder="Client A"
+ className="flex-1"
+ />
+
+ Save current design
+
+
+
+ Saving under an existing name overwrites it.
+
+
+ )}
+
{/* Frame Options */}
Frame
@@ -1004,7 +1390,7 @@ export default function CreatePage() {
-
+
-
+
Background Color
@@ -1037,18 +1421,16 @@ export default function CreatePage() {
value={backgroundColor}
onChange={(e) => setBackgroundColor(e.target.value)}
className="w-12 h-10 rounded border border-gray-300"
- disabled={!canCustomizeColors}
/>
setBackgroundColor(e.target.value)}
className="flex-1"
- disabled={!canCustomizeColors}
/>
-
+
Logo
- {!canCustomizeColors && (
- PRO Feature
+ {!canUseLogo && (
+ Pro
)}
- {!canCustomizeColors && (
+ {!canUseLogo && (
- Upgrade to PRO to add logos to your QR codes.
+ Your logo in the middle of the code tells people whose it is
+ before they decide to trust it.
-
-
- Upgrade Now
-
-
+
{
+ trackEvent('upgrade_prompt_shown', { reason: 'logo', plan: userPlan });
+ setUpgradeReason('logo');
+ setUpgradeOpen(true);
+ }}
+ >
+ Add my logo
+
)}
@@ -1119,8 +1510,7 @@ export default function CreatePage() {
type="file"
accept="image/*"
onChange={handleLogoUpload}
- disabled={!canCustomizeColors}
- className="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 disabled:opacity-50 disabled:cursor-not-allowed"
+ className="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 disabled:opacity-50 disabled:cursor-not-allowed"
/>
{logoUrl && (
{/* WRAPPER FOR REF AND FRAME */}
-
+
{/* Frame Label */}
{getFrameLabel() && (
)
- ) : qrContent ? (
-
-
+
) : (
@@ -1281,6 +1675,17 @@ export default function CreatePage() {
+
+
setUpgradeOpen(false)}
+ />
);
-}
+}
diff --git a/src/app/(main)/(app)/dashboard/page.tsx b/src/app/(main)/(app)/dashboard/page.tsx
index 0e80299..2aa229e 100644
--- a/src/app/(main)/(app)/dashboard/page.tsx
+++ b/src/app/(main)/(app)/dashboard/page.tsx
@@ -7,15 +7,15 @@ import { StatsGrid } from '@/components/dashboard/StatsGrid';
import { QRCodeCard } from '@/components/dashboard/QRCodeCard';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
-import { Badge } from '@/components/ui/Badge';
-import { useTranslation } from '@/hooks/useTranslation';
-import { useCsrf } from '@/hooks/useCsrf';
-import { showToast } from '@/components/ui/Toast';
-import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/Dialog';
-import { QrCode } from 'lucide-react';
-import { trackEvent, identifyUser } from '@/components/PostHogProvider';
-import { FREE_DYNAMIC_QR_LIMIT } from '@/lib/plans';
-import { OnboardingChecklist } from '@/components/dashboard/OnboardingChecklist';
+import { Badge } from '@/components/ui/Badge';
+import { useTranslation } from '@/hooks/useTranslation';
+import { useCsrf } from '@/hooks/useCsrf';
+import { showToast } from '@/components/ui/Toast';
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/Dialog';
+import { QrCode } from 'lucide-react';
+import { trackEvent, identifyUser } from '@/components/PostHogProvider';
+import { FREE_DYNAMIC_QR_LIMIT } from '@/lib/plans';
+import { OnboardingChecklist } from '@/components/dashboard/OnboardingChecklist';
interface QRCodeData {
id: string;
@@ -47,8 +47,8 @@ export default function DashboardPage() {
conversionRate: 0,
uniqueScans: 0,
});
- const [analyticsData, setAnalyticsData] = useState
(null);
- const [onboardingState, setOnboardingState] = useState(null);
+ const [analyticsData, setAnalyticsData] = useState(null);
+ const [onboardingState, setOnboardingState] = useState(null);
const blogPosts = [
@@ -121,11 +121,11 @@ export default function DashboardPage() {
// Store in localStorage for consistency
localStorage.setItem('user', JSON.stringify(user));
- identifyUser(user.id, {
- email: user.email,
- name: user.name,
- plan: user.plan || 'FREE',
- provider: 'google',
+ identifyUser(user.id, {
+ email: user.email,
+ name: user.name,
+ plan: user.plan || 'FREE',
+ provider: 'google',
});
trackEvent(isNewUser ? 'user_signup' : 'user_login', {
@@ -146,35 +146,35 @@ export default function DashboardPage() {
}, [searchParams, router]);
// Check for successful payment and verify session
- useEffect(() => {
- const success = searchParams.get('success');
- const sessionId = searchParams.get('session_id');
-
- if (success === 'true' && sessionId) {
- const verifySession = async () => {
- try {
- const response = await fetch('/api/stripe/verify-session', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ sessionId }),
- });
-
- if (response.ok) {
- const data = await response.json();
- setUserPlan(data.plan);
- setUpgradedPlan(data.plan);
- setShowUpgradeDialog(true);
- trackEvent('upgrade_completed', {
- plan: data.plan,
- source: 'stripe_checkout',
- });
- // Remove success parameter from URL
- router.replace('/dashboard');
- } else {
- console.error('Failed to verify session:', await response.text());
- }
+ useEffect(() => {
+ const success = searchParams.get('success');
+ const sessionId = searchParams.get('session_id');
+
+ if (success === 'true' && sessionId) {
+ const verifySession = async () => {
+ try {
+ const response = await fetch('/api/stripe/verify-session', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ sessionId }),
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ setUserPlan(data.plan);
+ setUpgradedPlan(data.plan);
+ setShowUpgradeDialog(true);
+ trackEvent('upgrade_completed', {
+ plan: data.plan,
+ source: 'stripe_checkout',
+ });
+ // Remove success parameter from URL
+ router.replace('/dashboard');
+ } else {
+ console.error('Failed to verify session:', await response.text());
+ }
} catch (error) {
console.error('Error verifying session:', error);
}
@@ -225,19 +225,19 @@ export default function DashboardPage() {
setUserPlan(userData.plan || 'FREE');
}
- // Fetch analytics data for trends (last 30 days = month comparison)
- const analyticsResponse = await fetch('/api/analytics/summary?range=30');
- if (analyticsResponse.ok) {
- const analytics = await analyticsResponse.json();
- setAnalyticsData(analytics);
- }
-
- const onboardingResponse = await fetch('/api/onboarding');
- if (onboardingResponse.ok) {
- const onboardingData = await onboardingResponse.json();
- setOnboardingState(onboardingData);
- }
- } catch (error) {
+ // Fetch analytics data for trends (last 30 days = month comparison)
+ const analyticsResponse = await fetch('/api/analytics/summary?range=30');
+ if (analyticsResponse.ok) {
+ const analytics = await analyticsResponse.json();
+ setAnalyticsData(analytics);
+ }
+
+ const onboardingResponse = await fetch('/api/onboarding');
+ if (onboardingResponse.ok) {
+ const onboardingData = await onboardingResponse.json();
+ setOnboardingState(onboardingData);
+ }
+ } catch (error) {
console.error('Error fetching data:', error);
setQrCodes([]);
setStats({
@@ -320,35 +320,35 @@ export default function DashboardPage() {
}
};
- return (
-
+ return (
+
{/* Header with Plan Badge */}
-
-
+
+
{t('dashboard.title')}
{!loading && qrCodes.length === 0
- ? 'Start here — create your first QR code in under 2 minutes'
+ ? 'Start here - create your first QR code in under 2 minutes'
: t('dashboard.subtitle')}
-
-
- {userPlan} Plan
-
- {userPlan === 'FREE' && (
-
- Upgrade
-
- )}
-
-
+
+
+ {userPlan} Plan
+
+ {userPlan === 'FREE' && (
+
+ Upgrade
+
+ )}
+
+
- {/* Stats Grid */}
-
-
-
+
+
-
-
{t('dashboard.recent_codes')}
-
+
+
{t('dashboard.recent_codes')}
+
{qrCodes.length > 0 && (
{deletingAll ? 'Deleting...' : 'Delete All'}
- )}
-
- Create New QR Code
-
-
-
+ )}
+
+
Create New QR Code
+
+
+
{loading ? (
@@ -394,14 +394,14 @@ export default function DashboardPage() {
))}
) : qrCodes.length === 0 ? (
-
+
Create your first QR code
- You have {FREE_DYNAMIC_QR_LIMIT} free dynamic QR codes. They redirect wherever you want and track every scan.
-
+ You have {FREE_DYNAMIC_QR_LIMIT} free dynamic QR codes. They redirect wherever you want and track every scan.
+
-
Create QR Code — it takes 90 seconds
+
Create QR Code - it takes 90 seconds
) : (
@@ -470,7 +470,7 @@ export default function DashboardPage() {
✓
- Custom Branding (Colors & Logo)
+ 4 Module Shapes, Eye Styles & Your Logo
✓
@@ -526,4 +526,4 @@ export default function DashboardPage() {
);
-}
+}
diff --git a/src/app/(main)/(app)/layout.tsx b/src/app/(main)/(app)/layout.tsx
index 5cf728f..553abad 100644
--- a/src/app/(main)/(app)/layout.tsx
+++ b/src/app/(main)/(app)/layout.tsx
@@ -8,13 +8,14 @@ export const metadata: Metadata = {
title: 'Dashboard | QR Master',
description: 'Manage your QR Master dashboard. Create dynamic QR codes, view real-time scan analytics, and configure your account settings in one secure place.',
robots: { index: false, follow: false },
- icons: {
- icon: [
- { url: '/favicon1.png', sizes: '512x512', type: 'image/png' },
- ],
- shortcut: '/favicon1.png',
- apple: '/favicon1.png',
- },
+ icons: {
+ icon: [
+ { url: '/favicon.svg', type: 'image/svg+xml' },
+ { url: '/favicon.ico', sizes: '16x16 32x32', type: 'image/x-icon' },
+ ],
+ shortcut: '/favicon.ico',
+ apple: '/logo.svg',
+ },
};
export default function AppGroupLayout({
diff --git a/src/app/(main)/(app)/qr/[id]/page.tsx b/src/app/(main)/(app)/qr/[id]/page.tsx
index e8ac40f..4dbc54e 100644
--- a/src/app/(main)/(app)/qr/[id]/page.tsx
+++ b/src/app/(main)/(app)/qr/[id]/page.tsx
@@ -3,13 +3,14 @@
import React, { useState, useEffect } from 'react';
import { useParams, useRouter } from 'next/navigation';
import Link from 'next/link';
-import { QRCodeSVG } from 'qrcode.react';
+import StyledQRCode from '@/components/generator/StyledQRCode';
+import { renderStyledQRSvg } from '@/lib/render-qr-svg';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import {
ArrowLeft, Edit, ExternalLink, Star, MessageSquare,
- BarChart3, Copy, Check, Pause, Play
+ BarChart3, Copy, Check, Pause, Play, Download
} from 'lucide-react';
import { showToast } from '@/components/ui/Toast';
import { useCsrf } from '@/hooks/useCsrf';
@@ -82,6 +83,55 @@ export default function QRDetailPage() {
showToast('Link copied!', 'success');
};
+ // Download straight from the dashboard. Previously the only way to get a
+ // file back out was to rebuild the code in /create, which also meant the
+ // design had to be recreated from memory.
+ const downloadQR = (format: 'svg' | 'png') => {
+ if (!qrCode) return;
+ const url = `${window.location.origin}/r/${qrCode.slug}`;
+ const style = {
+ foregroundColor: qrCode.style?.foregroundColor,
+ backgroundColor: qrCode.style?.backgroundColor,
+ moduleShape: qrCode.style?.moduleShape,
+ eyeFrameShape: qrCode.style?.eyeFrameShape,
+ eyeBallShape: qrCode.style?.eyeBallShape,
+ gradientMode: qrCode.style?.gradientMode,
+ gradientTo: qrCode.style?.gradientTo,
+ logoUrl: qrCode.style?.imageSettings?.src,
+ logoSize: qrCode.style?.imageSettings?.width,
+ };
+ // 1024px so the PNG is usable in print without a second export step.
+ const svg = renderStyledQRSvg(url, style, format === 'png' ? 1024 : 512);
+ const safeName = (qrCode.title || 'qr-code').replace(/[^a-z0-9]+/gi, '-').toLowerCase();
+
+ if (format === 'svg') {
+ const blob = new Blob([svg], { type: 'image/svg+xml' });
+ const href = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = href;
+ a.download = `${safeName}.svg`;
+ a.click();
+ URL.revokeObjectURL(href);
+ return;
+ }
+
+ const img = new Image();
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ canvas.width = 1024;
+ canvas.height = 1024;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+ ctx.drawImage(img, 0, 0, 1024, 1024);
+ const a = document.createElement('a');
+ a.href = canvas.toDataURL('image/png');
+ a.download = `${safeName}.png`;
+ a.click();
+ };
+ img.onerror = () => showToast('Could not render the PNG. Try the SVG instead.', 'error');
+ img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
+ };
+
const toggleStatus = async () => {
if (!qrCode) return;
const newStatus = qrCode.status === 'ACTIVE' ? 'PAUSED' : 'ACTIVE';
@@ -169,11 +219,32 @@ export default function QRDetailPage() {
-
@@ -187,6 +258,14 @@ export default function QRDetailPage() {
Open Link
+
+ downloadQR('png')}>
+ PNG
+
+ downloadQR('svg')}>
+ SVG
+
+
diff --git a/src/app/(main)/(app)/settings/page.tsx b/src/app/(main)/(app)/settings/page.tsx
index 9d4d530..1f52914 100644
--- a/src/app/(main)/(app)/settings/page.tsx
+++ b/src/app/(main)/(app)/settings/page.tsx
@@ -356,7 +356,7 @@ export default function SettingsPage() {
window.location.href = '/pricing'}
+ onClick={() => window.location.href = '/upgrade?from=/settings'}
>
Manage Subscription
@@ -365,7 +365,7 @@ export default function SettingsPage() {
{plan === 'FREE' && (
- window.location.href = '/pricing'}>
+ window.location.href = '/upgrade?reason=limit&from=/settings'}>
Upgrade Plan
diff --git a/src/app/(main)/(app)/upgrade/page.tsx b/src/app/(main)/(app)/upgrade/page.tsx
new file mode 100644
index 0000000..5a22ce2
--- /dev/null
+++ b/src/app/(main)/(app)/upgrade/page.tsx
@@ -0,0 +1,227 @@
+'use client';
+
+import React, { useEffect, useState } from 'react';
+import { useSearchParams } from 'next/navigation';
+import { Card, CardContent } from '@/components/ui/Card';
+import { Button } from '@/components/ui/Button';
+import { Badge } from '@/components/ui/Badge';
+import { showToast } from '@/components/ui/Toast';
+import { trackEvent } from '@/components/PostHogProvider';
+import { Check, Loader2 } from 'lucide-react';
+import {
+ FREE_DYNAMIC_QR_LIMIT,
+ PRO_DYNAMIC_QR_LIMIT,
+ BUSINESS_DYNAMIC_QR_LIMIT,
+} from '@/lib/plans';
+
+type PlanKey = 'FREE' | 'PRO' | 'BUSINESS';
+
+/**
+ * In-app upgrade page.
+ *
+ * /pricing lives in the (marketing) route group, so every upgrade link inside
+ * the app dropped the user out of the product shell and into the public site.
+ * This page sits in the (app) group, keeps the sidebar, and returns the user to
+ * where they came from after checkout.
+ *
+ * Copy note: the plan captions describe the moment each plan stops being enough,
+ * not a feature count. Someone on this page already knows what the product does
+ * - what they are deciding is whether they have crossed a line yet.
+ */
+const PLANS: {
+ key: PlanKey;
+ name: string;
+ price: string;
+ period: string;
+ caption: string;
+ features: string[];
+ popular?: boolean;
+}[] = [
+ {
+ key: 'FREE',
+ name: 'Free',
+ price: '€0',
+ period: 'forever',
+ caption: 'Enough to prove the idea on one or two placements.',
+ features: [
+ `${FREE_DYNAMIC_QR_LIMIT} active dynamic QR codes`,
+ 'Unlimited static codes that never expire',
+ 'Basic scan tracking',
+ 'Your colors, foreground and background',
+ 'SVG and PNG download',
+ ],
+ },
+ {
+ key: 'PRO',
+ name: 'Pro',
+ price: '€9',
+ period: 'per month',
+ popular: true,
+ caption: 'When one campaign is no longer the only campaign.',
+ features: [
+ `${PRO_DYNAMIC_QR_LIMIT} dynamic QR codes`,
+ 'Scan data by device, location and time',
+ '4 module shapes and custom eye styles',
+ 'Your logo in the centre of the code',
+ 'Everything in Free',
+ ],
+ },
+ {
+ key: 'BUSINESS',
+ name: 'Business',
+ price: '€29',
+ period: 'per month',
+ caption: 'When codes are produced in batches, not one at a time.',
+ features: [
+ `${BUSINESS_DYNAMIC_QR_LIMIT} dynamic QR codes`,
+ 'Bulk creation: 1,000 static or 500 dynamic per upload',
+ 'Full designer: 11 module shapes and colour gradients',
+ 'Saved design presets, applied to a whole bulk upload',
+ 'Priority email support',
+ 'Everything in Pro',
+ ],
+ },
+];
+
+const REASON_HEADLINES: Record
= {
+ limit: 'You are out of dynamic code slots.',
+ shapes: 'Module shapes start on Pro.',
+ logo: 'Your logo belongs inside the code.',
+ bulk: 'Bulk creation is a Business feature.',
+ analytics: 'You are seeing totals, not sources.',
+};
+
+export default function UpgradePage() {
+ const searchParams = useSearchParams();
+ const [currentPlan, setCurrentPlan] = useState('FREE');
+ const [loadingPlan, setLoadingPlan] = useState(null);
+
+ const reason = searchParams.get('reason');
+ const returnTo = searchParams.get('from');
+
+ useEffect(() => {
+ fetch('/api/user/plan')
+ .then((r) => (r.ok ? r.json() : null))
+ .then((d) => d?.plan && setCurrentPlan(d.plan))
+ .catch(() => {});
+ }, []);
+
+ useEffect(() => {
+ if (searchParams.get('canceled') === 'true') {
+ showToast('Checkout canceled. Nothing was charged.', 'info');
+ }
+ }, [searchParams]);
+
+ const handleUpgrade = async (plan: PlanKey) => {
+ if (plan === 'FREE') return;
+ setLoadingPlan(plan);
+ trackEvent('upgrade_clicked', { plan, source: 'in_app_upgrade', reason });
+
+ try {
+ const res = await fetch('/api/stripe/create-checkout-session', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ plan,
+ billingInterval: 'month',
+ returnPath: returnTo && returnTo.startsWith('/') ? returnTo : '/dashboard',
+ }),
+ });
+
+ if (!res.ok) {
+ const data = await res.json().catch(() => null);
+ throw new Error(data?.error || 'Could not start checkout.');
+ }
+
+ const { url } = await res.json();
+ window.location.href = url;
+ } catch (err: any) {
+ showToast(err?.message || 'Could not start checkout. Please try again.', 'error');
+ setLoadingPlan(null);
+ }
+ };
+
+ return (
+
+
+
+ {reason && REASON_HEADLINES[reason]
+ ? REASON_HEADLINES[reason]
+ : 'Pick the plan that matches what you are running'}
+
+
+ Every plan keeps your static codes working forever, and nothing you have
+ already printed stops resolving if you change plans. Cancel any time from
+ Settings.
+
+
+
+
+ {PLANS.map((plan) => {
+ const isCurrent = plan.key === currentPlan;
+ return (
+
+
+
+
{plan.name}
+ {plan.popular && Most popular }
+ {isCurrent && Current plan }
+
+
+
+ {plan.price}
+ {plan.period}
+
+ {plan.caption}
+
+
+ {plan.features.map((f) => (
+
+
+ {f}
+
+ ))}
+
+
+
+ {isCurrent ? (
+
+ Current plan
+
+ ) : plan.key === 'FREE' ? (
+
+ Included
+
+ ) : (
+ handleUpgrade(plan.key)}
+ >
+ {loadingPlan === plan.key ? (
+
+ Opening checkout...
+
+ ) : (
+ `Upgrade to ${plan.name}`
+ )}
+
+ )}
+
+
+
+ );
+ })}
+
+
+
+ Prices exclude VAT where applicable. Payments run through Stripe - QR Master
+ never sees your card details.
+
+
+ );
+}
diff --git a/src/app/(main)/(auth)/forgot-password/page.tsx b/src/app/(main)/(auth)/forgot-password/page.tsx
index 3004c7f..f2e1ea9 100644
--- a/src/app/(main)/(auth)/forgot-password/page.tsx
+++ b/src/app/(main)/(auth)/forgot-password/page.tsx
@@ -44,10 +44,10 @@ export default function ForgotPasswordPage() {
-
-
-
QR Master
-
+
+
+
QR Master
+
Check Your Email
We've sent you a password reset link
@@ -98,10 +98,10 @@ export default function ForgotPasswordPage() {
-
-
-
QR Master
-
+
+
+
QR Master
+
Forgot Password?
No worries, we'll send you reset instructions
diff --git a/src/app/(main)/(auth)/login/LoginClient.tsx b/src/app/(main)/(auth)/login/LoginClient.tsx
index 9a05857..b631c52 100644
--- a/src/app/(main)/(auth)/login/LoginClient.tsx
+++ b/src/app/(main)/(auth)/login/LoginClient.tsx
@@ -5,10 +5,10 @@ import { useRouter, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { Card, CardContent } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';
-import { Button } from '@/components/ui/Button';
-import { useTranslation } from '@/hooks/useTranslation';
-import { useCsrf } from '@/hooks/useCsrf';
-import { appendRedirectParam, sanitizeRedirectPath } from '@/lib/auth-flow';
+import { Button } from '@/components/ui/Button';
+import { useTranslation } from '@/hooks/useTranslation';
+import { useCsrf } from '@/hooks/useCsrf';
+import { appendRedirectParam, sanitizeRedirectPath } from '@/lib/auth-flow';
type LoginClientProps = {
showPageHeading?: boolean;
@@ -21,10 +21,10 @@ export default function LoginClient({ showPageHeading = true }: LoginClientProps
const { fetchWithCsrf, loading: csrfLoading } = useCsrf();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState('');
- const [showPassword, setShowPassword] = useState(false);
- const redirectTarget = sanitizeRedirectPath(searchParams.get('redirect'));
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
+ const redirectTarget = sanitizeRedirectPath(searchParams.get('redirect'));
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -59,12 +59,12 @@ export default function LoginClient({ showPageHeading = true }: LoginClientProps
console.error('PostHog tracking error:', error);
}
- // Check for redirect parameter
- const redirectUrl = data.needsOnboarding
- ? appendRedirectParam('/onboarding', redirectTarget)
- : (redirectTarget || '/dashboard');
- router.push(redirectUrl);
- router.refresh();
+ // Check for redirect parameter
+ const redirectUrl = data.needsOnboarding
+ ? appendRedirectParam('/onboarding', redirectTarget)
+ : (redirectTarget || '/dashboard');
+ router.push(redirectUrl);
+ router.refresh();
} else {
setError(data.error || 'Invalid email or password');
}
@@ -75,17 +75,17 @@ export default function LoginClient({ showPageHeading = true }: LoginClientProps
}
};
- const handleGoogleSignIn = () => {
- // Redirect to Google OAuth API route
- window.location.href = appendRedirectParam('/api/auth/google', redirectTarget);
- };
+ const handleGoogleSignIn = () => {
+ // Redirect to Google OAuth API route
+ window.location.href = appendRedirectParam('/api/auth/google', redirectTarget);
+ };
return (
-
+
QR Master
{showPageHeading ? (
@@ -203,9 +203,9 @@ export default function LoginClient({ showPageHeading = true }: LoginClientProps
Don't have an account?{' '}
-
- Sign up
-
+
+ Sign up
+
diff --git a/src/app/(main)/(auth)/login/page.tsx b/src/app/(main)/(auth)/login/page.tsx
index 395e07c..cff0751 100644
--- a/src/app/(main)/(auth)/login/page.tsx
+++ b/src/app/(main)/(auth)/login/page.tsx
@@ -2,7 +2,7 @@ import type { Metadata } from 'next';
import LoginClient from './LoginClient';
export const metadata: Metadata = {
- title: 'QR Master – Smart QR Generator & Analytics',
+ title: 'QR Master - Smart QR Generator & Analytics',
description: 'Create dynamic QR codes, track scans, and scale campaigns with secure analytics. Free advanced features, bulk generation, and custom branding available.',
robots: {
index: false,
@@ -10,11 +10,11 @@ export const metadata: Metadata = {
},
};
-export default function LoginPage() {
- return (
-
- Login to QR Master
-
-
- );
-}
+export default function LoginPage() {
+ return (
+
+ Login to QR Master
+
+
+ );
+}
diff --git a/src/app/(main)/(auth)/reset-password/page.tsx b/src/app/(main)/(auth)/reset-password/page.tsx
index c207b1b..b28a809 100644
--- a/src/app/(main)/(auth)/reset-password/page.tsx
+++ b/src/app/(main)/(auth)/reset-password/page.tsx
@@ -77,10 +77,10 @@ export default function ResetPasswordPage() {
-
-
-
QR Master
-
+
+
+
QR Master
+
Password Reset Successful
Your password has been updated
@@ -119,10 +119,10 @@ export default function ResetPasswordPage() {
-
-
-
QR Master
-
+
+
+
QR Master
+
Reset Your Password
Enter your new password below
diff --git a/src/app/(main)/(auth)/signup/SignupClient.tsx b/src/app/(main)/(auth)/signup/SignupClient.tsx
index a39fc2a..8fe6304 100644
--- a/src/app/(main)/(auth)/signup/SignupClient.tsx
+++ b/src/app/(main)/(auth)/signup/SignupClient.tsx
@@ -1,29 +1,29 @@
'use client';
-import React, { useState } from 'react';
-import { useRouter, useSearchParams } from 'next/navigation';
-import Link from 'next/link';
-import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
-import { Input } from '@/components/ui/Input';
-import { Button } from '@/components/ui/Button';
-import { useTranslation } from '@/hooks/useTranslation';
-import { useCsrf } from '@/hooks/useCsrf';
-import { appendRedirectParam, sanitizeRedirectPath } from '@/lib/auth-flow';
-
-export default function SignupClient() {
- const router = useRouter();
- const searchParams = useSearchParams();
- const { t } = useTranslation();
- const { fetchWithCsrf } = useCsrf();
+import React, { useState } from 'react';
+import { useRouter, useSearchParams } from 'next/navigation';
+import Link from 'next/link';
+import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
+import { Input } from '@/components/ui/Input';
+import { Button } from '@/components/ui/Button';
+import { useTranslation } from '@/hooks/useTranslation';
+import { useCsrf } from '@/hooks/useCsrf';
+import { appendRedirectParam, sanitizeRedirectPath } from '@/lib/auth-flow';
+
+export default function SignupClient() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const { t } = useTranslation();
+ const { fetchWithCsrf } = useCsrf();
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [loading, setLoading] = useState(false);
- const [error, setError] = useState('');
- const [showPassword, setShowPassword] = useState(false);
- const [showConfirmPassword, setShowConfirmPassword] = useState(false);
- const redirectTarget = sanitizeRedirectPath(searchParams.get('redirect'));
+ const [error, setError] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
+ const redirectTarget = sanitizeRedirectPath(searchParams.get('redirect'));
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -71,9 +71,9 @@ export default function SignupClient() {
console.error('PostHog tracking error:', error);
}
- // Redirect to onboarding
- router.push(appendRedirectParam('/onboarding', redirectTarget));
- router.refresh();
+ // Redirect to onboarding
+ router.push(appendRedirectParam('/onboarding', redirectTarget));
+ router.refresh();
} else {
setError(data.error || 'Failed to create account');
}
@@ -84,17 +84,17 @@ export default function SignupClient() {
}
};
- const handleGoogleSignIn = () => {
- // Redirect to Google OAuth API route
- window.location.href = appendRedirectParam('/api/auth/google', redirectTarget);
- };
+ const handleGoogleSignIn = () => {
+ // Redirect to Google OAuth API route
+ window.location.href = appendRedirectParam('/api/auth/google', redirectTarget);
+ };
return (
-
+
QR Master
Create Account
@@ -237,11 +237,11 @@ export default function SignupClient() {
-
- Already have an account?{' '}
-
- Sign in
-
+
+ Already have an account?{' '}
+
+ Sign in
+
diff --git a/src/app/(main)/(marketing)/MarketingLayout.tsx b/src/app/(main)/(marketing)/MarketingLayout.tsx
index 8d2ed34..c741392 100644
--- a/src/app/(main)/(marketing)/MarketingLayout.tsx
+++ b/src/app/(main)/(marketing)/MarketingLayout.tsx
@@ -93,7 +93,7 @@ export default function MarketingLayout({
flexible, measurable, and reliable.
- QR Master helps teams create dynamic QR codes that can be updated after printing—so you can stop reprinting materials every time something changes. Whether you’re running a menu, an event, or a multi-channel campaign, QR Master turns QR codes into a tool you can manage and measure.
+ QR Master helps teams create dynamic QR codes that can be updated after printing-so you can stop reprinting materials every time something changes. Whether you’re running a menu, an event, or a multi-channel campaign, QR Master turns QR codes into a tool you can manage and measure.
@@ -53,7 +53,7 @@ export default function AboutPage() {
Our Mission
- Create QR codes that work everywhere—and make campaigns measurable.
+ Create QR codes that work everywhere-and make campaigns measurable.
@@ -76,7 +76,7 @@ export default function AboutPage() {
Dynamic QR Codes
- Change the destination of a QR code after it’s already printed. Keep your printed materials valid—while you update your content anytime.
+ Change the destination of a QR code after it’s already printed. Keep your printed materials valid-while you update your content anytime.
Learn about Dynamic QR →
@@ -104,7 +104,7 @@ export default function AboutPage() {
Advanced Analytics
- Understand QR performance with scan analytics—so you can improve placements and campaigns based on real usage over time.
+ Understand QR performance with scan analytics-so you can improve placements and campaigns based on real usage over time.
See Analytics Features →
@@ -230,7 +230,7 @@ export default function AboutPage() {
Support hours:
- Monday–Friday, 9:00–17:00 CET
+ Monday-Friday, 9:00-17:00 CET
Languages:
diff --git a/src/app/(main)/(marketing)/alternatives/beaconstac/page.tsx b/src/app/(main)/(marketing)/alternatives/beaconstac/page.tsx
index a75865c..5dfb0fd 100644
--- a/src/app/(main)/(marketing)/alternatives/beaconstac/page.tsx
+++ b/src/app/(main)/(marketing)/alternatives/beaconstac/page.tsx
@@ -14,17 +14,17 @@ const competitor = competitors['beaconstac'];
export const metadata: Metadata = {
title: {
- absolute: 'Beaconstac / Uniqode Alternative for SMBs – QR Master',
+ absolute: 'Beaconstac / Uniqode Alternative - Free, €9 or €29',
},
description:
- 'Looking for a Beaconstac or Uniqode alternative? QR Master is the lightweight, affordable option for SMBs and freelancers who need dynamic QR codes and analytics without enterprise pricing. From €0 free.',
+ 'Uniqode is built for enterprise, and priced for it. If you need dynamic QR codes and scan analytics but not SOC2 and SSO: QR Master is €0, €9 or €29 a month.',
keywords:
'beaconstac alternative, uniqode alternative, beaconstac pricing, uniqode too expensive, beaconstac smb alternative, dynamic qr code alternative enterprise',
alternates: {
canonical: 'https://www.qrmaster.net/alternatives/beaconstac',
},
openGraph: {
- title: 'Beaconstac / Uniqode Alternative for SMBs – QR Master',
+ title: 'Beaconstac / Uniqode Alternative for SMBs - QR Master',
description:
'Uniqode (formerly Beaconstac) is excellent for enterprise. If you don\'t need SOC2 and SSO but do need reliable dynamic QR + analytics, QR Master starts free at €0.',
url: 'https://www.qrmaster.net/alternatives/beaconstac',
@@ -32,7 +32,7 @@ export const metadata: Metadata = {
images: ['/og-image.png'],
},
twitter: {
- title: 'Beaconstac / Uniqode Alternative for SMBs – QR Master',
+ title: 'Beaconstac / Uniqode Alternative for SMBs - QR Master',
description:
'Uniqode (formerly Beaconstac) is built for enterprise. QR Master is the affordable alternative for SMBs and freelancers who need the same core QR functionality.',
},
@@ -44,37 +44,37 @@ const faqItems = [
{
question: 'What is the difference between Beaconstac and Uniqode?',
answer:
- 'They are the same company. Beaconstac rebranded to Uniqode in 2023. The product is the same enterprise QR code management platform — the name changed, not the features or pricing model. When people search for "Beaconstac alternative" or "Uniqode alternative," they are looking for the same thing.',
+ 'They are the same company. Beaconstac rebranded to Uniqode in 2023. The product is the same enterprise QR code management platform - the name changed, not the features or pricing model. When people search for "Beaconstac alternative" or "Uniqode alternative," they are looking for the same thing.',
},
{
question: 'Why is Uniqode / Beaconstac considered expensive for SMBs?',
answer:
- 'Uniqode\'s entry price is around $5/month, but that tier includes very limited features. To get meaningful analytics, team management, and enough dynamic QR codes for a real use case, you need to spend $49–99/month or more. The enterprise features — SOC2 compliance, SSO/SAML, deep API access — are what justify that pricing for large organizations. For an SMB that needs 50 dynamic QR codes with scan analytics, those enterprise features are not relevant, and paying for them is waste.',
+ 'Uniqode\'s entry price is around $5/month, but that tier includes very limited features. To get meaningful analytics, team management, and enough dynamic QR codes for a real use case, you need to spend $49-99/month or more. The enterprise features - SOC2 compliance, SSO/SAML, deep API access - are what justify that pricing for large organizations. For an SMB that needs 50 dynamic QR codes with scan analytics, those enterprise features are not relevant, and paying for them is waste.',
},
{
question: 'Does QR Master have a free plan?',
answer:
- 'Yes. QR Master\'s free plan includes 3 active dynamic QR codes, unlimited static QR codes, and basic scan tracking. No credit card required. Uniqode does not offer a free plan — you pay from the first month. QR Master Pro at €9/month includes 50 dynamic QR codes, advanced analytics, and custom branding. Business at €29/month adds bulk creation and 500 dynamic codes.',
+ 'Yes. QR Master\'s free plan includes 3 active dynamic QR codes, unlimited static QR codes, and basic scan tracking. No credit card required. Uniqode does not offer a free plan - you pay from the first month. Colors are free on every plan. QR Master Pro at €9/month includes 50 dynamic QR codes, advanced analytics, module shapes and logo embedding. Business at €29/month adds bulk creation and 500 dynamic codes.',
},
{
question: 'Does QR Master support bulk QR code creation like Beaconstac?',
answer:
- 'Yes. QR Master Business (€29/month) supports CSV and Excel upload for bulk creation of up to 1,000 unique QR codes per batch. Each code can have a different destination URL, label, and UTM parameters. Beaconstac/Uniqode also supports bulk creation, but the feature is locked behind enterprise pricing tiers.',
+ 'Yes. QR Master Business (€29/month) supports CSV and Excel upload for bulk creation: up to 1,000 static codes, or up to 500 dynamic ones per batch, the dynamic cap being the Business allowance. Each code can have a different destination URL, label, and UTM parameters. Beaconstac/Uniqode also supports bulk creation, but the feature is locked behind enterprise pricing tiers.',
},
{
question: 'Is QR Master GDPR-compliant?',
answer:
- 'Yes. QR Master hashes IP addresses server-side before any analytics data is stored. No raw IP is ever written to the database. Scan analytics capture device type, time, country-level location, and UTM parameters — without storing personally identifiable information. This is built into the infrastructure and applies to all plans, including the free tier. Uniqode is a US company and requires additional DPA configuration for GDPR compliance.',
+ 'Yes. QR Master hashes IP addresses server-side before any analytics data is stored. No raw IP is ever written to the database. Scan analytics capture device type, time, country-level location, and UTM parameters - without storing personally identifiable information. This is built into the infrastructure and applies to all plans, including the free tier. Uniqode is a US company and requires additional DPA configuration for GDPR compliance.',
},
{
question: 'Who should stay on Beaconstac / Uniqode instead of switching?',
answer:
- 'Uniqode is genuinely the right tool for large enterprises that need SOC2 Type II certification, SSO/SAML authentication, deep API integrations, and formal vendor security review processes. If your procurement team requires a security certification or your IT team needs to integrate QR code management into enterprise identity systems, Uniqode is built for that. QR Master is not an enterprise compliance platform — it is a clean, fast, affordable tool for teams that need dynamic QR codes and analytics without the enterprise overhead.',
+ 'Uniqode is genuinely the right tool for large enterprises that need SOC2 Type II certification, SSO/SAML authentication, deep API integrations, and formal vendor security review processes. If your procurement team requires a security certification or your IT team needs to integrate QR code management into enterprise identity systems, Uniqode is built for that. QR Master is not an enterprise compliance platform - it is a clean, fast, affordable tool for teams that need dynamic QR codes and analytics without the enterprise overhead.',
},
{
question: 'Can I import my codes from Beaconstac into QR Master?',
answer:
- 'Beaconstac/Uniqode allows CSV export of your QR code data. You can use that export to re-create your dynamic codes in QR Master using the bulk upload feature (Business plan). For dynamic codes, the redirect URL changes — you will need to update printed materials or digital placements that point to Beaconstac\'s redirect infrastructure. Static codes are permanently encoded in the image and do not need migration — they continue working regardless of your Beaconstac subscription.',
+ 'Beaconstac/Uniqode allows CSV export of your QR code data. You can use that export to re-create your dynamic codes in QR Master using the bulk upload feature (Business plan, up to 500 dynamic codes per account). For dynamic codes, the redirect URL changes - you will need to update printed materials or digital placements that point to Beaconstac\'s redirect infrastructure. Static codes are permanently encoded in the image and do not need migration - they continue working regardless of your Beaconstac subscription.',
},
];
@@ -103,21 +103,21 @@ const relatedLinks = [
href: '/dynamic-qr-code-generator',
title: 'Dynamic QR Code Generator',
description:
- 'Create dynamic QR codes you can update after printing — with scan analytics, custom branding, and dashboard management.',
+ 'Create dynamic QR codes you can update after printing - with scan analytics, custom branding, and dashboard management.',
ctaLabel: 'Create your first dynamic QR code',
},
{
href: '/qr-code-analytics',
title: 'QR Code Analytics',
description:
- 'Track device, time, location, and UTM parameters for every scan — without storing raw IPs or PII.',
+ 'Track device, time, location, and UTM parameters for every scan - without storing raw IPs or PII.',
ctaLabel: 'See QR code analytics',
},
{
href: '/bulk-qr-code-generator',
title: 'Bulk QR Code Generator',
description:
- 'Generate up to 1,000 unique QR codes in one upload via CSV or Excel. Each code gets its own destination and UTM parameters.',
+ 'One upload, a whole batch: up to 1,000 static codes or up to 500 dynamic ones. Each code gets its own destination and UTM parameters.',
ctaLabel: 'Explore bulk QR creation',
},
{
@@ -165,11 +165,11 @@ export default function BeaconstacAlternativePage() {
{[
- 'Free plan with 3 active dynamic QR codes — no credit card required',
- 'Pro at €9/month vs Uniqode\'s $49–99/month for comparable features',
- 'GDPR-compliant analytics out of the box — no DPA configuration needed',
- 'Bulk creation up to 1,000 codes on Business (€29/month)',
- 'Simple onboarding — no enterprise setup process',
+ 'Free plan with 3 active dynamic QR codes - no credit card required',
+ 'Pro at €9/month vs Uniqode\'s $49-99/month for comparable features',
+ 'GDPR-compliant analytics out of the box - no DPA configuration needed',
+ 'Bulk creation on Business: 1,000 static or 500 dynamic (€29/month)',
+ 'Simple onboarding - no enterprise setup process',
].map((feature) => (
Starter plan with analytics
-
$49–99
+
$49-99
per month
@@ -258,28 +258,28 @@ export default function BeaconstacAlternativePage() {
Uniqode (formerly Beaconstac) is genuinely excellent at what it does. The platform is built for large
- enterprises that operate in compliance-heavy industries — healthcare, finance, government contracting —
+ enterprises that operate in compliance-heavy industries - healthcare, finance, government contracting -
where vendors need SOC2 Type II certification, single sign-on integration, formal security review, and
a dedicated account team. For those buyers, Uniqode is a legitimate choice.
The problem is that all of that infrastructure costs money, and Uniqode passes those costs through in
- its pricing. The entry plan at around $5/month is misleadingly cheap — it supports so few codes and
+ its pricing. The entry plan at around $5/month is misleadingly cheap - it supports so few codes and
offers so few features that almost no real use case fits it. To get 50 dynamic QR codes with proper
- analytics and team features, you are looking at $49–99/month before you even touch the enterprise
+ analytics and team features, you are looking at $49-99/month before you even touch the enterprise
tier.
For a restaurant owner who wants to update their digital menu link once a quarter, or a marketing
manager running a campaign with 20 QR codes on printed materials, or a freelancer building print
- campaigns for clients — the SOC2 certification is irrelevant, and $49+/month is a hard number to
+ campaigns for clients - the SOC2 certification is irrelevant, and $49+/month is a hard number to
justify when the core functionality needed is “create QR codes, track scans, update
destinations.”
QR Master is built for that majority use case. It doesn't have SOC2. It doesn't have SSO.
What it has is reliable dynamic QR code management, scan analytics with GDPR-compliant tracking, and
- bulk creation — at a price that makes sense for teams that don't need the enterprise compliance
+ bulk creation - at a price that makes sense for teams that don't need the enterprise compliance
layer.
@@ -290,7 +290,7 @@ export default function BeaconstacAlternativePage() {
When you should stay on Uniqode
If your organization requires a SOC2-certified QR code vendor, needs SSO/SAML integration, or goes
- through formal vendor security review — Uniqode is built for exactly that. QR Master is not. This page
+ through formal vendor security review - Uniqode is built for exactly that. QR Master is not. This page
is for the much larger group of SMBs and marketing teams who are paying enterprise prices for
functionality they could get at a fraction of the cost.
@@ -359,16 +359,16 @@ export default function BeaconstacAlternativePage() {
Pricing structure
- Uniqode starts at around $5/month but that plan is largely a placeholder — it supports so few codes
+ Uniqode starts at around $5/month but that plan is largely a placeholder - it supports so few codes
with so few features that most users immediately hit its limits. The next meaningful tier is $49/month
or higher. There is no free plan.
QR Master free plan includes 3 active dynamic QR codes, unlimited static codes, and basic scan
- tracking — permanently, without a credit card. Pro at €9/month (cancel anytime) covers 50 dynamic
- codes with full analytics and custom branding. Business at €29/month adds 500 codes and bulk
+ tracking - permanently, without a credit card. Pro at €9/month (cancel anytime) covers 50 dynamic
+ codes with full analytics, module shapes and logo. Business at €29/month adds 500 codes and bulk
creation. The gap between what you get at €9/month on QR Master vs $49/month on Uniqode is significant
- — not because QR Master has more features, but because it doesn't charge you for enterprise
+ - not because QR Master has more features, but because it doesn't charge you for enterprise
infrastructure you don't use.
@@ -376,14 +376,14 @@ export default function BeaconstacAlternativePage() {
Complexity and onboarding
- Uniqode is a mature enterprise platform. The interface reflects that — it is comprehensive, with
+ Uniqode is a mature enterprise platform. The interface reflects that - it is comprehensive, with
organization management, user roles, integration settings, and compliance tooling all visible.
For an enterprise IT team, that depth is valuable. For a marketing manager or restaurant owner who
just needs to create and track 20 dynamic QR codes, it adds overhead without adding value.
QR Master is deliberately simpler. Create a QR code, set the destination, download it, and see scans
- in the dashboard. The workflow is designed around the most common use cases — not around the edge
+ in the dashboard. The workflow is designed around the most common use cases - not around the edge
cases that enterprise compliance teams need.
@@ -397,7 +397,7 @@ export default function BeaconstacAlternativePage() {
QR Master anonymizes scan data at the infrastructure level. IP addresses are hashed server-side with
- a salt before any data is written — the raw IP is never stored. No configuration required. This
+ a salt before any data is written - the raw IP is never stored. No configuration required. This
applies from the free plan upward and is documented in the platform's open codebase.
@@ -469,7 +469,7 @@ export default function BeaconstacAlternativePage() {
{
step: '2',
title: 'Create a QR Master account',
- body: 'The free plan gives you 3 dynamic codes immediately. For larger migrations, start with a Pro (€9/month) or Business (€29/month) plan — Business includes bulk upload from CSV.',
+ body: 'The free plan gives you 3 dynamic codes immediately. For larger migrations, start with a Pro (€9/month) or Business (€29/month) plan - Business includes bulk upload from CSV.',
},
{
step: '3',
@@ -479,7 +479,7 @@ export default function BeaconstacAlternativePage() {
{
step: '4',
title: 'Update digital placements',
- body: 'Replace QR code images on your website, email, and digital materials immediately — these don\'t require a physical reprint.',
+ body: 'Replace QR code images on your website, email, and digital materials immediately - these don\'t require a physical reprint.',
},
{
step: '5',
diff --git a/src/app/(main)/(marketing)/alternatives/bitly/page.tsx b/src/app/(main)/(marketing)/alternatives/bitly/page.tsx
index c90b4ea..c107107 100644
--- a/src/app/(main)/(marketing)/alternatives/bitly/page.tsx
+++ b/src/app/(main)/(marketing)/alternatives/bitly/page.tsx
@@ -17,7 +17,7 @@ export const metadata: Metadata = {
absolute: 'QR Master vs Bitly QR Codes | Bitly Alternative',
},
description:
- 'Looking for a Bitly alternative for QR codes? Bitly\'s Core plan costs $10/month but only allows 2 QR codes total. QR Master is purpose-built for QR code management — 50 codes at €9/month, bulk creation, GDPR analytics. From €0.',
+ 'Looking for a Bitly alternative for QR codes? Bitly\'s Core plan costs $10/month but only allows 2 QR codes total. QR Master is purpose-built for QR code management - 50 codes at €9/month, bulk creation, GDPR analytics. From €0.',
keywords:
'bitly qr code alternative, bitly qr code limit, bitly alternative qr codes, bitly pricing qr codes, bitly 2 qr codes',
alternates: {
@@ -26,7 +26,7 @@ export const metadata: Metadata = {
openGraph: {
title: 'QR Master vs Bitly QR Codes | Bitly Alternative',
description:
- 'Bitly\'s Core plan costs $10/month but only gives you 2 QR codes. QR Master gives you 50 dynamic QR codes at €9/month — purpose-built for QR workflows, bulk creation, and GDPR analytics.',
+ 'Bitly\'s Core plan costs $10/month but only gives you 2 QR codes. QR Master gives you 50 dynamic QR codes at €9/month - purpose-built for QR workflows, bulk creation, and GDPR analytics.',
url: 'https://www.qrmaster.net/alternatives/bitly',
type: 'website',
images: ['/og-image.png'],
@@ -34,7 +34,7 @@ export const metadata: Metadata = {
twitter: {
title: 'QR Master vs Bitly QR Codes | Bitly Alternative',
description:
- 'Bitly gives you 2 QR codes for $10/month. QR Master gives you 50 at €9/month — purpose-built for real QR campaigns, not link shortening with QR as an afterthought.',
+ 'Bitly gives you 2 QR codes for $10/month. QR Master gives you 50 at €9/month - purpose-built for real QR campaigns, not link shortening with QR as an afterthought.',
},
};
@@ -54,7 +54,7 @@ const atAGlanceRows = [
{
useCase: 'Bulk QR creation',
bitly: 'No dedicated bulk QR generator.',
- qrMaster: 'CSV and Excel upload creates up to 1,000 unique QR codes per batch.',
+ qrMaster: 'CSV and Excel upload: up to 1,000 static codes, or up to 500 dynamic ones, per batch.',
},
{
useCase: 'QR campaign analytics',
@@ -72,17 +72,17 @@ const faqItems = [
{
question: 'How many QR codes does Bitly allow per plan?',
answer:
- 'Bitly\'s free plan allows 1 QR code. Their Core plan (~$10/month) markets "unlimited scans" prominently — but the actual limit that matters is the QR code count: 2 total. If you need a third QR code on that plan, you have to upgrade. Higher plans allow more codes, but the pricing jumps quickly relative to what you get. QR Master\'s Pro plan (€9/month) includes 50 dynamic QR codes with full analytics and no scan caps on redirects.',
+ 'Bitly\'s free plan allows 1 QR code. Their Core plan (~$10/month) markets "unlimited scans" prominently - but the actual limit that matters is the QR code count: 2 total. If you need a third QR code on that plan, you have to upgrade. Higher plans allow more codes, but the pricing jumps quickly relative to what you get. QR Master\'s Pro plan (€9/month) includes 50 dynamic QR codes with full analytics and no scan caps on redirects.',
},
{
question: 'Is Bitly good for QR code management?',
answer:
- 'Bitly works for QR codes in the sense that it can generate them and track clicks. But the product is built around link management and URL shortening — QR codes are a secondary feature. The workflow, the dashboard, and the pricing model are all designed around links, not QR code-specific use cases like restaurant menus, product packaging, event materials, or bulk creation for print campaigns. If QR codes are your primary use case, a purpose-built platform handles the workflow better.',
+ 'Bitly works for QR codes in the sense that it can generate them and track clicks. But the product is built around link management and URL shortening - QR codes are a secondary feature. The workflow, the dashboard, and the pricing model are all designed around links, not QR code-specific use cases like restaurant menus, product packaging, event materials, or bulk creation for print campaigns. If QR codes are your primary use case, a purpose-built platform handles the workflow better.',
},
{
question: 'How does Bitly pricing compare to QR Master for QR codes?',
answer:
- 'Bitly\'s free plan allows only 1 QR code. Their Core plan (~$10/month) allows 2 QR codes. Higher plans add more codes but pricing escalates steeply. QR Master\'s free plan includes 3 active dynamic QR codes and unlimited static codes. Pro at €9/month includes 50 dynamic codes with full analytics. Business at €29/month includes 500 codes and bulk creation of up to 1,000 at once. Neither the Pro nor Business plan caps QR code redirects by scan volume.',
+ 'Bitly\'s free plan allows only 1 QR code. Their Core plan (~$10/month) allows 2 QR codes. Higher plans add more codes but pricing escalates steeply. QR Master\'s free plan includes 3 active dynamic QR codes and unlimited static codes. Pro at €9/month includes 50 dynamic codes with full analytics. Business at €29/month includes 500 codes and bulk creation of up to 1,000 static codes, or up to 500 dynamic ones at once. Neither the Pro nor Business plan caps QR code redirects by scan volume.',
},
{
question: 'Does QR Master have link shortening like Bitly?',
@@ -92,12 +92,12 @@ const faqItems = [
{
question: 'Can I create QR codes in bulk on QR Master in a way Bitly can\'t?',
answer:
- 'Yes. QR Master Business (€29/month) supports CSV and Excel upload for bulk creation of up to 1,000 unique QR codes per batch. Each code in the batch can have a different destination URL, label, campaign name, and UTM parameters. Bitly does not offer bulk QR creation at any plan tier.',
+ 'Yes. QR Master Business (€29/month) supports CSV and Excel upload for bulk creation: up to 1,000 static codes, or up to 500 dynamic ones per batch. Each code in the batch can have a different destination URL, label, campaign name, and UTM parameters. Bitly does not offer bulk QR creation at any plan tier.',
},
{
question: 'Does QR Master comply with GDPR for scan analytics?',
answer:
- 'Yes. QR Master hashes IP addresses server-side before any scan data is stored. No raw IP address is ever written to the database. Analytics capture device type, scan time, country-level location, and UTM parameters — all without storing personally identifiable information. This is built into the infrastructure and applies from the free plan upward. Bitly is a US company with its own analytics approach — EU businesses should review their DPA for GDPR compliance.',
+ 'Yes. QR Master hashes IP addresses server-side before any scan data is stored. No raw IP address is ever written to the database. Analytics capture device type, scan time, country-level location, and UTM parameters - all without storing personally identifiable information. This is built into the infrastructure and applies from the free plan upward. Bitly is a US company with its own analytics approach - EU businesses should review their DPA for GDPR compliance.',
},
{
question: 'What happens to my Bitly QR codes if I cancel?',
@@ -131,21 +131,21 @@ const relatedLinks = [
href: '/dynamic-qr-code-generator',
title: 'Dynamic QR Code Generator',
description:
- 'Create QR codes built specifically for print campaigns, menus, packaging, and events — with no scan limits and updateable destinations.',
+ 'Create QR codes built specifically for print campaigns, menus, packaging, and events - with no scan limits and updateable destinations.',
ctaLabel: 'Create your first QR code',
},
{
href: '/qr-code-analytics',
title: 'QR Code Analytics',
description:
- 'See scan counts, device types, locations, and UTM attribution for every QR code — with no caps and no upgrades required to see your own data.',
+ 'See scan counts, device types, locations, and UTM attribution for every QR code - with no caps and no upgrades required to see your own data.',
ctaLabel: 'Explore analytics',
},
{
href: '/bulk-qr-code-generator',
title: 'Bulk QR Code Generator',
description:
- 'Create up to 1,000 unique QR codes from a CSV or Excel file. Each with its own URL, label, and tracking parameters. No manual creation one-by-one.',
+ 'One upload, a whole batch: up to 1,000 static codes or up to 500 dynamic ones. Each with its own URL, label, and tracking parameters. No creating them one by one.',
ctaLabel: 'Explore bulk QR creation',
},
{
@@ -186,18 +186,18 @@ export default function BitlyAlternativePage() {
A Bitly Alternative That Actually Lets You Create QR Codes
- Bitly is a link shortener. QR codes are a secondary feature — and their Core plan charges $10/month
+ Bitly is a link shortener. QR codes are a secondary feature - and their Core plan charges $10/month
for just 2 QR codes total. QR Master is built specifically for QR code management: 50 dynamic codes
at €9/month, bulk creation, and GDPR-compliant analytics.
{[
- '50 dynamic QR codes at €9/month — not 2 codes for $10',
+ '50 dynamic QR codes at €9/month - not 2 codes for $10',
'Free plan: 3 dynamic QR codes + unlimited static codes, €0',
- 'Bulk creation from CSV/Excel up to 1,000 codes (Business plan)',
+ 'Bulk creation from CSV/Excel: 1,000 static or 500 dynamic (Business)',
'Built for QR-specific workflows: menus, packaging, events, campaigns',
- 'GDPR-compliant analytics with hashed IPs — no configuration needed',
+ 'GDPR-compliant analytics with hashed IPs - no configuration needed',
].map((feature) => (
- Bitly Core — ~$10/month
+ Bitly Core - ~$10/month
2 QR codes
@@ -258,11 +258,11 @@ export default function BitlyAlternativePage() {
))}
- Marketed as "unlimited scans" — but you only get 2 codes total. Need a third? Upgrade.
+ Marketed as "unlimited scans" - but you only get 2 codes total. Need a third? Upgrade.
- QR Master Pro — €9/month
+ QR Master Pro - €9/month
50 QR codes
@@ -333,25 +333,25 @@ export default function BitlyAlternativePage() {
Bitly is excellent at what it was designed for: shortening URLs for social media posts, email
campaigns, and marketing links where you need a clean, short address. That core product is solid and
widely used. The problem starts when QR codes get added as a secondary feature inside a link
- management tool — the pricing model and workflow both reflect the link-first design.
+ management tool - the pricing model and workflow both reflect the link-first design.
The most glaring issue with Bitly for QR codes is the code count cap. Bitly's Core plan
- (~$10/month) is marketed around "unlimited clicks and scans" — which sounds generous.
- But that plan allows a total of 2 QR codes . Two. If you need a third QR code —
- for a second product, a second location, or a second campaign — you have to jump to a more expensive
+ (~$10/month) is marketed around "unlimited clicks and scans" - which sounds generous.
+ But that plan allows a total of 2 QR codes . Two. If you need a third QR code -
+ for a second product, a second location, or a second campaign - you have to jump to a more expensive
plan. For teams running any meaningful QR code operation, the code count wall is the first thing
you hit, not the scan volume.
The free plan allows exactly 1 QR code. For comparison, QR Master's free tier gives you 3
- active dynamic QR codes with basic analytics — and the Pro plan (€9/month) gives you 50. The
+ active dynamic QR codes with basic analytics - and the Pro plan (€9/month) gives you 50. The
economics of QR codes on Bitly force rapid upgrades the moment you have a campaign with more than
a trivial number of placements.
Beyond the code count, Bitly's QR workflow is an afterthought. The interface is built around
- link management — creating a short link is the primary action, and QR codes are generated as a
+ link management - creating a short link is the primary action, and QR codes are generated as a
secondary output from that. There is no bulk QR creation, no QR-specific analytics beyond click
counts, and no purpose-built tooling for the workflows that QR codes actually live in: restaurant
menus, product packaging, event programs, multi-location flyer campaigns.
@@ -363,8 +363,8 @@ export default function BitlyAlternativePage() {
>
When Bitly is still the right choice
- If you already use Bitly heavily for link shortening and genuinely need only 1–2 QR codes with no
- expectation of growth — staying on Bitly is reasonable. Consolidating tools has value. The problem
+ If you already use Bitly heavily for link shortening and genuinely need only 1-2 QR codes with no
+ expectation of growth - staying on Bitly is reasonable. Consolidating tools has value. The problem
starts the moment QR codes become a real part of your workflow, you need more than 2 codes, or you
need bulk creation. At that point the pricing math stops making sense.
@@ -433,15 +433,15 @@ export default function BitlyAlternativePage() {
Code count limits and pricing at scale
- Bitly's Core plan (~$10/month) advertises "unlimited scans" — which is technically
+ Bitly's Core plan (~$10/month) advertises "unlimited scans" - which is technically
accurate but misleading. The hard limit on that plan is the number of QR codes: 2 total. The free
plan gives you 1. For most marketing use cases, running into the code count wall happens before
scan volume ever becomes an issue.
QR Master Pro (€9/month) gives you 50 active dynamic QR codes with no scan limits on redirects.
- Business (€29/month) gives you 500 codes plus bulk creation of up to 1,000 unique codes from a
- single CSV upload. The pricing model is built around QR code management, not link click volume —
+ Business (€29/month) gives you 500 dynamic codes plus bulk creation of up to 1,000 static codes from a
+ single CSV upload. The pricing model is built around QR code management, not link click volume -
which means costs are predictable and don't scale with campaign success.
@@ -449,14 +449,14 @@ export default function BitlyAlternativePage() {
QR-specific workflow support
- QR Master is designed around QR code workflows — not link management. This means the platform has
+ QR Master is designed around QR code workflows - not link management. This means the platform has
purpose-built generators for specific QR code types: WiFi QR codes, vCard QR codes, restaurant menu
QR codes, PDF QR codes, and more. Each type has a tailored input form and generates the correct QR
- format for that use case. Bitly generates a URL-based QR code — that's the only type available.
+ format for that use case. Bitly generates a URL-based QR code - that's the only type available.
Bulk creation is another gap. If you are creating QR codes for a product line, an event with
- multiple sessions, or a direct mail campaign — creating them one at a time is not viable. QR
+ multiple sessions, or a direct mail campaign - creating them one at a time is not viable. QR
Master's Business plan generates up to 1,000 unique codes from a single CSV upload. Bitly has
no equivalent.
@@ -465,7 +465,7 @@ export default function BitlyAlternativePage() {
Analytics depth for QR use cases
- Bitly tracks clicks — that is its core analytics model. For QR codes, it reports scan counts in
+ Bitly tracks clicks - that is its core analytics model. For QR codes, it reports scan counts in
the same way it reports link clicks. There is no device-type breakdown specific to mobile QR
scanning, no distinction between campaign placements, and no UTM parameter injection designed
for QR workflows.
@@ -473,7 +473,7 @@ export default function BitlyAlternativePage() {
QR Master analytics are built around the QR scan as the unit of measurement. Each scan records
device type, operating system, country-level location, time, and UTM parameters. The dashboard is
- organized around QR codes — not links — so you can see scan patterns per code, per campaign, and
+ organized around QR codes - not links - so you can see scan patterns per code, per campaign, and
over time in a way that makes sense for print and physical media distribution.
@@ -494,7 +494,7 @@ export default function BitlyAlternativePage() {
{[
'Anyone who hit Bitly\'s code count cap and had to upgrade just to add a third QR code',
'Marketing teams needing more than 2 QR codes for under $50/month',
- 'Teams creating QR codes for product packaging, event programs, or retail displays — use cases Bitly has no specific tooling for',
+ 'Teams creating QR codes for product packaging, event programs, or retail displays - use cases Bitly has no specific tooling for',
'Anyone needing bulk QR creation from CSV or Excel',
'EU businesses that need GDPR-compliant tracking without extra configuration',
].map((item) => (
@@ -512,7 +512,7 @@ export default function BitlyAlternativePage() {
{[
- 'You use Bitly primarily for link shortening and genuinely need only 1–2 QR codes — the cost of a separate QR tool doesn\'t justify the switch',
+ 'You use Bitly primarily for link shortening and genuinely need only 1-2 QR codes - the cost of a separate QR tool doesn\'t justify the switch',
'You are already on a Bitly enterprise plan and QR codes are a minor part of a broader link management workflow that uses other Bitly features heavily',
'Your QR code count stays within Bitly\'s plan limits and you have no bulk creation needs',
].map((item) => (
@@ -567,7 +567,7 @@ export default function BitlyAlternativePage() {
{
step: '5',
title: 'Plan physical material replacement',
- body: 'For anything printed — flyers, packaging, business cards, menus — plan replacement into your next print run. Keep your Bitly account active until all physical materials are replaced in circulation.',
+ body: 'For anything printed - flyers, packaging, business cards, menus - plan replacement into your next print run. Keep your Bitly account active until all physical materials are replaced in circulation.',
},
].map((step) => (
@@ -596,7 +596,7 @@ export default function BitlyAlternativePage() {
50 QR codes at €9. Not 2 codes for $10.
Start free with 3 dynamic QR codes. Pro at €9/month for 50 codes with full analytics.
- Purpose-built for QR code workflows — not link shortening with QR as an afterthought.
+ Purpose-built for QR code workflows - not link shortening with QR as an afterthought.
Flowcode's free tier puts their logo on every QR code and routes your scanners through a
- Flowcode-branded interstitial page. QR Master sends scanners directly to your destination — no
+ Flowcode-branded interstitial page. QR Master sends scanners directly to your destination - no
third-party branding, no interstitials, at any plan level.
{[
- 'No Flowcode branding on your QR codes — even on the free plan',
- 'No branded interstitial page — instant redirect, no Flowcode marketing in between',
- 'Custom colors and logo from Pro (€9/month)',
- 'Bulk creation up to 1,000 codes on the Business plan',
- 'GDPR-compliant analytics with hashed IPs — built in, not a setting',
+ 'No Flowcode branding on your QR codes - even on the free plan',
+ 'No branded interstitial page - instant redirect, no Flowcode marketing in between',
+ 'Colors free, shapes and logo from Pro (€9/month)',
+ 'Bulk creation on Business: 1,000 static or 500 dynamic',
+ 'GDPR-compliant analytics with hashed IPs - built in, not a setting',
].map((feature) => (
Flowcode is a well-built product with strong design capabilities. The problem is not the product
- itself — it's the business model on the free tier. Flowcode monetizes free users by using their QR
+ itself - it's the business model on the free tier. Flowcode monetizes free users by using their QR
code placements as advertising inventory for Flowcode's own brand.
In practice, this means two things. First, the QR codes generated on the free plan are visually styled
- as Flowcode products — the distinctive round design with Flowcode design elements makes it clear to
+ as Flowcode products - the distinctive round design with Flowcode design elements makes it clear to
anyone familiar with the space that this is a Flowcode code, not a custom QR. If you're a
restaurant, a brand, or an agency putting this code on client materials, it is your placement that
Flowcode is using to advertise itself.
- Second — and more consequentially — Flowcode's free tier routes every scan through an interstitial
+ Second - and more consequentially - Flowcode's free tier routes every scan through an interstitial
page before the scanner reaches your destination. That page carries Flowcode branding. You are sending
- customers to your menu, product page, or campaign — but they pass through Flowcode's branded
+ customers to your menu, product page, or campaign - but they pass through Flowcode's branded
experience first. The customer's first impression is Flowcode, not you.
@@ -342,8 +342,8 @@ export default function FlowcodeAlternativePage() {
How QR Master handles this
QR Master does not apply third-party branding to QR codes at any plan level. The free tier generates
- standard QR codes — without a QR Master logo, without a forced visual style, and without an interstitial
- page. Scanners go directly to your destination. Custom colors and logo embedding are available on Pro
+ standard QR codes - without a QR Master logo, without a forced visual style, and without an interstitial
+ page. Scanners go directly to your destination. Colors are free on every plan. Module shapes and logo embedding start on Pro
(€9/month). White-label and your brand are the baseline, not an upgrade.
@@ -411,14 +411,14 @@ export default function FlowcodeAlternativePage() {
Branding control
- Flowcode's free QR codes are visually distinct — the rounded, branded design is recognizable.
+ Flowcode's free QR codes are visually distinct - the rounded, branded design is recognizable.
If you are an agency, a restaurant, or a brand putting these on client materials, the Flowcode
- aesthetic tells your audience that this is a Flowcode product. White-label — where the code looks
- like yours — requires a paid plan.
+ aesthetic tells your audience that this is a Flowcode product. White-label - where the code looks
+ like yours - requires a paid plan.
QR Master generates standard QR codes on all plans. On the free plan, you get a clean standard QR.
- On Pro (€9/month), you add your brand colors and logo to the center of the code. The baseline is
+ Brand colors are free on every plan. On Pro (€9/month), you add module shapes and your logo in the center of the code. The baseline is
always a neutral code that belongs to your brand, not ours.
@@ -427,17 +427,17 @@ export default function FlowcodeAlternativePage() {
Scan experience and interstitials
The interstitial is a real issue for anyone using QR codes in a customer-facing context. A scanner
- at a restaurant table or on a product package is primed to go directly to the destination — a menu,
+ at a restaurant table or on a product package is primed to go directly to the destination - a menu,
a product page, a contact form. An intermediate page breaks that expectation, even if it only lasts
a second or two. It's also a branding signal: Flowcode appears in the path between your brand
and your customer.
QR Master does not show a branded interstitial page. Like any dynamic QR platform, the redirect
- runs through QR Master's servers (qrmaster.net) to log the scan — but the scanner sees no marketing
+ runs through QR Master's servers (qrmaster.net) to log the scan - but the scanner sees no marketing
content, no QR Master splash page, and no dwell-time promotion. It processes the scan and forwards
- immediately. The visual and branding of the QR code itself is fully customizable — colors, logo,
- shape — without any QR Master identity imposed on it.
+ immediately. The visual and branding of the QR code itself is fully customizable - colors, logo,
+ shape - without any QR Master identity imposed on it.
@@ -472,7 +472,7 @@ export default function FlowcodeAlternativePage() {
'Brands that want QR codes to reflect their identity, not a third-party platform',
'Agencies putting QR codes on client materials who can\'t have Flowcode branding visible',
'EU businesses that need GDPR-compliant scan tracking without configuration',
- 'Teams that need bulk QR creation — Flowcode has no bulk generation tool',
+ 'Teams that need bulk QR creation - Flowcode has no bulk generation tool',
'Anyone priced out of Flowcode\'s white-label tier but needing clean, functional QR codes',
].map((item) => (
@@ -540,7 +540,7 @@ export default function FlowcodeAlternativePage() {
{
step: '5',
title: 'Plan the physical reprint',
- body: 'For printed materials — menus, flyers, packaging — plan the replacement into your next natural reprint cycle. Keep your Flowcode account active until the reprint is done and distributed.',
+ body: 'For printed materials - menus, flyers, packaging - plan the replacement into your next natural reprint cycle. Keep your Flowcode account active until the reprint is done and distributed.',
},
].map((step) => (
diff --git a/src/app/(main)/(marketing)/alternatives/qr-code-generator/page.tsx b/src/app/(main)/(marketing)/alternatives/qr-code-generator/page.tsx
index 29ad41e..a4ee13d 100644
--- a/src/app/(main)/(marketing)/alternatives/qr-code-generator/page.tsx
+++ b/src/app/(main)/(marketing)/alternatives/qr-code-generator/page.tsx
@@ -14,17 +14,17 @@ const competitor = competitors['qr-code-generator'];
export const metadata: Metadata = {
title: {
- absolute: 'QR-Code-Generator.com Alternative – No Bait and Switch | QR Master',
+ absolute: 'QR-Code-Generator.com Alternative - No Bait and Switch | QR Master',
},
description:
- 'Looking for a QR-Code-Generator.com alternative? QR Master gives you 3 truly free dynamic QR codes — no trial that expires mid-campaign, no forced annual contracts. Transparent pricing from €0.',
+ 'Looking for a QR-Code-Generator.com alternative? QR Master gives you 3 truly free dynamic QR codes - no trial that expires mid-campaign, no forced annual contracts. Transparent pricing from €0.',
keywords:
'qr-code-generator.com alternative, alternative to qr code generator, qr code generator free expired, dynamic qr code deactivated, qr code bait switch',
alternates: {
canonical: 'https://www.qrmaster.net/alternatives/qr-code-generator',
},
openGraph: {
- title: 'QR-Code-Generator.com Alternative – No Bait and Switch',
+ title: 'QR-Code-Generator.com Alternative - No Bait and Switch',
description:
'Your dynamic QR code stopped working after two weeks? QR Master offers 3 permanently free dynamic codes, honest pricing, and no hidden trial timers.',
url: 'https://www.qrmaster.net/alternatives/qr-code-generator',
@@ -32,7 +32,7 @@ export const metadata: Metadata = {
images: ['/og-image.png'],
},
twitter: {
- title: 'QR-Code-Generator.com Alternative – No Bait and Switch',
+ title: 'QR-Code-Generator.com Alternative - No Bait and Switch',
description:
'Your dynamic QR code stopped working after two weeks? QR Master offers 3 permanently free dynamic codes and honest pricing.',
},
@@ -44,37 +44,37 @@ const faqItems = [
{
question: 'Why did my dynamic QR code from QR-Code-Generator.com stop working?',
answer:
- 'QR-Code-Generator.com offers dynamic QR codes on a free trial basis — typically around 14 days. After the trial ends, the code is deactivated. Your printed materials (flyers, menus, packaging) become dead ends. To reactivate, they require purchasing an annual subscription. QR Master does not do this: the 3 free dynamic codes on our free plan stay active as long as your account exists.',
+ 'QR-Code-Generator.com offers dynamic QR codes on a free trial basis - typically around 14 days. After the trial ends, the code is deactivated. Your printed materials (flyers, menus, packaging) become dead ends. To reactivate, they require purchasing an annual subscription. QR Master does not do this: the 3 free dynamic codes on our free plan stay active as long as your account exists.',
},
{
question: 'Can I switch from QR-Code-Generator.com without reprinting everything?',
answer:
- 'Dynamic QR codes cannot be migrated directly because the destination URL is encoded into the QR code image itself — each provider uses their own redirect infrastructure. If you are still within the deactivation period, create new dynamic codes in QR Master immediately and update your placements (digital ones) or plan your next reprint run. Static QR codes you created on QR-Code-Generator.com remain permanently valid regardless of your subscription status.',
+ 'Dynamic QR codes cannot be migrated directly because the destination URL is encoded into the QR code image itself - each provider uses their own redirect infrastructure. If you are still within the deactivation period, create new dynamic codes in QR Master immediately and update your placements (digital ones) or plan your next reprint run. Static QR codes you created on QR-Code-Generator.com remain permanently valid regardless of your subscription status.',
},
{
question: 'What does QR Master give me for free, permanently?',
answer:
- 'The QR Master free plan includes 3 active dynamic QR codes with basic scan tracking and unlimited static QR codes — no trial period, no credit card required, no expiration. The 3 dynamic codes are always active. If you need more, Pro starts at €9/month for 50 dynamic codes with full analytics.',
+ 'The QR Master free plan includes 3 active dynamic QR codes with basic scan tracking and unlimited static QR codes - no trial period, no credit card required, no expiration. The 3 dynamic codes are always active. If you need more, Pro starts at €9/month for 50 dynamic codes with full analytics.',
},
{
question: 'Is the free plan at QR Master really free, or will it expire like QR-Code-Generator.com?',
answer:
- 'The free tier is permanently free within the defined limits. There is no 14-day clock, no activation fee, no "trial" framing. The 3 dynamic codes on the free plan continue working as long as your account is active. We make money from Pro (€9/month) and Business (€29/month) upgrades — not from deactivating free users after they\'ve already printed materials.',
+ 'The free tier is permanently free within the defined limits. There is no 14-day clock, no activation fee, no "trial" framing. The 3 dynamic codes on the free plan continue working as long as your account is active. We make money from Pro (€9/month) and Business (€29/month) upgrades - not from deactivating free users after they\'ve already printed materials.',
},
{
question: 'Does QR Master comply with GDPR for scan analytics?',
answer:
- 'Yes. QR Master anonymizes IP addresses using server-side hashing with a salt before any analytics data is stored. No personally identifiable IP addresses are recorded. Scan data includes device type, time, country-level location, and UTM parameters — all without storing raw IPs. This is built into the platform, not a bolt-on option.',
+ 'Yes. QR Master anonymizes IP addresses using server-side hashing with a salt before any analytics data is stored. No personally identifiable IP addresses are recorded. Scan data includes device type, time, country-level location, and UTM parameters - all without storing raw IPs. This is built into the platform, not a bolt-on option.',
},
{
question: 'What happens to my QR codes if I cancel my QR Master subscription?',
answer:
- 'If you downgrade from a paid plan to Free, your dynamic codes are paused (not deleted) if you exceed the 3-code free limit. You choose which 3 to keep active. Static codes are unaffected and remain permanently valid. If you close your account entirely, dynamic codes stop redirecting — which is why we recommend switching to static QR codes for any permanent materials that you cannot update.',
+ 'If you downgrade from a paid plan to Free, your dynamic codes are paused (not deleted) if you exceed the 3-code free limit. You choose which 3 to keep active. Static codes are unaffected and remain permanently valid. If you close your account entirely, dynamic codes stop redirecting - which is why we recommend switching to static QR codes for any permanent materials that you cannot update.',
},
{
question: 'Does QR Master support bulk QR code creation?',
answer:
- 'Yes. The Business plan (€29/month) includes bulk creation via CSV or Excel upload — up to 1,000 unique QR codes per batch. Each code can have a different destination URL, label, and UTM parameters. QR-Code-Generator.com does not offer bulk creation at any tier.',
+ 'Yes. The Business plan (€29/month) includes bulk creation via CSV or Excel upload: up to 1,000 static codes, or up to 500 dynamic ones per batch. Each code can have a different destination URL, label, and UTM parameters. QR-Code-Generator.com does not offer bulk creation at any tier.',
},
];
@@ -110,7 +110,7 @@ const relatedLinks = [
href: '/qr-code-analytics',
title: 'QR Code Analytics',
description:
- 'See which placements drive scans, which devices your audience uses, and where your codes are being scanned — all in one dashboard.',
+ 'See which placements drive scans, which devices your audience uses, and where your codes are being scanned - all in one dashboard.',
ctaLabel: 'Explore QR code analytics',
},
{
@@ -158,17 +158,17 @@ export default function QRCodeGeneratorAlternativePage() {
- QR-Code-Generator.com deactivates dynamic QR codes after roughly two weeks — right after you've
+ QR-Code-Generator.com deactivates dynamic QR codes after roughly two weeks - right after you've
printed the flyers. QR Master gives you 3 free dynamic codes that stay active permanently.
No hidden trial, no forced annual contract.
{[
- '3 permanently active dynamic QR codes — free forever',
+ '3 permanently active dynamic QR codes - free forever',
'Transparent pricing: Pro at €9/mo, cancel anytime',
'GDPR-compliant analytics with hashed IPs, built in',
- 'Bulk creation up to 1,000 codes (Business plan)',
+ 'Bulk creation: 1,000 static or 500 dynamic (Business plan)',
].map((item) => (
- Start Free — No Credit Card
+ Start Free - No Credit Card
- QR-Code-Generator.com markets dynamic QR codes as free to create. And they are — for about two weeks.
+ QR-Code-Generator.com markets dynamic QR codes as free to create. And they are - for about two weeks.
After roughly 14 days, those dynamic codes stop redirecting. Anyone who scans them sees a dead page
or a prompt to upgrade.
@@ -303,7 +303,7 @@ export default function QRCodeGeneratorAlternativePage() {
Hundreds of reviews on Trustpilot describe feeling “trapped” because the alternative
- — replacing all the printed materials — is more expensive. The annual plan costs around
+ - replacing all the printed materials - is more expensive. The annual plan costs around
€25.99/month billed yearly, over €300 upfront.
@@ -320,7 +320,7 @@ export default function QRCodeGeneratorAlternativePage() {
no countdown, no automatic deactivation.
- If you need more than 3, you upgrade to Pro at €9/month — month-to-month with no forced annual
+ If you need more than 3, you upgrade to Pro at €9/month - month-to-month with no forced annual
commitment. Static codes are unlimited and free forever.
@@ -396,7 +396,7 @@ export default function QRCodeGeneratorAlternativePage() {
- {/* Detailed Comparisons — 3 cards */}
+ {/* Detailed Comparisons - 3 cards */}
@@ -407,7 +407,7 @@ export default function QRCodeGeneratorAlternativePage() {
{[
{
label: 'Pricing transparency',
- body: 'QR-Code-Generator.com lists dynamic codes as free — true for the 14-day trial. Reactivation requires an annual plan billed upfront (€100–€300+).',
+ body: 'QR-Code-Generator.com lists dynamic codes as free - true for the 14-day trial. Reactivation requires an annual plan billed upfront (€100-€300+).',
highlight: 'QR Master pricing is explicit: Free for 3 dynamic codes, Pro at €9/month. No fine print about trial periods or forced billing cycles.',
accent: '#D97706',
},
@@ -456,7 +456,7 @@ export default function QRCodeGeneratorAlternativePage() {
},
{
title: 'Create a free QR Master account',
- body: 'Sign up at qrmaster.net — no credit card required. The free plan gives you 3 active dynamic QR codes immediately.',
+ body: 'Sign up at qrmaster.net - no credit card required. The free plan gives you 3 active dynamic QR codes immediately.',
},
{
title: 'Re-create your dynamic codes',
@@ -464,7 +464,7 @@ export default function QRCodeGeneratorAlternativePage() {
},
{
title: 'Update digital placements first',
- body: 'Replace the QR code image on your website, email signatures, social media, and digital ads — no reprinting needed.',
+ body: 'Replace the QR code image on your website, email signatures, social media, and digital ads - no reprinting needed.',
},
{
title: 'Plan your reprint cycle',
@@ -531,7 +531,7 @@ export default function QRCodeGeneratorAlternativePage() {
{[
- 'You only need static QR codes — both platforms generate these for free, and static codes never expire',
+ 'You only need static QR codes - both platforms generate these for free, and static codes never expire',
'You need one quick QR code for a presentation or digital-only use where deactivation doesn\'t matter',
'You\'re already on an active annual plan and aren\'t printing new materials anytime soon',
].map((item, idx) => (
diff --git a/src/app/(main)/(marketing)/authors/[slug]/page.tsx b/src/app/(main)/(marketing)/authors/[slug]/page.tsx
index 091ab05..3594cbd 100644
--- a/src/app/(main)/(marketing)/authors/[slug]/page.tsx
+++ b/src/app/(main)/(marketing)/authors/[slug]/page.tsx
@@ -6,22 +6,22 @@ import { getAuthorBySlug, getPostsByAuthor } from "@/lib/content";
import { authors } from "@/lib/author-data";
import { authorPageSchema } from "@/lib/schema";
-export function generateMetadata({ params }: { params: { slug: string } }) {
- const author = getAuthorBySlug(params.slug);
- if (!author) return {};
- return {
- title: {
- absolute: `${author.name} - ${author.role}`,
- },
- description: author.bio,
- alternates: {
- canonical: `https://www.qrmaster.net/authors/${author.slug}`,
- },
- openGraph: {
- url: `https://www.qrmaster.net/authors/${author.slug}`,
- },
- };
-}
+export function generateMetadata({ params }: { params: { slug: string } }) {
+ const author = getAuthorBySlug(params.slug);
+ if (!author) return {};
+ return {
+ title: {
+ absolute: `${author.name} - ${author.role}`,
+ },
+ description: author.bio,
+ alternates: {
+ canonical: `https://www.qrmaster.net/authors/${author.slug}`,
+ },
+ openGraph: {
+ url: `https://www.qrmaster.net/authors/${author.slug}`,
+ },
+ };
+}
export function generateStaticParams() {
return authors.map((author) => ({
@@ -59,7 +59,7 @@ export default function AuthorPage({ params }: { params: { slug: string } }) {
{author.name}
-
+
{author.role}
{author.bio}
diff --git a/src/app/(main)/(marketing)/bulk-qr-code-generator/page.tsx b/src/app/(main)/(marketing)/bulk-qr-code-generator/page.tsx
index 768280d..8181c40 100644
--- a/src/app/(main)/(marketing)/bulk-qr-code-generator/page.tsx
+++ b/src/app/(main)/(marketing)/bulk-qr-code-generator/page.tsx
@@ -14,10 +14,10 @@ import { MarketingPageTracker } from '@/components/marketing/MarketingAnalytics'
export const metadata: Metadata = {
title: {
- absolute: 'Bulk QR Code Generator for Excel, CSV and Google Sheets',
+ absolute: 'Bulk QR Code Generator - Static or Dynamic, from Excel',
},
description:
- 'Generate up to 1,000 QR codes from Excel, CSV, XLSX, or exported Google Sheets data. Upload, preview, batch-create, download ZIP files, or save to your dashboard.',
+ 'Upload a CSV or Excel file and get up to 1,000 static QR codes at once, or up to 500 trackable dynamic ones on Business. Preview every row before you generate.',
keywords:
'bulk qr code generator, bulk qr code generator excel, batch qr code generator, qr code from excel, csv qr code generator, bulk qr generator, bulk qr code generator in google sheets, spreadsheet qr generation',
alternates: {
@@ -28,17 +28,17 @@ export const metadata: Metadata = {
},
},
openGraph: {
- title: 'Bulk QR Code Generator for Excel, CSV and Google Sheets',
+ title: 'Bulk QR Code Generator - Static or Dynamic, from Excel',
description:
- 'Generate up to 1,000 QR codes from CSV, Excel, XLSX, or exported Google Sheets data.',
+ 'One spreadsheet in, a full batch out. Up to 1,000 static codes, or up to 500 trackable dynamic ones.',
url: 'https://www.qrmaster.net/bulk-qr-code-generator',
type: 'website',
images: ['/og-image.png'],
},
twitter: {
- title: 'Bulk QR Code Generator for Excel, CSV and Google Sheets',
+ title: 'Bulk QR Code Generator - Static or Dynamic, from Excel',
description:
- 'Generate up to 1,000 QR codes from CSV, Excel, XLSX, or exported Google Sheets data.',
+ 'One spreadsheet in, a full batch out. Up to 1,000 static codes, or up to 500 trackable dynamic ones.',
},
};
@@ -59,9 +59,9 @@ const featureCards = [
'The current bulk creation flow limits each upload to 1,000 rows so the batch stays predictable and reviewable.',
},
{
- title: 'Static QR output',
+ title: 'Static or dynamic output',
description:
- 'Bulk creation currently generates static QR codes. These codes do not include post-print editing or tracking.',
+ 'Choose per upload. Static for large print batches. Dynamic when the destination has to stay editable and the scans have to be countable.',
},
{
title: 'ZIP download',
@@ -117,13 +117,13 @@ const useCases = [
{
title: 'Product labels and inserts',
description:
- 'Generate large static batches for packaging, inserts, manuals, or support labels when every unit needs a QR code.',
+ 'Generate a batch for packaging, inserts, manuals, or support labels. Static when the link is permanent, dynamic when the linked page will change.',
points: ['One spreadsheet as input', 'Consistent file naming', 'Printable SVG output'],
},
{
title: 'Event materials',
description:
- 'Produce batches for badges, handouts, booth materials, or attendee resources when a static QR is enough.',
+ 'Produce batches for badges, handouts, booth materials, or attendee resources. Use dynamic codes when the event page changes after print.',
points: ['Batch generation from one file', 'Preview before generation', 'Download everything together'],
},
{
@@ -149,7 +149,7 @@ const faqItems = [
{
question: 'Are bulk-generated QR codes dynamic or trackable?',
answer:
- 'No. The current bulk creation flow generates static QR codes, so those codes do not include post-print editing or tracking.',
+ 'Both are available. Static is the default and runs up to 1,000 rows per upload. Dynamic codes stay editable after print and are trackable, capped at 500 on Business by your dynamic code allowance.',
},
{
question: 'What file formats can I upload?',
@@ -194,12 +194,12 @@ const softwareSchema = {
availability: 'https://schema.org/InStock',
},
description:
- 'Generate up to 1,000 static QR codes from CSV, Excel, XLSX, or exported Google Sheets files in the QR Master Business plan.',
+ 'Generate up to 1,000 static QR codes, or up to 500 trackable dynamic codes, from CSV, Excel, XLSX, or exported Google Sheets files on the QR Master Business plan.',
featureList: [
'CSV, XLS, and XLSX upload',
'Excel and Google Sheets CSV export workflow',
'Up to 1,000 rows per upload',
- 'Static QR code generation',
+ 'Static or dynamic QR code generation',
'ZIP download of generated SVG files',
'Optional save-to-dashboard step',
],
@@ -321,7 +321,7 @@ export default function BulkQRCodeGeneratorPage() {
Stop mapping spreadsheet rows to QR codes by hand. Upload a
CSV or Excel file, preview every code, and download up to
- 1,000 print-ready static codes in minutes.
+ 1,000 print-ready codes in minutes - static, or dynamic when you need to edit and track them later.
@@ -329,7 +329,7 @@ export default function BulkQRCodeGeneratorPage() {
{[
'CSV, XLS, and XLSX upload',
'Up to 1,000 rows per upload',
- 'Static QR code output',
+ 'Static or dynamic output per upload',
'ZIP download and optional save to dashboard',
].map((feature) => (
@@ -384,7 +384,7 @@ export default function BulkQRCodeGeneratorPage() {
))}
- Designed for bulk static output, not dynamic tracking.
+ Static for large print batches. Dynamic when you need tracking.
@@ -397,9 +397,9 @@ export default function BulkQRCodeGeneratorPage() {
Generate bulk QR codes without one-by-one setup
- Use the Business-plan bulk flow when you need a large static QR batch from a single spreadsheet.
+ One spreadsheet in, a full batch out. Static up to 1,000, or dynamic and trackable up to 500 on Business.
diff --git a/src/app/(main)/(marketing)/compare/[slug]/page.tsx b/src/app/(main)/(marketing)/compare/[slug]/page.tsx
index acd33c0..1362405 100644
--- a/src/app/(main)/(marketing)/compare/[slug]/page.tsx
+++ b/src/app/(main)/(marketing)/compare/[slug]/page.tsx
@@ -94,9 +94,12 @@ export function generateMetadata({ params }: PageProps): Metadata {
follow: true,
},
icons: {
- icon: [{ url: "/favicon1.png", type: "image/png" }],
- shortcut: "/favicon1.png",
- apple: "/favicon1.png",
+ icon: [
+ { url: "/favicon.svg", type: "image/svg+xml" },
+ { url: "/favicon.ico", sizes: "16x16 32x32", type: "image/x-icon" },
+ ],
+ shortcut: "/favicon.ico",
+ apple: "/logo.svg",
},
openGraph: {
title,
diff --git a/src/app/(main)/(marketing)/custom-qr-code-generator/page.tsx b/src/app/(main)/(marketing)/custom-qr-code-generator/page.tsx
index 28a8229..2cfe7be 100644
--- a/src/app/(main)/(marketing)/custom-qr-code-generator/page.tsx
+++ b/src/app/(main)/(marketing)/custom-qr-code-generator/page.tsx
@@ -1,4 +1,4 @@
-import { getAggregateRating } from '@/lib/testimonial-data';
+import { getAggregateRating } from '@/lib/testimonial-data';
import React from 'react';
import type { Metadata } from 'next';
import Link from 'next/link';
@@ -26,8 +26,8 @@ import {
import { MiniGenerator } from '@/components/marketing/MiniGenerator';
export const metadata: Metadata = {
- title: 'Free Custom QR Code Generator with Logo & Colors',
- description: 'Create custom QR codes with your logo, brand colors, and unique frames. Free designer with instant preview. Download PNG/SVG. No signup needed to try.',
+ title: 'Custom QR Code Generator with Logo & Brand Colors',
+ description: 'Put your logo and brand colors into the code itself. Live preview as you design, print-ready SVG export that stays sharp at any size. No signup needed to try.',
keywords: [
'custom qr code generator',
'qr code with logo',
@@ -337,7 +337,7 @@ export default function CustomQRCodeGeneratorPage() {
{
href: '/tools/barcode-generator',
title: 'Free Barcode Generator',
- description: 'Need a 1D barcode for retail or inventory? Create EAN-13, UPC-A, and Code 128 barcodes instantly — no signup required.',
+ description: 'Need a 1D barcode for retail or inventory? Create EAN-13, UPC-A, and Code 128 barcodes instantly - no signup required.',
ctaLabel: 'Create a barcode',
},
{
@@ -369,7 +369,7 @@ export default function CustomQRCodeGeneratorPage() {
- Add your logo, choose custom colors, and design unique frames. Professional QR codes in minutes – try it free, no signup required.
+ Add your logo, choose custom colors, and design unique frames. Professional QR codes in minutes - try it free, no signup required.
@@ -417,7 +417,7 @@ export default function CustomQRCodeGeneratorPage() {
- Your Logo Won't Break the QR Code – Here's Why
+ Your Logo Won't Break the QR Code - Here's Why
QR codes have built-in error correction. Our generator uses the highest level (H = 30% redundancy), which means up to 30% of the code can be covered or damaged and still scan perfectly.
@@ -681,7 +681,7 @@ export default function CustomQRCodeGeneratorPage() {
- {/* WHY CUSTOM DESIGN MATTERS — STATISTICS */}
+ {/* WHY CUSTOM DESIGN MATTERS - STATISTICS */}
@@ -692,27 +692,27 @@ export default function CustomQRCodeGeneratorPage() {
Why Brand Design in QR Codes Increases Engagement
- A custom QR code with your brand colors and logo doesn't just look better — it signals trust and gets scanned more often.
+ A custom QR code with your brand colors and logo doesn't just look better - it signals trust and gets scanned more often.
+80%
- Color increases brand recognition by up to 80%. A branded QR code using your brand colors is recognized and associated with your business faster than a generic black-and-white grid — increasing scan intent.
+ Color increases brand recognition by up to 80%. A branded QR code using your brand colors is recognized and associated with your business faster than a generic black-and-white grid - increasing scan intent.
- Source: University of Loyola Maryland — Color & Brand Recognition Study
+ Source: University of Loyola Maryland - Color & Brand Recognition Study
+40%
- Adding a recognizable brand element — like a logo — to a functional graphic increases user engagement and trust. Familiar visual cues reduce hesitation and increase the likelihood of scanning.
+ Adding a recognizable brand element - like a logo - to a functional graphic increases user engagement and trust. Familiar visual cues reduce hesitation and increase the likelihood of scanning.
- Source: Nielsen Norman Group — Visual Design and Trust Research
+ Source: Nielsen Norman Group - Visual Design and Trust Research
diff --git a/src/app/(main)/(marketing)/dynamic-barcode-generator/page.tsx b/src/app/(main)/(marketing)/dynamic-barcode-generator/page.tsx
index 4fe4c39..3900474 100644
--- a/src/app/(main)/(marketing)/dynamic-barcode-generator/page.tsx
+++ b/src/app/(main)/(marketing)/dynamic-barcode-generator/page.tsx
@@ -1,4 +1,4 @@
-import { getAggregateRating } from '@/lib/testimonial-data';
+import { getAggregateRating } from '@/lib/testimonial-data';
import React from 'react';
import type { Metadata } from 'next';
import { Button } from '@/components/ui/Button';
@@ -16,7 +16,7 @@ import {
export const metadata: Metadata = {
title: {
- absolute: 'Dynamic Barcode Generator – Trackable, Editable Barcodes',
+ absolute: 'Dynamic Barcode Generator - Trackable, Editable Barcodes',
},
description:
'Generate dynamic barcodes you can update after printing. Change the redirect URL anytime, track every scan, and manage all barcodes in one dashboard. Free to start.',
@@ -30,17 +30,17 @@ export const metadata: Metadata = {
},
},
openGraph: {
- title: 'Dynamic Barcode Generator – Trackable, Editable Barcodes',
+ title: 'Dynamic Barcode Generator - Trackable, Editable Barcodes',
description:
- 'Generate dynamic barcodes that redirect to any URL — and can be updated after printing. Track every scan with device, time, and location data.',
+ 'Generate dynamic barcodes that redirect to any URL - and can be updated after printing. Track every scan with device, time, and location data.',
url: 'https://www.qrmaster.net/dynamic-barcode-generator',
type: 'website',
images: ['/og-image.png'],
},
twitter: {
- title: 'Dynamic Barcode Generator – Trackable, Editable Barcodes',
+ title: 'Dynamic Barcode Generator - Trackable, Editable Barcodes',
description:
- 'Generate dynamic barcodes that redirect to any URL — and can be updated after printing. Track every scan with device, time, and location data.',
+ 'Generate dynamic barcodes that redirect to any URL - and can be updated after printing. Track every scan with device, time, and location data.',
},
};
@@ -53,7 +53,7 @@ const featureCards = [
{
title: 'Track every smartphone scan',
description:
- 'See device type, time, country, and referrer for every scan — in the same dashboard as your QR codes.',
+ 'See device type, time, country, and referrer for every scan - in the same dashboard as your QR codes.',
},
{
title: 'CODE128 and CODE39 supported',
@@ -111,28 +111,28 @@ const useCases = [
description:
'Print CODE128 barcodes on event badges that link to personalized schedules, session recordings, or attendee portals. Update the destination as content changes.',
example:
- 'Conference badges with a dynamic barcode redirect to an attendee agenda page — updated in real time as the schedule changes. Scanned with smartphone cameras at check-in kiosks.',
+ 'Conference badges with a dynamic barcode redirect to an attendee agenda page - updated in real time as the schedule changes. Scanned with smartphone cameras at check-in kiosks.',
},
{
title: 'Packaging inserts & product cards',
description:
- 'Link physical inserts inside product boxes to setup guides, how-to videos, or support pages. Update the destination when content moves — no reprint needed.',
+ 'Link physical inserts inside product boxes to setup guides, how-to videos, or support pages. Update the destination when content moves - no reprint needed.',
example:
- 'A hardware brand updates the setup guide URL six months post-launch to a new video format — without reprinting a single insert.',
+ 'A hardware brand updates the setup guide URL six months post-launch to a new video format - without reprinting a single insert.',
},
{
title: 'Digital signage & screens',
description:
- 'Display a CODE128 barcode on screens or monitors. Visitors scan with their smartphone and land on a current URL — updated from the dashboard without changing the display.',
+ 'Display a CODE128 barcode on screens or monitors. Visitors scan with their smartphone and land on a current URL - updated from the dashboard without changing the display.',
example:
- 'A trade-show monitor shows a barcode linking to the current product demo page — redirected to a post-show recording after the event.',
+ 'A trade-show monitor shows a barcode linking to the current product demo page - redirected to a post-show recording after the event.',
},
{
title: 'Internal asset tracking (browser-based)',
description:
'Use CODE128 barcodes on internal assets where staff scan with smartphones to open web-based inventory or maintenance forms. URLs update as systems change.',
example:
- 'IT tags company laptops with dynamic barcodes. Staff scan to open the current helpdesk form — URL updated when the ticketing system moves.',
+ 'IT tags company laptops with dynamic barcodes. Staff scan to open the current helpdesk form - URL updated when the ticketing system moves.',
},
];
@@ -140,12 +140,12 @@ const faqItems = [
{
question: 'What is a dynamic barcode?',
answer:
- 'A dynamic barcode (CODE128 or CODE39) encodes a short redirect URL. When scanned with a smartphone camera, it opens a browser and routes through QR Master to your current destination — which you can update without changing the printed barcode. Note: it requires a smartphone scan, not a POS laser scanner.',
+ 'A dynamic barcode (CODE128 or CODE39) encodes a short redirect URL. When scanned with a smartphone camera, it opens a browser and routes through QR Master to your current destination - which you can update without changing the printed barcode. Note: it requires a smartphone scan, not a POS laser scanner.',
},
{
question: 'How is a dynamic barcode different from a static one?',
answer:
- 'A static barcode directly encodes a fixed value — a number, URL, or product code — that cannot be changed after printing. A dynamic barcode encodes a redirect link so the final destination can be updated at any time from your dashboard.',
+ 'A static barcode directly encodes a fixed value - a number, URL, or product code - that cannot be changed after printing. A dynamic barcode encodes a redirect link so the final destination can be updated at any time from your dashboard.',
},
{
question:
@@ -166,7 +166,7 @@ const faqItems = [
{
question: 'Can I change a barcode destination after printing?',
answer:
- 'Yes — that is the core value of a dynamic barcode. Log in to your dashboard, find the barcode, and update the redirect URL. Scanners immediately reach the new destination.',
+ 'Yes - that is the core value of a dynamic barcode. Log in to your dashboard, find the barcode, and update the redirect URL. Scanners immediately reach the new destination.',
},
{
question: 'Do I need to reprint if the linked page changes?',
@@ -186,13 +186,13 @@ const softwareSchema = {
'@type': 'SoftwareApplication',
'@id': 'https://www.qrmaster.net/dynamic-barcode-generator#software',
name: 'QR Master - Dynamic Barcode Generator',
- applicationCategory: 'BusinessApplication',
- aggregateRating: {
- '@type': 'AggregateRating',
- ratingValue: getAggregateRating().ratingValue,
- reviewCount: getAggregateRating().reviewCount,
- bestRating: getAggregateRating().bestRating,
- worstRating: getAggregateRating().worstRating,
+ applicationCategory: 'BusinessApplication',
+ aggregateRating: {
+ '@type': 'AggregateRating',
+ ratingValue: getAggregateRating().ratingValue,
+ reviewCount: getAggregateRating().reviewCount,
+ bestRating: getAggregateRating().bestRating,
+ worstRating: getAggregateRating().worstRating,
},
operatingSystem: 'Web Browser',
offers: {
@@ -225,7 +225,7 @@ const howToSchema = {
url: 'https://www.qrmaster.net/authors/timo',
},
description:
- 'Create a dynamic barcode that redirects to a URL you can update anytime — without reprinting the label.',
+ 'Create a dynamic barcode that redirects to a URL you can update anytime - without reprinting the label.',
totalTime: 'PT3M',
step: [
{
@@ -252,7 +252,7 @@ const howToSchema = {
'@type': 'HowToStep',
position: 4,
name: 'Update anytime',
- text: 'Change the destination URL from your dashboard whenever needed — no reprint required.',
+ text: 'Change the destination URL from your dashboard whenever needed - no reprint required.',
},
],
};
@@ -281,14 +281,14 @@ const relatedLinks = [
href: '/dynamic-qr-code-generator',
title: 'Dynamic QR Code Generator',
description:
- 'The same redirect-and-track approach for 2D QR codes — ideal for consumer-facing print materials scanned by smartphones.',
+ 'The same redirect-and-track approach for 2D QR codes - ideal for consumer-facing print materials scanned by smartphones.',
ctaLabel: 'Create dynamic QR codes',
},
{
href: '/bulk-qr-code-generator',
title: 'Bulk Barcode & QR Generator',
description:
- 'Upload a CSV and generate hundreds of dynamic barcodes or QR codes in one batch — with tracking for every code.',
+ 'Upload a CSV and generate hundreds of dynamic barcodes or QR codes in one batch - with tracking for every code.',
ctaLabel: 'Generate codes in bulk',
},
{
@@ -316,7 +316,7 @@ const relatedLinks = [
href: '/tools/barcode-generator',
title: 'Free Static Barcode Generator',
description:
- 'Need a one-time barcode with no redirect? Use the free static barcode generator — no account required.',
+ 'Need a one-time barcode with no redirect? Use the free static barcode generator - no account required.',
ctaLabel: 'Generate a static barcode',
},
];
@@ -454,17 +454,17 @@ export default function DynamicBarcodeGeneratorPage() {
Create CODE128 or CODE39 barcodes that encode a redirect
URL. When scanned with a smartphone, they open a browser and
- redirect to your destination — which you can update anytime
+ redirect to your destination - which you can update anytime
without reprinting.
{[
- 'Scanned by smartphone cameras — redirect opens in the browser',
+ 'Scanned by smartphone cameras - redirect opens in the browser',
'Update the destination from your dashboard without touching the label',
- 'Track every scan — device, country, time, and UTM data',
- 'Free plan includes 3 active dynamic barcodes — no credit card required',
+ 'Track every scan - device, country, time, and UTM data',
+ 'Free plan includes 3 active dynamic barcodes - no credit card required',
].map((feature) => (
@@ -513,7 +513,7 @@ export default function DynamicBarcodeGeneratorPage() {
- {/* Hero visual — barcode mockup */}
+ {/* Hero visual - barcode mockup */}
{/* Simulated barcode SVG */}
@@ -948,7 +948,7 @@ export default function DynamicBarcodeGeneratorPage() {
How to Create a Dynamic Barcode
@@ -1042,7 +1042,7 @@ export default function DynamicBarcodeGeneratorPage() {
: 'text-[20px] text-[#ea2261]'
}
>
- {item.static ? '✓' : '—'}
+ {item.static ? '✓' : '-'}
))}
@@ -1066,7 +1066,7 @@ export default function DynamicBarcodeGeneratorPage() {
: 'text-[20px] text-[#ea2261]'
}
>
- {item.dynamic ? '✓' : '—'}
+ {item.dynamic ? '✓' : '-'}
))}
@@ -1085,7 +1085,7 @@ export default function DynamicBarcodeGeneratorPage() {
The same redirect-and-track infrastructure that powers QR
- Master's dynamic QR codes — now available for standard linear
+ Master's dynamic QR codes - now available for standard linear
barcode formats.
@@ -1177,7 +1177,7 @@ export default function DynamicBarcodeGeneratorPage() {
Static barcodes are a liability when the page or document
- behind them changes. Dynamic barcodes eliminate that risk —
+ behind them changes. Dynamic barcodes eliminate that risk -
and add scan intelligence to every label.
@@ -1243,7 +1243,7 @@ export default function DynamicBarcodeGeneratorPage() {
Which barcode format works dynamically?
- Dynamic barcodes must encode a URL — so only formats that
+ Dynamic barcodes must encode a URL - so only formats that
support full ASCII strings work. Numeric-only formats like
EAN-13, UPC, and ITF-14 cannot embed a redirect URL and are
available as static barcodes only .
@@ -1297,7 +1297,7 @@ export default function DynamicBarcodeGeneratorPage() {
- Static only — numeric formats
+ Static only - numeric formats
{[
@@ -1353,7 +1353,7 @@ export default function DynamicBarcodeGeneratorPage() {
A wrong link on printed material usually means a reprint.
With a dynamic QR code, you fix the destination online in
- seconds — the printed flyer, menu, or card never has to
+ seconds - the printed flyer, menu, or card never has to
change.
@@ -1071,7 +1071,7 @@ export default function DynamicQRCodeGeneratorPage() {
after it has been created and printed. Instead of encoding the
final URL directly into the image, a dynamic QR code contains a
short managed redirect link. When someone scans the code, the
- redirect sends them to whatever destination is currently set —
+ redirect sends them to whatever destination is currently set -
which means you can update the link, fix a typo, or point the
same printed code at a new campaign at any time, without
reprinting anything.
@@ -1081,7 +1081,7 @@ export default function DynamicQRCodeGeneratorPage() {
codes can also be tracked: each scan is logged with its time,
device type, and approximate location, so printed materials
become measurable instead of invisible. Static QR codes offer
- neither of these capabilities — the destination is permanently
+ neither of these capabilities - the destination is permanently
baked into the image and no scan data is recorded.
@@ -1312,7 +1312,7 @@ export default function DynamicQRCodeGeneratorPage() {
- {/* WHY DYNAMIC QR — STATISTICS */}
+ {/* WHY DYNAMIC QR - STATISTICS */}
@@ -1337,8 +1337,8 @@ export default function DynamicQRCodeGeneratorPage() {
Why Dynamic QR Codes Deliver Better Business Outcomes
- The ability to update destinations after printing — and track
- every scan — transforms a static print asset into a measurable
+ The ability to update destinations after printing - and track
+ every scan - transforms a static print asset into a measurable
marketing channel.
@@ -1348,8 +1348,8 @@ export default function DynamicQRCodeGeneratorPage() {
89% vs 33%
- Companies with strong omnichannel customer engagement —
- enabled by closed-loop tracking from offline to online —
+ Companies with strong omnichannel customer engagement -
+ enabled by closed-loop tracking from offline to online -
retain 89% of their customers , compared to
only 33% for companies with weak omnichannel engagement.
@@ -1363,7 +1363,7 @@ export default function DynamicQRCodeGeneratorPage() {
>
Aberdeen Group
{' '}
- — Omnichannel Customer Engagement Study
+ - Omnichannel Customer Engagement Study
@@ -1375,7 +1375,7 @@ export default function DynamicQRCodeGeneratorPage() {
of small businesses report printing and direct mail errors as
a major source of wasted marketing budget. Dynamic QR codes
address the part of that risk caused by a changed
- destination — update the link online instead of reprinting.
+ destination - update the link online instead of reprinting.
Source:{' '}
@@ -1398,7 +1398,7 @@ export default function DynamicQRCodeGeneratorPage() {
- {/* SERVICES COMPARISON — targets "what services offer dynamic QR codes?" AI query */}
+ {/* SERVICES COMPARISON - targets "what services offer dynamic QR codes?" AI query */}
@@ -1410,8 +1410,8 @@ export default function DynamicQRCodeGeneratorPage() {
Which services offer dynamic QR codes?
- Dynamic QR codes — where the destination URL can be changed after
- printing — are offered by several platforms. Here is how they
+ Dynamic QR codes - where the destination URL can be changed after
+ printing - are offered by several platforms. Here is how they
compare on price, privacy, and use case fit.
diff --git a/src/app/(main)/(marketing)/faq/page.tsx b/src/app/(main)/(marketing)/faq/page.tsx
index cae7bc6..d0cbdc9 100644
--- a/src/app/(main)/(marketing)/faq/page.tsx
+++ b/src/app/(main)/(marketing)/faq/page.tsx
@@ -14,7 +14,7 @@ function truncateAtWord(text: string, maxLength: number): string {
}
export async function generateMetadata(): Promise
{
- const title = truncateAtWord('QR Code FAQ – Common Questions Answered | QR Master', 60);
+ const title = truncateAtWord('QR Code FAQ - Common Questions Answered | QR Master', 60);
const description = truncateAtWord(
'Quick answers to common QR code questions: Do QR codes expire? Static vs dynamic? Can they be scanned through laminate? Get clear answers.',
160
@@ -61,12 +61,12 @@ const faqs: FAQItemWithRichText[] = [
{
question: 'Do QR codes expire?',
answer:
- 'Static QR codes never expire — the destination is permanently encoded in the image and works indefinitely. Dynamic QR codes remain active as long as your subscription is active. QR Master keeps static QR codes functional forever, including on the free plan.',
+ 'Static QR codes never expire - the destination is permanently encoded in the image and works indefinitely. Dynamic QR codes remain active as long as your subscription is active. QR Master keeps static QR codes functional forever, including on the free plan.',
},
{
question: 'Will QR codes become obsolete?',
answer:
- 'QR codes are unlikely to become obsolete in the near future. Adoption has accelerated since 2020 — Statista reports that QR code usage grew by over 750% between 2018 and 2023. Every major smartphone camera app now natively reads QR codes without a separate app, removing the main adoption barrier.',
+ 'QR codes are unlikely to become obsolete in the near future. Adoption has accelerated since 2020 - Statista reports that QR code usage grew by over 750% between 2018 and 2023. Every major smartphone camera app now natively reads QR codes without a separate app, removing the main adoption barrier.',
},
{
question: 'Will QR codes replace barcodes?',
@@ -81,7 +81,7 @@ const faqs: FAQItemWithRichText[] = [
{
question: 'Do QR codes work with a cracked phone screen?',
answer:
- 'Usually yes, as long as the camera can still capture the QR code image. Minor cracks often do not prevent scanning. A heavily cracked screen that distorts the camera view may cause scanning failures. The QR code itself is not affected — only the device reading it matters.',
+ 'Usually yes, as long as the camera can still capture the QR code image. Minor cracks often do not prevent scanning. A heavily cracked screen that distorts the camera view may cause scanning failures. The QR code itself is not affected - only the device reading it matters.',
},
{
question: 'When were QR codes invented?',
@@ -91,7 +91,7 @@ const faqs: FAQItemWithRichText[] = [
{
question: 'Can QR codes run out?',
answer:
- 'No — QR codes cannot run out. The QR code standard supports approximately 10^9 unique combinations for a typical URL, far more than could ever be used. Generating a new QR code does not "use up" anything from a shared pool. Each code is generated independently.',
+ 'No - QR codes cannot run out. The QR code standard supports approximately 10^9 unique combinations for a typical URL, far more than could ever be used. Generating a new QR code does not "use up" anything from a shared pool. Each code is generated independently.',
},
{
question: 'What is a dynamic QR code?',
@@ -155,7 +155,7 @@ const faqs: FAQItemWithRichText[] = [
{
question: 'How does bulk QR creation work today?',
answer:
- 'QR Master currently supports bulk QR creation through spreadsheet upload in the Business plan. The flow accepts CSV, XLS, and XLSX files, supports up to 1,000 rows per upload, and generates static QR codes.',
+ 'QR Master supports bulk QR creation through spreadsheet upload in the Business plan. The flow accepts CSV, XLS, and XLSX files and handles up to 1,000 rows per upload. You choose static or dynamic output per upload; dynamic is capped at 500 by the Business dynamic code allowance.',
answerRich: (
<>
QR Master currently supports bulk QR creation through spreadsheet upload in the Business plan.
diff --git a/src/app/(main)/(marketing)/guide/[slug]/page.tsx b/src/app/(main)/(marketing)/guide/[slug]/page.tsx
index d879c9a..d435ec8 100644
--- a/src/app/(main)/(marketing)/guide/[slug]/page.tsx
+++ b/src/app/(main)/(marketing)/guide/[slug]/page.tsx
@@ -80,9 +80,12 @@ export function generateMetadata({ params }: PageProps): Metadata {
follow: true,
},
icons: {
- icon: [{ url: "/favicon1.png", type: "image/png" }],
- shortcut: "/favicon1.png",
- apple: "/favicon1.png",
+ icon: [
+ { url: "/favicon.svg", type: "image/svg+xml" },
+ { url: "/favicon.ico", sizes: "16x16 32x32", type: "image/x-icon" },
+ ],
+ shortcut: "/favicon.ico",
+ apple: "/logo.svg",
},
openGraph: {
title,
diff --git a/src/app/(main)/(marketing)/layout.tsx b/src/app/(main)/(marketing)/layout.tsx
index c80e6c1..e217cdf 100644
--- a/src/app/(main)/(marketing)/layout.tsx
+++ b/src/app/(main)/(marketing)/layout.tsx
@@ -1,15 +1,15 @@
-import type { Metadata } from 'next';
-import '@/styles/globals.css';
-import MarketingLayout from './MarketingLayout';
-// Import schema functions from library
-import { organizationSchema } from '@/lib/schema';
+import type { Metadata } from 'next';
+import '@/styles/globals.css';
+import MarketingLayout from './MarketingLayout';
+// Import schema functions from library
+import { organizationSchema } from '@/lib/schema';
const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true';
export const metadata: Metadata = {
metadataBase: new URL('https://www.qrmaster.net'),
title: {
- default: 'QR Master – Smart QR Generator & Analytics',
+ default: 'QR Master - Smart QR Generator & Analytics',
template: '%s | QR Master',
},
description: 'Create dynamic QR codes, track scans, and scale campaigns with secure analytics.',
@@ -17,14 +17,14 @@ export const metadata: Metadata = {
robots: isIndexable
? { index: true, follow: true }
: { index: false, follow: false },
- icons: {
- icon: [
- { url: '/favicon.svg', type: 'image/svg+xml' },
- { url: '/favicon.ico', sizes: '16x16 32x32', type: 'image/x-icon' },
- ],
- shortcut: '/favicon.ico',
- apple: '/logo.svg',
- },
+ icons: {
+ icon: [
+ { url: '/favicon.svg', type: 'image/svg+xml' },
+ { url: '/favicon.ico', sizes: '16x16 32x32', type: 'image/x-icon' },
+ ],
+ shortcut: '/favicon.ico',
+ apple: '/logo.svg',
+ },
twitter: {
card: 'summary_large_image',
site: '@qrmaster',
@@ -33,7 +33,7 @@ export const metadata: Metadata = {
openGraph: {
type: 'website',
siteName: 'QR Master',
- title: 'QR Master – Smart QR Generator & Analytics',
+ title: 'QR Master - Smart QR Generator & Analytics',
description: 'Create dynamic QR codes, track scans, and scale campaigns with secure analytics.',
images: [
{
@@ -52,20 +52,20 @@ export const metadata: Metadata = {
},
};
-export default function MarketingGroupLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
+export default function MarketingGroupLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
return (
<>
-
-
- {children}
-
- >
- );
-}
+
+
+ {children}
+
+ >
+ );
+}
diff --git a/src/app/(main)/(marketing)/learn/page.tsx b/src/app/(main)/(marketing)/learn/page.tsx
index dd86355..69b2fa2 100644
--- a/src/app/(main)/(marketing)/learn/page.tsx
+++ b/src/app/(main)/(marketing)/learn/page.tsx
@@ -3,8 +3,8 @@ import { pillarMeta } from "@/lib/pillar-data";
import { getPublishedPosts } from "@/lib/content";
export const metadata = {
- title: "QR Mastery: Free QR Code Guides & Tutorials",
- description: "QR Mastery is the free learning hub by QR Master: step-by-step guides to create, track, and optimize dynamic QR codes. No account needed to start.",
+ title: "QR Mastery - Guides on Tracking, Print Size & Providers",
+ description: "Free guides on the things that go wrong: dead links after printing, scans you cannot attribute, codes too small to read, and providers that switch codes off. No account.",
alternates: {
canonical: "https://www.qrmaster.net/learn",
},
@@ -27,7 +27,7 @@ export default function LearnHubPage() {
QR Mastery: The QR Code Knowledge Hub
- Master the art of QR codes. Explore our expert guides on generation, tracking, security, and marketing strategies — all free, no account needed.
+ Master the art of QR codes. Explore our expert guides on generation, tracking, security, and marketing strategies - all free, no account needed.
Ready to put it into practice? Create an editable code with the{" "}
diff --git a/src/app/(main)/(marketing)/newsletter/NewsletterClient.tsx b/src/app/(main)/(marketing)/newsletter/NewsletterClient.tsx
index 4e6d50a..97b53e1 100644
--- a/src/app/(main)/(marketing)/newsletter/NewsletterClient.tsx
+++ b/src/app/(main)/(marketing)/newsletter/NewsletterClient.tsx
@@ -813,9 +813,9 @@ export default function NewsletterClient() {
{data.segments.rows.map((row) => (
- {row.name || '—'}
+ {row.name || '-'}
{row.email}
- {row.emailDomain || '—'}
+ {row.emailDomain || '-'}
{row.plan}
{row.lifecycleStageLabel}
{row.fitScore}
@@ -823,16 +823,16 @@ export default function NewsletterClient() {
{row.leadScore}
{row.signupSourceLabel}
{row.signupSourceSelfReportedLabel}
- {row.signupCampaign || '—'}
- {row.signupLandingPath || '—'}
+ {row.signupCampaign || '-'}
+ {row.signupLandingPath || '-'}
{row.primaryUseCaseLabel}
{row.primaryGoalLabel}
{row.jobRoleLabel}
- {row.companyName || '—'}
+ {row.companyName || '-'}
{row.teamSizeLabel}
{new Date(row.createdAt).toLocaleDateString()}
- {row.firstQrCreatedAt ? new Date(row.firstQrCreatedAt).toLocaleDateString() : '—'}
- {row.activationAt ? new Date(row.activationAt).toLocaleDateString() : '—'}
+ {row.firstQrCreatedAt ? new Date(row.firstQrCreatedAt).toLocaleDateString() : '-'}
+ {row.activationAt ? new Date(row.activationAt).toLocaleDateString() : '-'}
{row.qrCount}
{row.dynamicQrCount}
{row.scanCount}
@@ -888,7 +888,7 @@ export default function NewsletterClient() {
{row.leadScore}
{getUseCaseLabel(row.primaryUseCase)}
{getRoleLabel(row.jobRole)}
- {row.companyName || '—'}
+ {row.companyName || '-'}
{row.upgradeBadges.map((badge) => (
diff --git a/src/app/(main)/(marketing)/page.tsx b/src/app/(main)/(marketing)/page.tsx
index 6eaffce..f31c10e 100644
--- a/src/app/(main)/(marketing)/page.tsx
+++ b/src/app/(main)/(marketing)/page.tsx
@@ -17,10 +17,10 @@ function truncateAtWord(text: string, maxLength: number): string {
export async function generateMetadata(): Promise
{
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.',
+ 'Change where a printed QR code points - without reprinting it. 3 dynamic codes free forever, unlimited static codes that never expire, scan tracking. No card.',
160
);
- const brandTitle = 'QR Master - Free Dynamic QR Code Generator with Tracking';
+ const brandTitle = 'QR Master - Free QR Code Generator, Editable After Print';
return {
title: brandTitle,
@@ -90,7 +90,7 @@ export default function HomePage() {
Features include: Dynamic QR codes with real-time tracking, bulk QR
- code generation from Excel/CSV, custom branding with colors and logos,
+ code generation from Excel/CSV, free colour control on every plan,
advanced scan analytics showing device types and locations, vCard QR
codes for digital business cards, restaurant menu QR codes, and a free{' '}
barcode generator for EAN-13,
diff --git a/src/app/(main)/(marketing)/pricing/PricingClient.tsx b/src/app/(main)/(marketing)/pricing/PricingClient.tsx
index 2f9bf02..f8368aa 100644
--- a/src/app/(main)/(marketing)/pricing/PricingClient.tsx
+++ b/src/app/(main)/(marketing)/pricing/PricingClient.tsx
@@ -145,10 +145,10 @@ export default function PricingPage() {
`${FREE_DYNAMIC_QR_LIMIT} active dynamic QR codes (8 types available)`,
'Unlimited static QR codes',
'Basic scan tracking',
- 'Standard QR design templates',
+ 'Your colors - foreground and background, free',
'Download as SVG/PNG',
],
- caption: 'Good for proving the mechanism on 1–3 placements.',
+ caption: 'Good for proving the mechanism on 1-3 placements.',
buttonText: currentPlan === 'FREE' ? 'Current Plan' : 'Cancel paid plan',
buttonVariant: 'outline' as const,
disabled: currentPlan === 'FREE',
@@ -162,10 +162,10 @@ export default function PricingPage() {
period: billingPeriod === 'month' ? 'per month' : 'per year',
showDiscount: billingPeriod === 'year',
features: [
- '50 dynamic QR codes — enough for a full campaign, or several at once',
+ '50 dynamic QR codes - enough for a full campaign, or several at once',
'Compare scans by device and location, not just a running total',
'Unlimited static QR codes',
- 'Custom branding (colors & logos)',
+ '4 module shapes, custom eye styles and your logo',
],
buttonText: isCurrentPlanWithInterval('PRO', selectedInterval)
? 'Current Plan'
@@ -187,7 +187,9 @@ export default function PricingPage() {
'500 dynamic QR codes',
'Unlimited static QR codes',
'Everything from Pro',
- 'Bulk QR Creation (up to 1,000, static output)',
+ 'Full designer: 11 module shapes and colour gradients',
+ 'Saved design presets, applied to a whole bulk upload',
+ 'Bulk QR Creation (1,000 static or 500 dynamic)',
'Priority email support',
'Advanced tracking & insights',
],
@@ -229,8 +231,8 @@ export default function PricingPage() {
Every plan includes unlimited static codes that never expire. Pick
- based on how many active placements you're tracking right now —
- not how many you might need someday — and upgrade in seconds if
+ based on how many active placements you're tracking right now -
+ not how many you might need someday - and upgrade in seconds if
that changes.
diff --git a/src/app/(main)/(marketing)/pricing/page.tsx b/src/app/(main)/(marketing)/pricing/page.tsx
index 0a1565f..f7f6add 100644
--- a/src/app/(main)/(marketing)/pricing/page.tsx
+++ b/src/app/(main)/(marketing)/pricing/page.tsx
@@ -56,7 +56,7 @@ const faqItems = [
{
question: 'Which plan includes bulk QR creation?',
answer:
- 'Bulk QR creation is included in the Business plan. Bulk-created codes are static output, not dynamic or trackable.',
+ 'Bulk QR creation is included in the Business plan. You choose static or dynamic per upload: up to 1,000 static codes, or up to 500 dynamic ones, limited by the Business dynamic code allowance.',
},
{
question: 'Which plans include analytics and branding?',
@@ -116,7 +116,7 @@ export default function PricingPage() {
Compare free QR generators with paid dynamic QR workflows before
choosing a plan. The difference usually comes down to how many
placements you're running at once, and whether you need to
- compare them — not just the number of codes.
+ compare them - not just the number of codes.
SVG for Everything Larger
- 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.
+ 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.
@@ -205,7 +205,7 @@ export default function QRCodePrintSizeGuidePage() {
Error Correction Level
- Use Level Q (25% recovery) or Level H (30% recovery) for print — especially if you add a logo. Higher error correction means more modules and a slightly larger minimum size.
+ Use Level Q (25% recovery) or Level H (30% recovery) for print - especially if you add a logo. Higher error correction means more modules and a slightly larger minimum size.
@@ -282,11 +282,11 @@ export default function QRCodePrintSizeGuidePage() {
},
{
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.',
+ 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.',
+ 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?',
diff --git a/src/app/(main)/(marketing)/qr-code-tracking/page.tsx b/src/app/(main)/(marketing)/qr-code-tracking/page.tsx
index 6c24581..c5e4e5b 100644
--- a/src/app/(main)/(marketing)/qr-code-tracking/page.tsx
+++ b/src/app/(main)/(marketing)/qr-code-tracking/page.tsx
@@ -16,10 +16,10 @@ import {
export const metadata: Metadata = {
title: {
- absolute: 'QR Code Tracking: Track QR Code Scans',
+ absolute: 'QR Code Tracking - See Which Placement Drove the Scan',
},
description:
- 'Track QR code scans with dynamic QR tracking. See scan time, device, location context, placements, and privacy-aware analytics for printed campaigns.',
+ 'Give every flyer, poster and table tent its own code, then see which one actually worked. Scan data by time, device and location. IPs hashed, no scanner data.',
keywords:
'qr code tracking, qr code analytics, track qr scans, dynamic qr tracking, qr scan analytics',
alternates: {
@@ -30,9 +30,9 @@ export const metadata: Metadata = {
},
},
openGraph: {
- title: 'QR Code Tracking: Track QR Code Scans',
+ title: 'QR Code Tracking - See Which Placement Drove the Scan',
description:
- 'Track QR code scans with analytics for time, device, and location context. Use dynamic QR codes to measure placements and campaigns.',
+ 'One code per placement turns print into measurable media. Scan data by time, device, and location context - for flyers, posters, packaging, and events.',
url: 'https://www.qrmaster.net/qr-code-tracking',
type: 'website',
images: ['/og-image.png'],
@@ -193,7 +193,7 @@ const faqItems = [
{
question: 'Is QR code scan tracking free?',
answer:
- 'Yes — the Free plan includes basic scan tracking for up to 3 active dynamic QR codes. Pro and Business plans include extended scan history, more active codes, and full analytics.',
+ 'Yes - the Free plan includes basic scan tracking for up to 3 active dynamic QR codes. Pro and Business plans include extended scan history, more active codes, and full analytics.',
},
{
question: 'Can I see unlimited scan history?',
@@ -391,7 +391,7 @@ export default function QRCodeTrackingPage() {
Track QR code scans by time, device, and location context
with dynamic QR codes, so printed campaigns and physical
- placements stop being guesswork — see which placement
+ placements stop being guesswork - see which placement
worked before you plan the next print run.
@@ -502,7 +502,7 @@ export default function QRCodeTrackingPage() {
QR codes without tracking vs. with QR Master tracking
- Printing a QR code without scan analytics is like running a billboard campaign with no impression data — you spend the budget but can't tell what worked.
+ Printing a QR code without scan analytics is like running a billboard campaign with no impression data - you spend the budget but can't tell what worked.
@@ -515,10 +515,10 @@ export default function QRCodeTrackingPage() {
Without tracking
- – You print QR codes and have no idea if anyone scans them
- – You can't tell which flyer, sign, or table card performs best
- – Broken or outdated destination URLs require a full reprint
- – No way to know if your campaign timing or placement was right
+ - You print QR codes and have no idea if anyone scans them
+ - You can't tell which flyer, sign, or table card performs best
+ - Broken or outdated destination URLs require a full reprint
+ - No way to know if your campaign timing or placement was right
@@ -533,7 +533,7 @@ export default function QRCodeTrackingPage() {
+ See exactly which QR codes are being scanned and when
+ Compare placements: restaurant table A vs. table B, flyer vs. window sign
- + Know the device mix — 89% of scans are mobile, so you can optimize landing pages
+ + Know the device mix - 89% of scans are mobile, so you can optimize landing pages
+ Update destinations without reprinting; fix errors in seconds from your dashboard
@@ -824,7 +824,7 @@ export default function QRCodeTrackingPage() {
Industry data
- QR code adoption is accelerating — tracking makes that growth measurable
+ QR code adoption is accelerating - tracking makes that growth measurable
As QR scan volumes grow, businesses that track their codes gain compounding insight advantages over those that print blind.
@@ -854,7 +854,7 @@ export default function QRCodeTrackingPage() {
- {/* WHY QR TRACKING MATTERS — STATISTICS */}
+ {/* WHY QR TRACKING MATTERS - STATISTICS */}
@@ -879,7 +879,7 @@ export default function QRCodeTrackingPage() {
Why Tracking Makes QR Codes Measurable Marketing Assets
- Without scan analytics, a printed QR code is invisible — you can't
+ Without scan analytics, a printed QR code is invisible - you can't
tell if your campaign placement is working. Tracking turns every
scan into actionable data.
@@ -890,8 +890,8 @@ export default function QRCodeTrackingPage() {
89% vs 33%
- Companies with strong omnichannel engagement — requiring
- closed-loop tracking from offline to online — retain{' '}
+ Companies with strong omnichannel engagement - requiring
+ closed-loop tracking from offline to online - retain{' '}
89% of their customers , compared to 33% for
businesses without integrated tracking.
@@ -905,7 +905,7 @@ export default function QRCodeTrackingPage() {
>
Aberdeen Group
{' '}
- — Omnichannel Customer Engagement Study
+ - Omnichannel Customer Engagement Study
@@ -916,7 +916,7 @@ export default function QRCodeTrackingPage() {
of small businesses identify print and direct mail errors as a
major source of wasted marketing budget. QR tracking reveals
- which placements actually drive scans — so you reprint only
+ which placements actually drive scans - so you reprint only
what works.
diff --git a/src/app/(main)/(marketing)/reprint-calculator/page.tsx b/src/app/(main)/(marketing)/reprint-calculator/page.tsx
index 2189c2f..4564148 100644
--- a/src/app/(main)/(marketing)/reprint-calculator/page.tsx
+++ b/src/app/(main)/(marketing)/reprint-calculator/page.tsx
@@ -1,4 +1,4 @@
-import { getAggregateRating } from '@/lib/testimonial-data';
+import { getAggregateRating } from '@/lib/testimonial-data';
import React from 'react';
import type { Metadata } from 'next';
import ReprintSavingsCalculator from '@/components/marketing/ReprintSavingsCalculator';
@@ -40,13 +40,13 @@ const softwareSchema = {
'@type': 'WebApplication',
'@id': 'https://www.qrmaster.net/reprint-calculator#app',
name: 'QR Code Reprint Cost Calculator',
- applicationCategory: 'BusinessApplication',
- aggregateRating: {
- '@type': 'AggregateRating',
- ratingValue: getAggregateRating().ratingValue,
- reviewCount: getAggregateRating().reviewCount,
- bestRating: getAggregateRating().bestRating,
- worstRating: getAggregateRating().worstRating,
+ applicationCategory: 'BusinessApplication',
+ aggregateRating: {
+ '@type': 'AggregateRating',
+ ratingValue: getAggregateRating().ratingValue,
+ reviewCount: getAggregateRating().reviewCount,
+ bestRating: getAggregateRating().bestRating,
+ worstRating: getAggregateRating().worstRating,
},
operatingSystem: 'Web Browser',
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
@@ -157,7 +157,7 @@ export default function ReprintCalculatorPage() {
Every time a URL changes, static QR codes become useless trash.
- Dynamic QR codes let you update the destination URL at any time — your printed materials stay usable even when links change.
+ Dynamic QR codes let you update the destination URL at any time - your printed materials stay usable even when links change.
diff --git a/src/app/(main)/(marketing)/tools/barcode-generator/BarcodeFormatPicker.tsx b/src/app/(main)/(marketing)/tools/barcode-generator/BarcodeFormatPicker.tsx
index b1b2ea5..37f31f1 100644
--- a/src/app/(main)/(marketing)/tools/barcode-generator/BarcodeFormatPicker.tsx
+++ b/src/app/(main)/(marketing)/tools/barcode-generator/BarcodeFormatPicker.tsx
@@ -51,7 +51,7 @@ const RESULTS: Record
= {
'pharmacode': {
format: 'Pharmacode',
label: 'Pharmacode',
- description: 'A pharmaceutical packaging standard used to verify correct product packaging. Encodes a single numeric value (3–131071).',
+ description: 'A pharmaceutical packaging standard used to verify correct product packaging. Encodes a single numeric value (3-131071).',
example: '12345',
color: 'red',
},
diff --git a/src/app/(main)/(marketing)/tools/barcode-generator/BarcodeGeneratorClient.tsx b/src/app/(main)/(marketing)/tools/barcode-generator/BarcodeGeneratorClient.tsx
index 216510c..f2e00d2 100644
--- a/src/app/(main)/(marketing)/tools/barcode-generator/BarcodeGeneratorClient.tsx
+++ b/src/app/(main)/(marketing)/tools/barcode-generator/BarcodeGeneratorClient.tsx
@@ -164,7 +164,7 @@ export default function BarcodeGeneratorClient() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="url" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/barcode-generator/BarcodeGuide.tsx b/src/app/(main)/(marketing)/tools/barcode-generator/BarcodeGuide.tsx
index 5f3875a..d835713 100644
--- a/src/app/(main)/(marketing)/tools/barcode-generator/BarcodeGuide.tsx
+++ b/src/app/(main)/(marketing)/tools/barcode-generator/BarcodeGuide.tsx
@@ -14,7 +14,7 @@ export function BarcodeGuide() {
- Barcode Generator – How Barcodes Work and Why They Matter
+ Barcode Generator - How Barcodes Work and Why They Matter
@@ -138,7 +138,7 @@ export function BarcodeGuide() {
Pharmacode
Pharmaceutical packaging
- 3–131071 numeric
+ 3-131071 numeric
Pharma
@@ -185,7 +185,7 @@ export function BarcodeGuide() {
Barcode Accuracy: What the Research Shows
- Barcodes are not just convenient — they are scientifically proven to reduce errors across industries. Independent research from logistics, healthcare, and economics consistently shows the same result: switching from manual data entry to barcode scanning produces dramatic accuracy gains.
+ Barcodes are not just convenient - they are scientifically proven to reduce errors across industries. Independent research from logistics, healthcare, and economics consistently shows the same result: switching from manual data entry to barcode scanning produces dramatic accuracy gains.
@@ -240,7 +240,7 @@ export function BarcodeGuide() {
rel="noopener noreferrer"
className="underline hover:text-slate-600"
>
- New England Journal of Medicine — "Effect of Bar-Code Technology on the Safety of Medication Administration"
+ New England Journal of Medicine - "Effect of Bar-Code Technology on the Safety of Medication Administration"
{'; '}
- AHRQ — Barcode Medication Administration (BCMA) research
+ AHRQ - Barcode Medication Administration (BCMA) research
{'.'}
@@ -267,7 +267,7 @@ export function BarcodeGuide() {
Significant Productivity Gains at Checkout (Retail Economics)
- The productivity impact of barcodes extends beyond accuracy. Economic research from the National Bureau of Economic Research (NBER) documents dramatic throughput gains for retailers when switching from manual key-entry to barcode scanners — an effect that transformed checkout speed and operational cost structures across the industry.
+ The productivity impact of barcodes extends beyond accuracy. Economic research from the National Bureau of Economic Research (NBER) documents dramatic throughput gains for retailers when switching from manual key-entry to barcode scanners - an effect that transformed checkout speed and operational cost structures across the industry.
Source:{' '}
@@ -277,7 +277,7 @@ export function BarcodeGuide() {
rel="noopener noreferrer"
className="underline hover:text-slate-600"
>
- NBER Working Paper — "Raising the Barcode Scanner: Technology and Productivity in the Retail Sector"
+ NBER Working Paper - "Raising the Barcode Scanner: Technology and Productivity in the Retail Sector"
{'.'}
@@ -329,7 +329,7 @@ export function BarcodeGuide() {
Understanding Check Digits
- Most barcodes (like EAN and UPC) include a "Check Digit"—the last number in the sequence. This digit is calculated mathematically from the other numbers to ensure the barcode is scanned correctly. Even if a barcode is slightly damaged or scratched, the scanner uses the check digit to verify the integrity of the data.
+ Most barcodes (like EAN and UPC) include a "Check Digit"-the last number in the sequence. This digit is calculated mathematically from the other numbers to ensure the barcode is scanned correctly. Even if a barcode is slightly damaged or scratched, the scanner uses the check digit to verify the integrity of the data.
Best Practices for Printing Barcodes
@@ -363,7 +363,7 @@ export function BarcodeGuide() {
},
{
question: 'Can I download barcodes in vector format (SVG)?',
- answer: 'Yes — SVG downloads are available. SVG files are vector-based, meaning they can be scaled to any size without losing quality. This is ideal for professional product packaging and labels.',
+ answer: 'Yes - SVG downloads are available. SVG files are vector-based, meaning they can be scaled to any size without losing quality. This is ideal for professional product packaging and labels.',
},
{
question: 'How do I generate a barcode online?',
@@ -375,11 +375,11 @@ export function BarcodeGuide() {
},
{
question: 'Can I use these barcodes for Amazon (EAN/UPC)?',
- answer: 'You can generate the barcode
image here if you already have a valid EAN/UPC number. However, you cannot create a globally registered EAN/UPC number here — you must purchase official numbers from GS1 to list products on Amazon or in major retail systems.',
+ answer: 'You can generate the barcode
image here if you already have a valid EAN/UPC number. However, you cannot create a globally registered EAN/UPC number here - you must purchase official numbers from GS1 to list products on Amazon or in major retail systems.',
},
{
question: 'What is the difference between a barcode and a QR code?',
- answer: 'A barcode stores data in one dimension (horizontal bars) and is mainly used for product identification. A QR code stores data in two dimensions (a matrix) and can hold much more information — URLs, contact details, WiFi credentials, and more.',
+ answer: 'A barcode stores data in one dimension (horizontal bars) and is mainly used for product identification. A QR code stores data in two dimensions (a matrix) and can hold much more information - URLs, contact details, WiFi credentials, and more.',
},
]}
/>
diff --git a/src/app/(main)/(marketing)/tools/barcode-generator/page.tsx b/src/app/(main)/(marketing)/tools/barcode-generator/page.tsx
index 58caae7..17a590e 100644
--- a/src/app/(main)/(marketing)/tools/barcode-generator/page.tsx
+++ b/src/app/(main)/(marketing)/tools/barcode-generator/page.tsx
@@ -23,10 +23,10 @@ import {
// SEO Optimized Metadata
export const metadata: Metadata = {
title: {
- absolute: 'Free Custom Barcode Generator - EAN, UPC, Code 128',
+ absolute: 'Barcode Generator - EAN-13, UPC-A, Code 128, Free',
},
description:
- 'Free custom barcode generator and barcode maker for EAN-13, UPC-A, UPC barcode, and Code 128. Create scannable labels for retail and inventory, then download PNG or SVG.',
+ 'Type a number, get a scannable barcode. EAN-13, UPC-A and Code 128 for retail shelves, inventory and labels. Download print-ready PNG or SVG. Free, no signup.',
keywords: [
'barcode generator',
'custom barcode generator',
@@ -49,9 +49,8 @@ export const metadata: Metadata = {
},
},
openGraph: {
- title: 'Free Custom Barcode Generator - EAN, UPC & Code 128',
- description:
- 'Free online barcode maker for EAN-13, UPC-A, and Code 128. Create scannable custom barcodes in seconds and download PNG or SVG.',
+ title: 'Barcode Generator - EAN-13, UPC-A, Code 128, Free',
+ description: 'Type a number, get a scannable barcode. For retail shelves, inventory and labels.',
url: 'https://www.qrmaster.net/tools/barcode-generator',
siteName: 'QR Master',
locale: 'en_US',
@@ -62,9 +61,8 @@ export const metadata: Metadata = {
},
twitter: {
card: 'summary_large_image',
- title: 'Free Barcode Generator',
- description:
- 'Create custom barcodes in seconds. Download high-quality PNG/SVG.',
+ title: 'Barcode Generator - EAN-13, UPC-A, Code 128, Free',
+ description: 'Type a number, get a scannable barcode. For retail shelves, inventory and labels.',
},
robots: {
index: true,
@@ -163,7 +161,7 @@ const jsonLd = {
'Can I download barcodes in vector format (SVG)?': {
question: 'Can I download barcodes in vector format (SVG)?',
answer:
- 'Yes! We offer SVG downloads. SVG files are vector-based, meaning they can be scaled to any size without losing quality—perfect for professional product packaging.',
+ 'Yes! We offer SVG downloads. SVG files are vector-based, meaning they can be scaled to any size without losing quality-perfect for professional product packaging.',
},
'How do I generate a barcode online?': {
question: 'How do I generate a barcode online?',
@@ -178,7 +176,7 @@ const jsonLd = {
'Can I use these barcodes for Amazon (EAN/UPC)?': {
question: 'Can I use these barcodes for Amazon (EAN/UPC)?',
answer:
- 'You can generate the image for Amazon here if you already have your EAN/UPC number. However, you cannot "create" a valid global EAN number here—you must purchase those official numbers from GS1 to sell on major platforms like Amazon.',
+ 'You can generate the image for Amazon here if you already have your EAN/UPC number. However, you cannot "create" a valid global EAN number here-you must purchase those official numbers from GS1 to sell on major platforms like Amazon.',
},
'What is the difference between a barcode and a QR code?': {
question: 'What is the difference between a barcode and a QR code?',
@@ -188,7 +186,7 @@ const jsonLd = {
'What barcode format do Amazon and Walmart require?': {
question: 'What barcode format do Amazon and Walmart require?',
answer:
- 'Amazon and Walmart require UPC-A (12 digits) for products sold in the United States and Canada, and EAN-13 (13 digits) for products sold internationally. You must purchase official GS1-registered numbers to sell on these platforms — you cannot self-generate valid retail UPC/EAN numbers.',
+ 'Amazon and Walmart require UPC-A (12 digits) for products sold in the United States and Canada, and EAN-13 (13 digits) for products sold internationally. You must purchase official GS1-registered numbers to sell on these platforms - you cannot self-generate valid retail UPC/EAN numbers.',
},
'What is the minimum print size for a scannable barcode?': {
question: 'What is the minimum print size for a scannable barcode?',
@@ -203,7 +201,7 @@ const jsonLd = {
'What is the difference between EAN-13 and UPC-A?': {
question: 'What is the difference between EAN-13 and UPC-A?',
answer:
- 'EAN-13 (13 digits) is the international retail standard used in Europe, Asia, and globally. UPC-A (12 digits) is the North American retail standard used in the US and Canada. An EAN-13 barcode starting with a 0 is actually a UPC-A code — all UPC-A codes are a subset of EAN-13. Most modern POS scanners read both formats.',
+ 'EAN-13 (13 digits) is the international retail standard used in Europe, Asia, and globally. UPC-A (12 digits) is the North American retail standard used in the US and Canada. An EAN-13 barcode starting with a 0 is actually a UPC-A code - all UPC-A codes are a subset of EAN-13. Most modern POS scanners read both formats.',
},
}),
],
@@ -258,7 +256,7 @@ export default function BarcodeGeneratorPage() {
- Free Tool — Professional & Fast
+ Free Tool - Professional & Fast
@@ -399,13 +397,13 @@ export default function BarcodeGeneratorPage() {
Inventory & Logistics
- Supports letters + numbers — best for internal SKU systems
+ Supports letters + numbers - best for internal SKU systems
- Barcode vs. QR Code — When to Use Which
+ Barcode vs. QR Code - When to Use Which
diff --git a/src/app/(main)/(marketing)/tools/call-qr-code-generator/PhoneGenerator.tsx b/src/app/(main)/(marketing)/tools/call-qr-code-generator/PhoneGenerator.tsx
index fa786c2..7449f3b 100644
--- a/src/app/(main)/(marketing)/tools/call-qr-code-generator/PhoneGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/call-qr-code-generator/PhoneGenerator.tsx
@@ -86,7 +86,7 @@ export default function PhoneGenerator() {
return (
<>
-
setShowPopup(false)} />
+ setShowPopup(false)} variant="url" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/call-qr-code-generator/page.tsx b/src/app/(main)/(marketing)/tools/call-qr-code-generator/page.tsx
index cfe69fb..5497132 100644
--- a/src/app/(main)/(marketing)/tools/call-qr-code-generator/page.tsx
+++ b/src/app/(main)/(marketing)/tools/call-qr-code-generator/page.tsx
@@ -139,7 +139,7 @@ export default function CallQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
@@ -148,7 +148,7 @@ export default function CallQRCodePage() {
- Create a QR code that opens the phone dialer with your number pre-filled — one scan, no typing.
+ Create a QR code that opens the phone dialer with your number pre-filled - one scan, no typing.
Works on every smartphone.
diff --git a/src/app/(main)/(marketing)/tools/crypto-qr-code/CryptoGenerator.tsx b/src/app/(main)/(marketing)/tools/crypto-qr-code/CryptoGenerator.tsx
index dacb2b2..6084a13 100644
--- a/src/app/(main)/(marketing)/tools/crypto-qr-code/CryptoGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/crypto-qr-code/CryptoGenerator.tsx
@@ -135,7 +135,7 @@ export default function CryptoGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="crypto" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/crypto-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/crypto-qr-code/page.tsx
index 1f80eca..748da8d 100644
--- a/src/app/(main)/(marketing)/tools/crypto-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/crypto-qr-code/page.tsx
@@ -10,24 +10,24 @@ import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils
// SEO Optimized Metadata
export const metadata: Metadata = {
title: {
- absolute: 'Free Crypto QR Code Generator | Krypto QR Code Erstellen | QR Master',
+ absolute: 'Crypto Wallet QR Code Generator - BTC, ETH, USDT',
},
- description: 'Create a QR code for your Crypto wallet address. Erstelle Bitcoin & Ethereum QR Codes für einfache Zahlungen. Supports BTC, ETH, USDT & more.',
+ description: 'Turn a wallet address into a QR code so nobody retypes 42 characters. Supports Bitcoin, Ethereum, USDT, Solana and more. Free, no signup, no wallet access.',
keywords: ['crypto qr code', 'bitcoin qr generator', 'ethereum qr code', 'crypto wallet qr', 'donation qr code', 'krypto qr code', 'bitcoin qr code erstellen', 'kryptowährung qr code', 'wallet adresse qr code'],
alternates: {
canonical: 'https://www.qrmaster.net/tools/crypto-qr-code',
},
openGraph: {
- title: 'Free Crypto QR Code Generator | QR Master',
- description: 'Generate QR codes to accept Crypto payments securely. Supports BTC, ETH, SOL.',
+ title: 'Crypto Wallet QR Code Generator - BTC, ETH, USDT',
+ description: 'Turn a wallet address into a QR code so nobody retypes 42 characters. No wallet access.',
type: 'website',
url: 'https://www.qrmaster.net/tools/crypto-qr-code',
images: [{ url: '/og-crypto-generator.png', width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
- title: 'Free Crypto QR Code Generator',
- description: 'Create secure QR codes for your crypto wallet.',
+ title: 'Crypto Wallet QR Code Generator - BTC, ETH, USDT',
+ description: 'Turn a wallet address into a QR code so nobody retypes 42 characters. No wallet access.',
},
robots: {
index: true,
@@ -143,7 +143,7 @@ export default function CryptoQRCodePage() {
- Free Tool — Secure & Private
+ Free Tool - Secure & Private
diff --git a/src/app/(main)/(marketing)/tools/email-qr-code/EmailGenerator.tsx b/src/app/(main)/(marketing)/tools/email-qr-code/EmailGenerator.tsx
index 028b54f..d0a4834 100644
--- a/src/app/(main)/(marketing)/tools/email-qr-code/EmailGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/email-qr-code/EmailGenerator.tsx
@@ -104,7 +104,7 @@ export default function EmailGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="url" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/email-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/email-qr-code/page.tsx
index ef9c25d..685a410 100644
--- a/src/app/(main)/(marketing)/tools/email-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/email-qr-code/page.tsx
@@ -108,7 +108,7 @@ export default function EmailPage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
diff --git a/src/app/(main)/(marketing)/tools/event-qr-code/EventGenerator.tsx b/src/app/(main)/(marketing)/tools/event-qr-code/EventGenerator.tsx
index 7cf66f4..a25465d 100644
--- a/src/app/(main)/(marketing)/tools/event-qr-code/EventGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/event-qr-code/EventGenerator.tsx
@@ -114,7 +114,7 @@ export default function EventGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="meeting" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/event-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/event-qr-code/page.tsx
index dc89650..05c73b0 100644
--- a/src/app/(main)/(marketing)/tools/event-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/event-qr-code/page.tsx
@@ -139,7 +139,7 @@ export default function EventQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
diff --git a/src/app/(main)/(marketing)/tools/facebook-qr-code/FacebookGenerator.tsx b/src/app/(main)/(marketing)/tools/facebook-qr-code/FacebookGenerator.tsx
index 65c32a0..4480846 100644
--- a/src/app/(main)/(marketing)/tools/facebook-qr-code/FacebookGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/facebook-qr-code/FacebookGenerator.tsx
@@ -85,7 +85,7 @@ export default function FacebookGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="social" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/facebook-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/facebook-qr-code/page.tsx
index f33fdb4..78da1fa 100644
--- a/src/app/(main)/(marketing)/tools/facebook-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/facebook-qr-code/page.tsx
@@ -10,24 +10,24 @@ import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils
// SEO Optimized Metadata
export const metadata: Metadata = {
title: {
- absolute: 'Free Facebook QR Code Generator | Get Likes & Follows | QR Master',
+ absolute: 'Facebook QR Code Generator - Page, Profile or Group',
},
- description: 'Create a QR code for your Facebook Page, Profile, or Group. Facebook QR Code erstellen. Scanners follow you instantly. Free & Easy.',
+ description: 'Turn a Facebook Page, profile or group into a scannable QR code. One scan opens it in the app. Add your colors and logo, download PNG or SVG. Free, no signup.',
keywords: ['facebook qr code', 'fb qr generator', 'facebook page qr', 'follow qr code', 'social media qr code', 'facebook qr code erstellen', 'facebook seite qr code', 'facebook gruppe qr code', 'facebook profil qr code', 'mehr likes qr code'],
alternates: {
canonical: 'https://www.qrmaster.net/tools/facebook-qr-code',
},
openGraph: {
- title: 'Free Facebook QR Code Generator | QR Master',
- description: 'Generate QR codes to grow your Facebook audience. Instant app redirect.',
+ title: 'Facebook QR Code Generator - Page, Profile or Group',
+ description: 'One scan opens your Page, profile or group in the app. Your colors, your logo, PNG or SVG.',
type: 'website',
url: 'https://www.qrmaster.net/tools/facebook-qr-code',
images: [{ url: '/og-facebook-generator.png', width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
- title: 'Free Facebook QR Code Generator',
- description: 'Create QR codes for Facebook. Boost your engagement.',
+ title: 'Facebook QR Code Generator - Page, Profile or Group',
+ description: 'One scan opens your Page, profile or group in the app. Your colors, your logo, PNG or SVG.',
},
robots: {
index: true,
@@ -139,7 +139,7 @@ export default function FacebookQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
@@ -285,7 +285,7 @@ export default function FacebookQRCodePage() {
Where a Facebook QR Code Works Best
- Facebook is where local communities, events, and groups live. A QR code bridges the gap between a physical location and your page, group, or event — no searching, no typos.
+ Facebook is where local communities, events, and groups live. A QR code bridges the gap between a physical location and your page, group, or event - no searching, no typos.
diff --git a/src/app/(main)/(marketing)/tools/geolocation-qr-code/GeolocationGenerator.tsx b/src/app/(main)/(marketing)/tools/geolocation-qr-code/GeolocationGenerator.tsx
index 80e8541..3e9cdf1 100644
--- a/src/app/(main)/(marketing)/tools/geolocation-qr-code/GeolocationGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/geolocation-qr-code/GeolocationGenerator.tsx
@@ -105,7 +105,7 @@ export default function GeolocationGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="url" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/geolocation-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/geolocation-qr-code/page.tsx
index adf65e8..daf00cf 100644
--- a/src/app/(main)/(marketing)/tools/geolocation-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/geolocation-qr-code/page.tsx
@@ -10,24 +10,24 @@ import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils
// SEO Optimized Metadata
export const metadata: Metadata = {
title: {
- absolute: 'Free Geolocation QR Code Generator | Standort & Map Links | QR Master',
+ absolute: 'Location QR Code Generator - Maps & Directions, Free',
},
- description: 'Create a QR code for a specific location. Erstelle einen Map QR Code für Google Maps. Coordinates & Directions instantly. Standort teilen leicht gemacht.',
+ description: 'Turn an address or GPS coordinates into a QR code. One scan opens directions in Google Maps or Apple Maps. For signage, flyers and event wayfinding. No signup.',
keywords: ['location qr code', 'maps qr code', 'google maps qr generator', 'geolocation qr', 'coordinates qr code', 'standort qr code', 'google maps qr code erstellen', 'koordinaten qr code', 'wegbeschreibung qr code', 'maps qr code generator'],
alternates: {
canonical: 'https://www.qrmaster.net/tools/geolocation-qr-code',
},
openGraph: {
- title: 'Free Geolocation QR Code Generator | QR Master',
- description: 'Navigate users to any location with a QR code. Opens directly in Google Maps.',
+ title: 'Location QR Code Generator - Maps & Directions, Free',
+ description: 'One scan opens directions in Google Maps or Apple Maps. For signage and wayfinding.',
type: 'website',
url: 'https://www.qrmaster.net/tools/geolocation-qr-code',
images: [{ url: '/og-geolocation-generator.png', width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
- title: 'Free Geolocation QR Code Generator',
- description: 'Create QR codes for maps and locations. Instant and free.',
+ title: 'Location QR Code Generator - Maps & Directions, Free',
+ description: 'One scan opens directions in Google Maps or Apple Maps. For signage and wayfinding.',
},
robots: {
index: true,
@@ -139,7 +139,7 @@ export default function GeolocationQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
diff --git a/src/app/(main)/(marketing)/tools/google-review-qr-code/GoogleReviewGenerator.tsx b/src/app/(main)/(marketing)/tools/google-review-qr-code/GoogleReviewGenerator.tsx
index c1b88cf..6549c59 100644
--- a/src/app/(main)/(marketing)/tools/google-review-qr-code/GoogleReviewGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/google-review-qr-code/GoogleReviewGenerator.tsx
@@ -86,7 +86,7 @@ export default function GoogleReviewGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="googleReview" />
diff --git a/src/app/(main)/(marketing)/tools/google-review-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/google-review-qr-code/page.tsx
index e76959d..a28efc2 100644
--- a/src/app/(main)/(marketing)/tools/google-review-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/google-review-qr-code/page.tsx
@@ -10,10 +10,10 @@ import { generateSoftwareAppSchema } from '@/lib/schema-utils';
export const metadata: Metadata = {
title: {
- absolute: 'Google Review QR Code Generator — Free | QR Master',
+ absolute: 'Google Review QR Code Generator - Free, No Signup',
},
description:
- 'Create a QR code for your Google Reviews in seconds. Customers scan once and land directly on your review form. Free, no signup required.',
+ 'Ask for the review while the customer is still standing there. One scan opens your Google review form - no searching, no link. Free, no signup, PNG or SVG.',
keywords: [
'qr code for google reviews',
'qr code generator for google reviews',
@@ -29,17 +29,15 @@ export const metadata: Metadata = {
},
},
openGraph: {
- title: 'Google Review QR Code Generator — Free | QR Master',
- description:
- 'Create a QR code that takes customers directly to your Google review form. More reviews, less friction.',
+ title: 'Google Review QR Code Generator - Free, No Signup',
+ description: 'Ask while the customer is still standing there. One scan opens your Google review form.',
type: 'website',
url: 'https://www.qrmaster.net/tools/google-review-qr-code',
},
twitter: {
card: 'summary_large_image',
- title: 'Google Review QR Code Generator — Free',
- description:
- 'Create a QR code that takes customers directly to your Google review form.',
+ title: 'Google Review QR Code Generator - Free, No Signup',
+ description: 'Ask while the customer is still standing there. One scan opens your Google review form.',
},
robots: {
index: true,
@@ -103,7 +101,7 @@ const jsonLd = {
name: 'How do I find my Google Review link?',
acceptedAnswer: {
'@type': 'Answer',
- text: 'Open Google Maps → search for your business → click Share → Copy link. Alternatively, go to your Google Business Profile dashboard → click "Get more reviews" — this gives you a direct review shortlink.',
+ text: 'Open Google Maps → search for your business → click Share → Copy link. Alternatively, go to your Google Business Profile dashboard → click "Get more reviews" - this gives you a direct review shortlink.',
},
},
{
@@ -188,13 +186,13 @@ export default function GoogleReviewQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
Google Review QR Code
- Generator — Free
+ Generator - Free
@@ -391,7 +389,7 @@ export default function GoogleReviewQRCodePage() {
- {/* WHY REVIEWS MATTER — STATISTICS */}
+ {/* WHY REVIEWS MATTER - STATISTICS */}
@@ -405,7 +403,7 @@ export default function GoogleReviewQRCodePage() {
A Google Review QR code reduces the friction
- between a satisfied customer and a published review — the single
+ between a satisfied customer and a published review - the single
biggest barrier to getting more reviews.
@@ -416,8 +414,8 @@ export default function GoogleReviewQRCodePage() {
of consumers will leave a review for a business{' '}
- if they are asked — but most businesses never
- ask, or ask via email where completion rates drop to 1–3%.
+ if they are asked - but most businesses never
+ ask, or ask via email where completion rates drop to 1-3%.
Source:{' '}
@@ -439,7 +437,7 @@ export default function GoogleReviewQRCodePage() {
increase in conversion rates for products and businesses with
reviews compared to those without. Capturing reviews at the
- point of sale — where satisfaction is highest — maximizes this
+ point of sale - where satisfaction is highest - maximizes this
effect.
@@ -470,13 +468,13 @@ export default function GoogleReviewQRCodePage() {
Why Google Review QR Codes Work Better Than Asking Verbally
-
Verbally asking for a review creates a promise customers intend to keep but rarely fulfill. The moment they leave your business, the intention fades. A Google Review QR code shortens the gap between the moment of satisfaction and the act of leaving a review to a single scan — while the experience is still fresh and the customer is still engaged.
+
Verbally asking for a review creates a promise customers intend to keep but rarely fulfill. The moment they leave your business, the intention fades. A Google Review QR code shortens the gap between the moment of satisfaction and the act of leaving a review to a single scan - while the experience is still fresh and the customer is still engaged.
The Best Placement for Google Review QR Codes
-
Placement is everything. The highest-performing locations are those where customers are already pausing: on printed receipts so they see it while reviewing the bill, on table tent cards at restaurants between ordering and paying, on the front door or exit so it is the last thing they see when leaving satisfied, and on packaging inserts inside product boxes that customers open at home after a purchase. Display the QR code at roughly A5 size with a clear label such as "Happy with your visit? Leave us a Google Review" — customers do not need instructions beyond that. Checkout counters and front desk areas work especially well because staff can gesture toward the code while the customer is already in a positive frame of mind.
+
Placement is everything. The highest-performing locations are those where customers are already pausing: on printed receipts so they see it while reviewing the bill, on table tent cards at restaurants between ordering and paying, on the front door or exit so it is the last thing they see when leaving satisfied, and on packaging inserts inside product boxes that customers open at home after a purchase. Display the QR code at roughly A5 size with a clear label such as "Happy with your visit? Leave us a Google Review" - customers do not need instructions beyond that. Checkout counters and front desk areas work especially well because staff can gesture toward the code while the customer is already in a positive frame of mind.
How Many More Reviews Will You Get?
The BrightLocal research above shows 70% of consumers will leave a review if asked, and asking in person at the point of sale removes the friction that email follow-ups add. The reason is timing: a QR code captures the customer at peak satisfaction, requiring no extra steps beyond scanning and tapping the star rating. Email review requests, by contrast, arrive hours or days later when the emotional high has passed and competing priorities fill the inbox. Even a modest increase in monthly reviews compounds over a year into a stronger local search presence, since Google's ranking algorithm weighs both review count and recency.
Responding to Reviews: What to Do After You Collect Them
-
Collecting reviews is only half the strategy. Responding to every review — positive and negative — signals to Google that your business is active and engaged, which supports local ranking. For positive reviews, a brief personalised thank-you (mentioning a specific detail if possible) reinforces the relationship. For critical reviews, acknowledge the issue, apologise where appropriate, and invite further contact offline. Google surfaces response rate and speed in its quality signals, so even a short reply within 24 hours outperforms silence. To understand which QR code placements are driving the most scans before reviewers land on Google, use QR code scan tracking to measure volume by location and time of day.
+
Collecting reviews is only half the strategy. Responding to every review - positive and negative - signals to Google that your business is active and engaged, which supports local ranking. For positive reviews, a brief personalised thank-you (mentioning a specific detail if possible) reinforces the relationship. For critical reviews, acknowledge the issue, apologise where appropriate, and invite further contact offline. Google surfaces response rate and speed in its quality signals, so even a short reply within 24 hours outperforms silence. To understand which QR code placements are driving the most scans before reviewers land on Google, use QR code scan tracking to measure volume by location and time of day.
@@ -511,7 +509,7 @@ export default function GoogleReviewQRCodePage() {
href: '/dynamic-qr-code-generator',
title: 'Dynamic QR Code Generator',
description:
- 'Update your review link or redirect to a different page anytime — no reprint needed.',
+ 'Update your review link or redirect to a different page anytime - no reprint needed.',
ctaLabel: 'Create dynamic QR code',
},
{
diff --git a/src/app/(main)/(marketing)/tools/instagram-qr-code/InstagramGenerator.tsx b/src/app/(main)/(marketing)/tools/instagram-qr-code/InstagramGenerator.tsx
index c8957bc..4413e9c 100644
--- a/src/app/(main)/(marketing)/tools/instagram-qr-code/InstagramGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/instagram-qr-code/InstagramGenerator.tsx
@@ -90,7 +90,7 @@ export default function InstagramGenerator() {
return (
<>
-
setShowPopup(false)} />
+ setShowPopup(false)} variant="social" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/instagram-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/instagram-qr-code/page.tsx
index 1be3881..94b87a3 100644
--- a/src/app/(main)/(marketing)/tools/instagram-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/instagram-qr-code/page.tsx
@@ -11,9 +11,9 @@ 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',
+ absolute: 'Instagram QR Code Generator - Free, No Signup',
},
- description: 'Create a free Instagram QR code for your profile. Scanners follow you instantly — no app login required. Customizable & downloadable in seconds.',
+ description: 'Turn your Instagram profile into a scannable QR code. One scan opens your profile in the app - no login needed. Add your colors and logo, download PNG or SVG.',
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',
@@ -23,16 +23,16 @@ export const metadata: Metadata = {
},
},
openGraph: {
- title: 'Free Instagram QR Code Generator | QR Master',
- description: 'Generate QR codes to grow your Instagram following. Instant app redirect.',
+ title: 'Instagram QR Code Generator - Free, No Signup',
+ description: 'One scan opens your Instagram profile in the app. Your colors, your logo, PNG or SVG.',
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.',
+ title: 'Instagram QR Code Generator - Free, No Signup',
+ description: 'One scan opens your Instagram profile in the app. Your colors, your logo, PNG or SVG.',
},
robots: {
index: true,
@@ -146,12 +146,12 @@ export default function InstagramQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
Instagram QR Code Generator
- — Boost Your Following
+ - Boost Your Following
@@ -317,7 +317,7 @@ export default function InstagramQRCodePage() {
{
href: '/tools/whatsapp-qr-code',
title: 'WhatsApp QR Code',
- description: 'Let customers message you instantly — no number sharing required.',
+ description: 'Let customers message you instantly - no number sharing required.',
ctaLabel: 'Create WhatsApp QR',
},
{
@@ -329,7 +329,7 @@ export default function InstagramQRCodePage() {
{
href: '/dynamic-qr-code-generator',
title: 'Dynamic QR Codes',
- description: 'Track how many people scan your Instagram QR code — by day, device, and city.',
+ description: 'Track how many people scan your Instagram QR code - by day, device, and city.',
ctaLabel: 'Try Dynamic QR',
},
]}
@@ -342,25 +342,25 @@ export default function InstagramQRCodePage() {
Where an Instagram QR Code Beats a Profile Link
- A profile link only works where people can click. An Instagram QR code works in the physical world — wherever a customer is already looking at your brand but can't tap a link.
+ A profile link only works where people can click. An Instagram QR code works in the physical world - wherever a customer is already looking at your brand but can't tap a link.
Cafés, salons & shops
- Counter cards, mirrors, and window stickers turn walk-in customers into followers while they wait — the moment they're most likely to check your feed.
+ Counter cards, mirrors, and window stickers turn walk-in customers into followers while they wait - the moment they're most likely to check your feed.
Packaging & unboxing
- A QR code on the package insert catches customers at the unboxing moment — ideal for brands that want user-generated content and repeat buyers.
+ A QR code on the package insert catches customers at the unboxing moment - ideal for brands that want user-generated content and repeat buyers.
Events & pop-ups
- Booth banners and table displays let visitors follow you in two seconds instead of searching your handle — and misspelling it.
+ Booth banners and table displays let visitors follow you in two seconds instead of searching your handle - and misspelling it.
@@ -369,7 +369,7 @@ export default function InstagramQRCodePage() {
dynamic QR code {' '}
and compare placements with{' '}
QR code tracking {' '}
- — scans by time, device, and location.
+ - scans by time, device, and location.
diff --git a/src/app/(main)/(marketing)/tools/paypal-qr-code/PayPalGenerator.tsx b/src/app/(main)/(marketing)/tools/paypal-qr-code/PayPalGenerator.tsx
index b11beef..5aea360 100644
--- a/src/app/(main)/(marketing)/tools/paypal-qr-code/PayPalGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/paypal-qr-code/PayPalGenerator.tsx
@@ -126,7 +126,7 @@ export default function PayPalGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="crypto" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/paypal-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/paypal-qr-code/page.tsx
index 4c7963a..29dc963 100644
--- a/src/app/(main)/(marketing)/tools/paypal-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/paypal-qr-code/page.tsx
@@ -140,7 +140,7 @@ export default function PayPalQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
diff --git a/src/app/(main)/(marketing)/tools/sms-qr-code/SMSGenerator.tsx b/src/app/(main)/(marketing)/tools/sms-qr-code/SMSGenerator.tsx
index 7bce37e..b41429e 100644
--- a/src/app/(main)/(marketing)/tools/sms-qr-code/SMSGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/sms-qr-code/SMSGenerator.tsx
@@ -88,7 +88,7 @@ export default function SMSGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="url" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/sms-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/sms-qr-code/page.tsx
index 75d0ba7..f3ba658 100644
--- a/src/app/(main)/(marketing)/tools/sms-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/sms-qr-code/page.tsx
@@ -123,7 +123,7 @@ export default function SMSQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
diff --git a/src/app/(main)/(marketing)/tools/teams-qr-code/TeamsGenerator.tsx b/src/app/(main)/(marketing)/tools/teams-qr-code/TeamsGenerator.tsx
index 6ad7369..6d2fd00 100644
--- a/src/app/(main)/(marketing)/tools/teams-qr-code/TeamsGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/teams-qr-code/TeamsGenerator.tsx
@@ -108,7 +108,7 @@ export default function TeamsGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="meeting" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/teams-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/teams-qr-code/page.tsx
index c90a486..e9b4e5d 100644
--- a/src/app/(main)/(marketing)/tools/teams-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/teams-qr-code/page.tsx
@@ -12,24 +12,24 @@ import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils
// SEO Optimized Metadata
export const metadata: Metadata = {
title: {
- absolute: 'Free Microsoft Teams QR Code Generator | Join Meetings | QR Master',
+ absolute: 'Microsoft Teams Meeting QR Code Generator - Free',
},
- description: 'Create a QR code for your Microsoft Teams meeting. Teams QR Code erstellen. Attendees scan to join instantly. For hybrid meetings & office displays.',
+ description: 'Turn a Teams meeting link into a QR code. Attendees scan to join - no meeting ID, no copy-paste. Print it for meeting rooms, desks and hybrid event signage.',
keywords: ['teams qr code', 'microsoft teams meeting qr', 'join teams qr code', 'meeting room qr', 'teams invitation qr', 'hybrid meeting qr code', 'microsoft teams qr code erstellen', 'teams meeting qr code', 'teams besprechung qr', 'teams beitreten qr'],
alternates: {
canonical: 'https://www.qrmaster.net/tools/teams-qr-code',
},
openGraph: {
- title: 'Free Microsoft Teams QR Code Generator | QR Master',
- description: 'Generate QR codes for Teams meetings. One scan to join instantly.',
+ title: 'Microsoft Teams Meeting QR Code Generator - Free',
+ description: 'Attendees scan to join. No meeting ID, no copy-paste. Print it for meeting rooms and desks.',
type: 'website',
url: 'https://www.qrmaster.net/tools/teams-qr-code',
images: [{ url: '/og-teams-generator.png', width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
- title: 'Free Microsoft Teams QR Code Generator',
- description: 'Create Teams meeting QR codes. Instant and free.',
+ title: 'Microsoft Teams Meeting QR Code Generator - Free',
+ description: 'Attendees scan to join. No meeting ID, no copy-paste. Print it for meeting rooms and desks.',
},
robots: {
index: true,
@@ -132,7 +132,7 @@ export default function TeamsQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
@@ -267,13 +267,13 @@ export default function TeamsQRCodePage() {
- setShowPopup(false)} />
+ setShowPopup(false)} variant="url" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/text-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/text-qr-code/page.tsx
index 0d74732..7a1e885 100644
--- a/src/app/(main)/(marketing)/tools/text-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/text-qr-code/page.tsx
@@ -139,7 +139,7 @@ export default function TextQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
diff --git a/src/app/(main)/(marketing)/tools/tiktok-qr-code/TikTokGenerator.tsx b/src/app/(main)/(marketing)/tools/tiktok-qr-code/TikTokGenerator.tsx
index 2cfe4c4..492e1e8 100644
--- a/src/app/(main)/(marketing)/tools/tiktok-qr-code/TikTokGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/tiktok-qr-code/TikTokGenerator.tsx
@@ -90,7 +90,7 @@ export default function TiktokGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="social" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/tiktok-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/tiktok-qr-code/page.tsx
index 7d675ff..4cae875 100644
--- a/src/app/(main)/(marketing)/tools/tiktok-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/tiktok-qr-code/page.tsx
@@ -10,24 +10,24 @@ import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils
// SEO Optimized Metadata
export const metadata: Metadata = {
title: {
- absolute: 'Free TikTok QR Code Generator | Get Followers | QR Master',
+ absolute: 'TikTok QR Code Generator - Free, No Signup',
},
- description: 'Create a QR code for your TikTok profile. TikTok QR Code erstellen. Scanners follow you instantly. Customize with colors and frames.',
+ description: 'Turn your TikTok profile into a scannable QR code. One scan opens your profile in the app. Add your colors and logo, download PNG or SVG. Free, no signup.',
keywords: ['tiktok qr code', 'tik tok qr generator', 'tiktok follow qr', 'social media qr code', 'tiktok profile qr', 'tiktok qr code erstellen', 'tiktok profil qr code', 'mehr tiktok follower', 'tiktok scanncode'],
alternates: {
canonical: 'https://www.qrmaster.net/tools/tiktok-qr-code',
},
openGraph: {
- title: 'Free TikTok QR Code Generator | QR Master',
- description: 'Generate QR codes to grow your TikTok following. Instant app redirect.',
+ title: 'TikTok QR Code Generator - Free, No Signup',
+ description: 'One scan opens your TikTok profile in the app. Your colors, your logo, PNG or SVG.',
type: 'website',
url: 'https://www.qrmaster.net/tools/tiktok-qr-code',
images: [{ url: '/og-tiktok-generator.png', width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
- title: 'Free TikTok QR Code Generator',
- description: 'Create QR codes for TikTok. Get more followers.',
+ title: 'TikTok QR Code Generator - Free, No Signup',
+ description: 'One scan opens your TikTok profile in the app. Your colors, your logo, PNG or SVG.',
},
robots: {
index: true,
@@ -140,7 +140,7 @@ export default function TiktokQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
diff --git a/src/app/(main)/(marketing)/tools/twitter-qr-code/TwitterGenerator.tsx b/src/app/(main)/(marketing)/tools/twitter-qr-code/TwitterGenerator.tsx
index b7d37cb..f9bab45 100644
--- a/src/app/(main)/(marketing)/tools/twitter-qr-code/TwitterGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/twitter-qr-code/TwitterGenerator.tsx
@@ -90,7 +90,7 @@ export default function TwitterGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="social" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/twitter-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/twitter-qr-code/page.tsx
index d356440..2732cd0 100644
--- a/src/app/(main)/(marketing)/tools/twitter-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/twitter-qr-code/page.tsx
@@ -10,24 +10,24 @@ import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils
// SEO Optimized Metadata
export const metadata: Metadata = {
title: {
- absolute: 'Free Twitter (X) QR Code Generator | Follow & Connect | QR Master',
+ absolute: 'X (Twitter) QR Code Generator - Free, No Signup',
},
- description: 'Create a QR code for your X (Twitter) profile. Twitter QR Code erstellen. Scanners follow you instantly. Free & Customizable.',
+ description: 'Turn your X profile into a scannable QR code. One scan opens your profile in the app. Add your colors and logo, download PNG or SVG. Free, no signup required.',
keywords: ['twitter qr code', 'x qr generator', 'twitter follow qr', 'social media qr code', 'x profile qr', 'twitter qr code erstellen', 'x qr code erstellen', 'twitter profil qr code', 'x profil qr code'],
alternates: {
canonical: 'https://www.qrmaster.net/tools/twitter-qr-code',
},
openGraph: {
- title: 'Free Twitter (X) QR Code Generator | QR Master',
- description: 'Generate QR codes to grow your X (Twitter) following. Instant app redirect.',
+ title: 'X (Twitter) QR Code Generator - Free, No Signup',
+ description: 'One scan opens your X profile in the app. Your colors, your logo, PNG or SVG.',
type: 'website',
url: 'https://www.qrmaster.net/tools/twitter-qr-code',
images: [{ url: '/og-twitter-generator.png', width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
- title: 'Free Twitter (X) QR Code Generator',
- description: 'Create QR codes for X. Boost your following.',
+ title: 'X (Twitter) QR Code Generator - Free, No Signup',
+ description: 'One scan opens your X profile in the app. Your colors, your logo, PNG or SVG.',
},
robots: {
index: true,
@@ -139,7 +139,7 @@ export default function TwitterQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
@@ -301,7 +301,7 @@ export default function TwitterQRCodePage() {
Live commentary
- Sports venues, meetups, and community events display the code where the live conversation happens — the moment people want to join in.
+ Sports venues, meetups, and community events display the code where the live conversation happens - the moment people want to join in.
@@ -310,7 +310,7 @@ export default function TwitterQRCodePage() {
dynamic QR code {' '}
with{' '}
QR code tracking {' '}
- — the printed code stays valid even if the destination changes.
+ - the printed code stays valid even if the destination changes.
diff --git a/src/app/(main)/(marketing)/tools/url-qr-code/URLGenerator.tsx b/src/app/(main)/(marketing)/tools/url-qr-code/URLGenerator.tsx
index af911db..400adeb 100644
--- a/src/app/(main)/(marketing)/tools/url-qr-code/URLGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/url-qr-code/URLGenerator.tsx
@@ -84,7 +84,7 @@ export default function URLGenerator() {
return (
<>
-
setShowPopup(false)} />
+ setShowPopup(false)} variant="url" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/url-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/url-qr-code/page.tsx
index 0e02ed0..954db2c 100644
--- a/src/app/(main)/(marketing)/tools/url-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/url-qr-code/page.tsx
@@ -10,24 +10,24 @@ import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils
// SEO Optimized Metadata
export const metadata: Metadata = {
title: {
- absolute: 'Free URL QR Code Generator | Link to Any Website | QR Master',
+ absolute: 'URL QR Code Generator - Free, No Signup, Never Expires',
},
- description: 'Create a QR code for your website. Erstelle kostenlos einen QR Code für deine Webseite. Static and free forever. Link zu jeder URL.',
+ description: 'Paste any link, get a scannable QR code. Unlimited static codes that never expire, on every plan including free. Download print-ready PNG or SVG. No signup.',
keywords: ['url qr code', 'website qr code', 'link qr generator', 'free qr code generator', 'url to qr', 'qr code erstellen', 'link qr code erstellen', 'website qr code generator', 'kostenlos qr code erstellen', 'url zu qr code', 'webseite verlinken qr'],
alternates: {
canonical: 'https://www.qrmaster.net/tools/url-qr-code',
},
openGraph: {
- title: 'Free URL QR Code Generator | QR Master',
- description: 'Turn any URL into a QR code. Share websites instantly.',
+ title: 'URL QR Code Generator - Free, No Signup, Never Expires',
+ description: 'Paste any link, get a scannable code. Unlimited static codes that never expire.',
type: 'website',
url: 'https://www.qrmaster.net/tools/url-qr-code',
images: [{ url: '/og-url-generator.png', width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
- title: 'Free URL QR Code Generator',
- description: 'Create QR codes for any link. Instant and free.',
+ title: 'URL QR Code Generator - Free, No Signup, Never Expires',
+ description: 'Paste any link, get a scannable code. Unlimited static codes that never expire.',
},
robots: {
index: true,
@@ -123,7 +123,7 @@ export default function URLQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
@@ -232,13 +232,13 @@ export default function URLQRCodePage() {
URL QR Code Use Cases: When to Use a Link QR Code
-
A URL QR code is the simplest and most universal type of QR code: scan it, and a browser opens a specific web address. That simplicity is its strength. Any printed material that references a website becomes interactive the moment you add a URL QR code — no app required, no account needed, no special hardware beyond a standard smartphone camera.
+
A URL QR code is the simplest and most universal type of QR code: scan it, and a browser opens a specific web address. That simplicity is its strength. Any printed material that references a website becomes interactive the moment you add a URL QR code - no app required, no account needed, no special hardware beyond a standard smartphone camera.
Marketing Materials: Flyers, Brochures & Posters
-
Printed marketing materials have a fundamental limitation: they cannot be clicked. A URL QR code solves this by acting as a physical hyperlink. Flyers for an event can link directly to a registration page, eliminating the step of typing a long URL. Brochures can link to a detailed product page, a video demo, or a portfolio. Outdoor posters can point to a landing page with a time-sensitive offer. The critical design principle is placement and contrast: the QR code should appear on a clean background with at least 1 cm of quiet zone around it, and the call-to-action label — such as "Scan to book your spot" — should tell users exactly what they will find before they scan. A URL QR code effectively turns print advertising into a measurable digital funnel.
+
Printed marketing materials have a fundamental limitation: they cannot be clicked. A URL QR code solves this by acting as a physical hyperlink. Flyers for an event can link directly to a registration page, eliminating the step of typing a long URL. Brochures can link to a detailed product page, a video demo, or a portfolio. Outdoor posters can point to a landing page with a time-sensitive offer. The critical design principle is placement and contrast: the QR code should appear on a clean background with at least 1 cm of quiet zone around it, and the call-to-action label - such as "Scan to book your spot" - should tell users exactly what they will find before they scan. A URL QR code effectively turns print advertising into a measurable digital funnel.
Product Packaging & Labels
-
Product packaging is increasingly the first place customers turn for more information after purchase. A URL QR code on a label can link to setup instructions, video tutorials, an FAQ page, a warranty registration form, or a support portal — replacing bulky printed manuals and keeping the information always up to date. For consumables and repeat-purchase products, the QR code can link to a reorder page, transforming packaging from a cost centre into a sales channel. Food and beverage brands use URL QR codes to link to nutritional databases, sourcing information, and sustainability reports. The key advantage over a printed URL is that customers are far more likely to scan than to type a long web address — especially when they are already holding the product in their hands.
+
Product packaging is increasingly the first place customers turn for more information after purchase. A URL QR code on a label can link to setup instructions, video tutorials, an FAQ page, a warranty registration form, or a support portal - replacing bulky printed manuals and keeping the information always up to date. For consumables and repeat-purchase products, the QR code can link to a reorder page, transforming packaging from a cost centre into a sales channel. Food and beverage brands use URL QR codes to link to nutritional databases, sourcing information, and sustainability reports. The key advantage over a printed URL is that customers are far more likely to scan than to type a long web address - especially when they are already holding the product in their hands.
Dynamic vs Static URL QR Codes
-
A static URL QR code encodes the destination directly into the pattern — it is permanent, requires no server, and works forever, but it cannot be changed after printing. If your URL changes, the code breaks. A dynamic QR code works differently: it encodes a short redirect URL that you control, so you can update the destination at any time without reprinting the physical code. Dynamic codes are the right choice for anything printed at scale (packaging runs, banners, long-running campaigns) where reprinting after a URL change would be costly. They also unlock scan analytics — data on how many people scanned, from which device, country, and at what time — which static codes cannot provide. For one-off or low-stakes uses like a personal project or a single event flyer, a free static URL QR code is perfectly sufficient.
+
A static URL QR code encodes the destination directly into the pattern - it is permanent, requires no server, and works forever, but it cannot be changed after printing. If your URL changes, the code breaks. A dynamic QR code works differently: it encodes a short redirect URL that you control, so you can update the destination at any time without reprinting the physical code. Dynamic codes are the right choice for anything printed at scale (packaging runs, banners, long-running campaigns) where reprinting after a URL change would be costly. They also unlock scan analytics - data on how many people scanned, from which device, country, and at what time - which static codes cannot provide. For one-off or low-stakes uses like a personal project or a single event flyer, a free static URL QR code is perfectly sufficient.
diff --git a/src/app/(main)/(marketing)/tools/vcard-qr-code/VCardGenerator.tsx b/src/app/(main)/(marketing)/tools/vcard-qr-code/VCardGenerator.tsx
index 5cac1b0..55bc4fb 100644
--- a/src/app/(main)/(marketing)/tools/vcard-qr-code/VCardGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/vcard-qr-code/VCardGenerator.tsx
@@ -120,7 +120,7 @@ export default function VCardGenerator() {
return (
<>
-
setShowPopup(false)} />
+ setShowPopup(false)} variant="vcard" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/vcard-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/vcard-qr-code/page.tsx
index 1b17447..d3203e3 100644
--- a/src/app/(main)/(marketing)/tools/vcard-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/vcard-qr-code/page.tsx
@@ -11,9 +11,9 @@ import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils
// SEO Optimized Metadata
export const metadata: Metadata = {
title: {
- absolute: 'Free vCard QR Code Generator | QR Master',
+ absolute: 'vCard QR Code Generator - Save to Contacts in One Tap',
},
- 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.',
+ description: 'Put your contact details on a business card as a QR code. One scan saves you to their phone - no typing, no app. Download print-ready PNG or SVG. No signup.',
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',
@@ -23,16 +23,16 @@ export const metadata: Metadata = {
},
},
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.',
+ title: 'vCard QR Code Generator - Save to Contacts in One Tap',
+ description: 'One scan saves you to their phone. No typing, no app. Print-ready PNG or SVG.',
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.',
+ title: 'vCard QR Code Generator - Save to Contacts in One Tap',
+ description: 'One scan saves you to their phone. No typing, no app. Print-ready PNG or SVG.',
},
robots: {
index: true,
@@ -105,7 +105,7 @@ const jsonLd = {
},
'Can I test the QR code before printing?': {
question: 'Can I test the QR code before printing?',
- answer: 'Yes — and you should. Scan the generated code with your own phone camera before sending it to print. If the contact card opens with the correct name, phone, and email, the printed version will behave identically.',
+ answer: 'Yes - and you should. Scan the generated code with your own phone camera before sending it to print. If the contact card opens with the correct name, phone, and email, the printed version will behave identically.',
},
}),
],
@@ -143,7 +143,7 @@ export default function VCardQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
@@ -258,7 +258,7 @@ export default function VCardQRCodePage() {
- {/* WHY DIGITAL BUSINESS CARDS — STATISTICS */}
+ {/* WHY DIGITAL BUSINESS CARDS - STATISTICS */}
@@ -276,7 +276,7 @@ export default function VCardQRCodePage() {
88%
- 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.
+ 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.
Source: Adobe Business Research
@@ -286,10 +286,10 @@ export default function VCardQRCodePage() {
1-tap save
- 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.
+ 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.
- vCard 3.0 / VCF format — supported natively by iOS and Android
+ vCard 3.0 / VCF format - supported natively by iOS and Android
@@ -307,25 +307,25 @@ export default function VCardQRCodePage() {
Where a vCard QR Code Pays Off
- A vCard QR code works anywhere someone should save your details in seconds — without typing anything.
+ A vCard QR code works anywhere someone should save your details in seconds - without typing anything.
Business cards
- Print the QR code on the back of your paper card. Instead of your card ending up in a drawer, your contact lands in the address book — name, phone, email, and company in one tap.
+ Print the QR code on the back of your paper card. Instead of your card ending up in a drawer, your contact lands in the address book - name, phone, email, and company in one tap.
Events & trade shows
- Put it on your badge, booth signage, or presentation slides. Attendees scan while you talk — no fumbling with cards, and no follow-up emails asking for your details.
+ Put it on your badge, booth signage, or presentation slides. Attendees scan while you talk - no fumbling with cards, and no follow-up emails asking for your details.
Everyday networking
- Save it as your phone wallpaper or in your wallet. Any spontaneous meeting becomes a saved contact — perfect for freelancers, sales teams, and consultants.
+ Save it as your phone wallpaper or in your wallet. Any spontaneous meeting becomes a saved contact - perfect for freelancers, sales teams, and consultants.
@@ -393,11 +393,11 @@ export default function VCardQRCodePage() {
/>
diff --git a/src/app/(main)/(marketing)/tools/whatsapp-qr-code/WhatsAppGenerator.tsx b/src/app/(main)/(marketing)/tools/whatsapp-qr-code/WhatsAppGenerator.tsx
index bd0a779..89aec73 100644
--- a/src/app/(main)/(marketing)/tools/whatsapp-qr-code/WhatsAppGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/whatsapp-qr-code/WhatsAppGenerator.tsx
@@ -95,7 +95,7 @@ export default function WhatsappGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="social" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/whatsapp-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/whatsapp-qr-code/page.tsx
index 56f200b..c6ab7a9 100644
--- a/src/app/(main)/(marketing)/tools/whatsapp-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/whatsapp-qr-code/page.tsx
@@ -139,7 +139,7 @@ export default function WhatsappQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
@@ -280,13 +280,13 @@ export default function WhatsappQRCodePage() {
How to Use WhatsApp QR Codes for Business
-
WhatsApp QR codes remove the biggest obstacle between a potential customer and a conversation: saving a phone number. Instead of typing digits manually, customers scan once and land directly in a chat — with your pre-filled message already loaded and ready to send. For businesses that rely on fast, personal communication, that friction reduction is significant.
+
WhatsApp QR codes remove the biggest obstacle between a potential customer and a conversation: saving a phone number. Instead of typing digits manually, customers scan once and land directly in a chat - with your pre-filled message already loaded and ready to send. For businesses that rely on fast, personal communication, that friction reduction is significant.
Customer Support & Service Teams
Support teams can use WhatsApp QR codes on help pages, packaging inserts, and warranty cards to give customers a direct line without publishing a number publicly in plain text. The pre-filled message field is especially powerful here: you can pre-load context such as "Hi, I need help with my order #" so agents receive structured requests from the start. This reduces back-and-forth and speeds up resolution time. Place the QR code at the end of a printed receipt or inside a product box to catch customers at the exact moment they might need help.
Restaurants, Cafes & Retail Stores
-
Physical businesses benefit the most from WhatsApp QR codes placed at the point of purchase. A cafe can display a QR code at the counter that pre-fills "I'd like to place a takeaway order" — customers scan, confirm their order via WhatsApp, and staff prepare it without phone calls interrupting busy periods. Restaurants can use QR codes on table cards for reservations or feedback. Retail stores can place them near fitting rooms so shoppers can ask about sizes or alternative products without waiting for staff. The key is pairing the QR code with a clear call-to-action label like "Chat with us on WhatsApp" and placing it at eye level.
+
Physical businesses benefit the most from WhatsApp QR codes placed at the point of purchase. A cafe can display a QR code at the counter that pre-fills "I'd like to place a takeaway order" - customers scan, confirm their order via WhatsApp, and staff prepare it without phone calls interrupting busy periods. Restaurants can use QR codes on table cards for reservations or feedback. Retail stores can place them near fitting rooms so shoppers can ask about sizes or alternative products without waiting for staff. The key is pairing the QR code with a clear call-to-action label like "Chat with us on WhatsApp" and placing it at eye level.
Marketing Campaigns & Lead Generation
-
WhatsApp QR codes on printed flyers, outdoor posters, or event banners create a measurable bridge from offline marketing to a live conversation. Unlike a website URL, a WhatsApp link initiates a direct dialogue — which converts at a much higher rate than a contact form. For lead generation campaigns, pre-fill the message with the campaign name or offer so you can track which placement is driving inbound chats. To measure QR code performance across multiple placements, combine your WhatsApp QR code strategy with QR code scan analytics to see which posters, flyers, or locations generate the most engagement.
+
WhatsApp QR codes on printed flyers, outdoor posters, or event banners create a measurable bridge from offline marketing to a live conversation. Unlike a website URL, a WhatsApp link initiates a direct dialogue - which converts at a much higher rate than a contact form. For lead generation campaigns, pre-fill the message with the campaign name or offer so you can track which placement is driving inbound chats. To measure QR code performance across multiple placements, combine your WhatsApp QR code strategy with QR code scan analytics to see which posters, flyers, or locations generate the most engagement.
diff --git a/src/app/(main)/(marketing)/tools/wifi-qr-code/WiFiGenerator.tsx b/src/app/(main)/(marketing)/tools/wifi-qr-code/WiFiGenerator.tsx
index e94eea2..50a9175 100644
--- a/src/app/(main)/(marketing)/tools/wifi-qr-code/WiFiGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/wifi-qr-code/WiFiGenerator.tsx
@@ -96,7 +96,7 @@ export default function WiFiGenerator() {
return (
<>
-
setShowPopup(false)} />
+ setShowPopup(false)} variant="wifi" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/wifi-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/wifi-qr-code/page.tsx
index 30295f3..8447a7a 100644
--- a/src/app/(main)/(marketing)/tools/wifi-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/wifi-qr-code/page.tsx
@@ -158,7 +158,7 @@ export default function WiFiQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
@@ -289,7 +289,7 @@ export default function WiFiQRCodePage() {
- {/* WHY WIFI QR CODES MATTER — STATISTICS */}
+ {/* WHY WIFI QR CODES MATTER - STATISTICS */}
@@ -300,14 +300,14 @@ export default function WiFiQRCodePage() {
Why WiFi QR Codes Improve Customer Experience
- A WiFi QR code eliminates the single biggest friction point between your guest and your network — manual password entry.
+ A WiFi QR code eliminates the single biggest friction point between your guest and your network - manual password entry.
#1 Amenity
- 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.
+ 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.
Source: J.D. Power Hotel Guest Satisfaction Study
@@ -317,10 +317,10 @@ export default function WiFiQRCodePage() {
Effort = Loyalty
- 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.
+ 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.
- Source: Harvard Business Review — "Stop Trying to Delight Your Customers" (Customer Effort Score research)
+ Source: Harvard Business Review - "Stop Trying to Delight Your Customers" (Customer Effort Score research)
@@ -347,7 +347,7 @@ export default function WiFiQRCodePage() {
- setShowPopup(false)} />
+ setShowPopup(false)} variant="social" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/youtube-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/youtube-qr-code/page.tsx
index 9221c12..c4bbea9 100644
--- a/src/app/(main)/(marketing)/tools/youtube-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/youtube-qr-code/page.tsx
@@ -139,7 +139,7 @@ export default function YoutubeQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
diff --git a/src/app/(main)/(marketing)/tools/zoom-qr-code/ZoomGenerator.tsx b/src/app/(main)/(marketing)/tools/zoom-qr-code/ZoomGenerator.tsx
index c9e2c2c..ea4e555 100644
--- a/src/app/(main)/(marketing)/tools/zoom-qr-code/ZoomGenerator.tsx
+++ b/src/app/(main)/(marketing)/tools/zoom-qr-code/ZoomGenerator.tsx
@@ -115,7 +115,7 @@ export default function ZoomGenerator() {
return (
<>
- setShowPopup(false)} />
+ setShowPopup(false)} variant="meeting" />
{/* Main Generator Card */}
diff --git a/src/app/(main)/(marketing)/tools/zoom-qr-code/page.tsx b/src/app/(main)/(marketing)/tools/zoom-qr-code/page.tsx
index 561edac..0c784ec 100644
--- a/src/app/(main)/(marketing)/tools/zoom-qr-code/page.tsx
+++ b/src/app/(main)/(marketing)/tools/zoom-qr-code/page.tsx
@@ -10,24 +10,24 @@ import { generateSoftwareAppSchema, generateFaqSchema } from '@/lib/schema-utils
// SEO Optimized Metadata
export const metadata: Metadata = {
title: {
- absolute: 'Free Zoom QR Code Generator | Join Meetings Instantly | QR Master',
+ absolute: 'Zoom Meeting QR Code Generator - Scan to Join, Free',
},
- description: 'Create a QR code for your Zoom meeting. Zoom QR Code erstellen. Attendees scan to join instantly. Perfect for conference rooms & invites.',
+ description: 'Turn any Zoom meeting link into a QR code. Attendees scan and join - no typing meeting IDs. Print it for conference rooms, invites and event signage.',
keywords: ['zoom qr code', 'zoom meeting qr', 'join zoom qr code', 'meeting room qr', 'zoom invitation qr', 'conference qr code', 'zoom qr code erstellen', 'zoom meeting qr code', 'video konferenz qr', 'zoom beitreten qr'],
alternates: {
canonical: 'https://www.qrmaster.net/tools/zoom-qr-code',
},
openGraph: {
- title: 'Free Zoom QR Code Generator | QR Master',
- description: 'Generate QR codes for Zoom meetings. One scan to join instantly.',
+ title: 'Zoom Meeting QR Code Generator - Scan to Join, Free',
+ description: 'Attendees scan and join. No typing meeting IDs. Print it for conference rooms and invites.',
type: 'website',
url: 'https://www.qrmaster.net/tools/zoom-qr-code',
images: [{ url: '/og-zoom-generator.png', width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
- title: 'Free Zoom QR Code Generator',
- description: 'Create Zoom meeting QR codes. Instant and free.',
+ title: 'Zoom Meeting QR Code Generator - Scan to Join, Free',
+ description: 'Attendees scan and join. No typing meeting IDs. Print it for conference rooms and invites.',
},
robots: {
index: true,
@@ -130,7 +130,7 @@ export default function ZoomQRCodePage() {
- Free Tool — No Signup Required
+ Free Tool - No Signup Required
diff --git a/src/app/(main)/(marketing)/vs/beaconstac/page.tsx b/src/app/(main)/(marketing)/vs/beaconstac/page.tsx
index 2d03a83..5b8e7b3 100644
--- a/src/app/(main)/(marketing)/vs/beaconstac/page.tsx
+++ b/src/app/(main)/(marketing)/vs/beaconstac/page.tsx
@@ -104,9 +104,9 @@ const categories: Array<{
name: 'Bulk QR Creation',
qrMaster: {
rating: 'strong',
- summary: 'Up to 1,000 codes per CSV/Excel upload. Business plan (EUR 29/month).',
+ summary: 'Per CSV/Excel upload: 1,000 static codes or 500 dynamic. Business plan (EUR 29/month).',
detail:
- 'The Business plan includes CSV and Excel bulk upload for generating up to 1,000 unique QR codes in one batch. Each code in the batch can have a different destination URL, label, campaign name, and UTM parameters. The batch downloads as a ZIP of SVG and PNG files. This is designed for product packaging, event programs, direct mail campaigns, and retail displays.',
+ 'The Business plan includes CSV and Excel bulk upload. You pick static or dynamic per upload: up to 1,000 static codes in one batch, or up to 500 dynamic ones, that cap being the Business dynamic allowance. Each code in the batch can have a different destination URL, label, campaign name, and UTM parameters. The batch downloads as a ZIP of SVG and PNG files. This is designed for product packaging, event programs, direct mail campaigns, and retail displays.',
},
beaconstac: {
rating: 'mixed',
@@ -371,7 +371,7 @@ export default function VsBeaconstacPage() {
},
{
label: 'You need bulk creation',
- body: 'for product packaging, print campaigns, or events - up to 1,000 codes per batch at EUR 29/month',
+ body: 'for product packaging, print campaigns, or events - 1,000 static or 500 dynamic per batch at EUR 29/month',
},
{
label: 'You want a simple interface',
@@ -449,7 +449,7 @@ export default function VsBeaconstacPage() {
qrMaster: 'EUR 9/month',
},
{
- useCase: 'Bulk creation (500-1,000 codes)',
+ useCase: 'Bulk creation (1,000 static or 500 dynamic)',
beaconstac: 'Enterprise tier - custom pricing',
qrMaster: 'EUR 29/month (Business plan)',
},
diff --git a/src/app/(main)/api/auth/signup/route.ts b/src/app/(main)/api/auth/signup/route.ts
index a438152..f1d8398 100644
--- a/src/app/(main)/api/auth/signup/route.ts
+++ b/src/app/(main)/api/auth/signup/route.ts
@@ -97,14 +97,14 @@ export async function POST(request: NextRequest) {
triggerLifecycleScoring(user.id, 'signup');
- // Send welcome email (fire-and-forget — never block signup)
+ // Send welcome email (fire-and-forget - never block signup)
try {
await sendWelcomeEmail(user.email, user.name ?? 'there');
} catch (emailError) {
console.error('Welcome email failed:', emailError);
}
- // Meta Conversions API — CompleteRegistration event
+ // Meta Conversions API - CompleteRegistration event
sendConversionEvent({
eventName: 'CompleteRegistration',
userData: {
diff --git a/src/app/(main)/api/cron/retention-emails/route.ts b/src/app/(main)/api/cron/retention-emails/route.ts
index aed9294..dc4f930 100644
--- a/src/app/(main)/api/cron/retention-emails/route.ts
+++ b/src/app/(main)/api/cron/retention-emails/route.ts
@@ -1,8 +1,14 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/db';
-import { sendActivationNudgeEmail, sendUpgradeNudgeEmail, sendThirtyDayNudgeEmail } from '@/lib/email';
+import {
+ sendActivationNudgeEmail,
+ sendUpgradeNudgeEmail,
+ sendThirtyDayNudgeEmail,
+ sendFirstScanEmail,
+} from '@/lib/email';
+import { DYNAMIC_QR_LIMITS } from '@/lib/plans';
-// Protect with a shared secret — set CRON_SECRET in Vercel env vars
+// 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;
@@ -17,14 +23,17 @@ export async function GET(request: NextRequest) {
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);
+ const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
let activationSent = 0;
- let upgradeSent = 0;
+ let limitSent = 0;
+ let firstScanSent = 0;
let thirtyDaySent = 0;
- // Day-3: signed up > 3 days ago, never created a QR code, hasn't received this email yet
+ // ── Day 3: signed up, never created anything ─────────────────────────────
+ // Unchanged. This one is legitimately time-based: the absence of activity is
+ // the trigger, and absence only becomes meaningful after some time passes.
const activationCandidates = await db.user.findMany({
where: {
createdAt: { lt: threeDaysAgo },
@@ -50,34 +59,83 @@ export async function GET(request: NextRequest) {
}
}
- // 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({
+ // ── Limit reached: behaviour, not calendar ───────────────────────────────
+ // This replaces the old day-7 upgrade nudge, which fired at day 7 regardless
+ // of usage. Someone with a single code was getting a mail about a ceiling
+ // they had not come near - an upgrade pitch to a person with no pain, sent
+ // under the founder's own name. Now it only goes to people actually blocked.
+ const freeUsers = await db.user.findMany({
where: {
- createdAt: { lt: sevenDaysAgo },
- upgradeNudgeSentAt: null,
plan: 'FREE',
+ limitReachedNudgeSentAt: null,
},
- include: {
- _count: { select: { qrCodes: true } },
- },
+ select: { id: true, email: true, name: 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);
- }
+ for (const user of freeUsers) {
+ if (!user.email) continue;
+ const activeDynamic = await db.qRCode.count({
+ where: { userId: user.id, type: 'DYNAMIC', status: 'ACTIVE' },
+ });
+
+ if (activeDynamic < DYNAMIC_QR_LIMITS.FREE) continue;
+
+ try {
+ await sendUpgradeNudgeEmail(user.email, user.name ?? 'there', activeDynamic);
+ await db.user.update({
+ where: { id: user.id },
+ data: { limitReachedNudgeSentAt: now },
+ });
+ limitSent++;
+ } catch (err) {
+ console.error(`Limit 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
+ // ── First scan: the only trigger that is not a date ──────────────────────
+ // Fires the day after the first scan, so the event is still recent enough to
+ // be an occasion rather than a fact from the archive. The 7-day floor stops
+ // this from firing for historic users whose first scan was months ago.
+ const firstScanCandidates = await db.user.findMany({
+ where: {
+ firstScanAt: { not: null, lte: oneDayAgo },
+ firstScanNudgeSentAt: null,
+ },
+ select: { id: true, email: true, name: true, firstScanAt: true },
+ });
+
+ for (const user of firstScanCandidates) {
+ if (!user.email || !user.firstScanAt) continue;
+
+ const scan = await db.qRScan.findFirst({
+ where: { qr: { userId: user.id } },
+ orderBy: { ts: 'asc' },
+ select: { ts: true, device: true, country: true, qr: { select: { title: true } } },
+ });
+
+ if (!scan) continue;
+
+ try {
+ await sendFirstScanEmail(user.email, user.name ?? 'there', {
+ qrTitle: scan.qr?.title ?? 'your QR code',
+ device: scan.device,
+ country: scan.country,
+ ts: scan.ts,
+ });
+ await db.user.update({
+ where: { id: user.id },
+ data: { firstScanNudgeSentAt: now },
+ });
+ firstScanSent++;
+ } catch (err) {
+ console.error(`First scan mail failed for ${user.email}:`, err);
+ }
+ }
+
+ // ── Day 30: built around the user's own numbers ─────────────────────────
+ // The old version argued from branding and cited a pattern among Pro users
+ // that was never sourced. This one argues from the scan count the user
+ // actually produced, which needs no testimonial to be believable.
const thirtyDayCandidates = await db.user.findMany({
where: {
createdAt: { lt: thirtyDaysAgo },
@@ -90,24 +148,41 @@ export async function GET(request: NextRequest) {
});
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);
- }
+ if (user._count.qrCodes === 0 || !user.email) continue;
+
+ const scanCount = await db.qRScan.count({
+ where: {
+ qr: { userId: user.id },
+ ts: { gte: thirtyDaysAgo },
+ },
+ });
+
+ // No scans means the pitch has no evidence behind it. Staying quiet is
+ // better than sending "your codes were scanned 0 times this month".
+ if (scanCount === 0) continue;
+
+ try {
+ await sendThirtyDayNudgeEmail(
+ user.email,
+ user.name ?? 'there',
+ user._count.qrCodes,
+ scanCount
+ );
+ 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,
+ limitNudgesSent: limitSent,
+ firstScanEmailsSent: firstScanSent,
thirtyDayNudgesSent: thirtyDaySent,
});
}
diff --git a/src/app/(main)/api/design-presets/route.ts b/src/app/(main)/api/design-presets/route.ts
new file mode 100644
index 0000000..2c99408
--- /dev/null
+++ b/src/app/(main)/api/design-presets/route.ts
@@ -0,0 +1,129 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { getSessionUserId } from '@/lib/session';
+import { db } from '@/lib/db';
+import { csrfProtection } from '@/lib/csrf';
+import { z } from 'zod';
+
+/**
+ * Saved QR design presets.
+ *
+ * Business-only. The value here is repeatability, not novelty: an agency running
+ * several clients needs client A to look identical across every code, including
+ * the 500 that came out of one spreadsheet upload.
+ */
+
+const MAX_PRESETS = 50;
+
+const presetSchema = z.object({
+ name: z.string().min(1, 'Name is required').max(60),
+ style: z.record(z.any()),
+});
+
+function isAllowed(plan: string | undefined): boolean {
+ return plan === 'BUSINESS' || plan === 'ENTERPRISE';
+}
+
+export async function GET() {
+ const userId = getSessionUserId();
+ if (!userId) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const presets = await db.qRDesignPreset.findMany({
+ where: { userId },
+ orderBy: { updatedAt: 'desc' },
+ });
+
+ return NextResponse.json(presets);
+}
+
+export async function POST(request: NextRequest) {
+ const csrfCheck = csrfProtection(request);
+ if (!csrfCheck.valid) {
+ return NextResponse.json({ error: csrfCheck.error }, { status: 403 });
+ }
+
+ const userId = getSessionUserId();
+ if (!userId) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const user = await db.user.findUnique({
+ where: { id: userId },
+ select: { plan: true },
+ });
+
+ if (!isAllowed(user?.plan)) {
+ return NextResponse.json(
+ {
+ error: 'Upgrade required',
+ message: 'Saved design presets are part of the Business plan.',
+ plan: user?.plan ?? 'FREE',
+ },
+ { status: 403 }
+ );
+ }
+
+ let data;
+ try {
+ data = presetSchema.parse(await request.json());
+ } catch (err) {
+ if (err instanceof z.ZodError) {
+ return NextResponse.json(
+ { error: 'Invalid input', details: err.errors },
+ { status: 400 }
+ );
+ }
+ return NextResponse.json({ error: 'Invalid input' }, { status: 400 });
+ }
+
+ const count = await db.qRDesignPreset.count({ where: { userId } });
+ const existing = await db.qRDesignPreset.findFirst({
+ where: { userId, name: data.name },
+ select: { id: true },
+ });
+
+ if (!existing && count >= MAX_PRESETS) {
+ return NextResponse.json(
+ {
+ error: 'Preset limit reached',
+ message: `You can keep up to ${MAX_PRESETS} presets. Delete one to save another.`,
+ },
+ { status: 403 }
+ );
+ }
+
+ // Same name overwrites rather than creating a near-duplicate. Someone saving
+ // "Client A" twice means "update it", not "keep both".
+ const preset = await db.qRDesignPreset.upsert({
+ where: { userId_name: { userId, name: data.name } },
+ create: { userId, name: data.name, style: data.style },
+ update: { style: data.style },
+ });
+
+ return NextResponse.json(preset, { status: existing ? 200 : 201 });
+}
+
+export async function DELETE(request: NextRequest) {
+ const csrfCheck = csrfProtection(request);
+ if (!csrfCheck.valid) {
+ return NextResponse.json({ error: csrfCheck.error }, { status: 403 });
+ }
+
+ const userId = getSessionUserId();
+ if (!userId) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const id = new URL(request.url).searchParams.get('id');
+ if (!id) {
+ return NextResponse.json({ error: 'Missing id' }, { status: 400 });
+ }
+
+ const deleted = await db.qRDesignPreset.deleteMany({ where: { id, userId } });
+ if (deleted.count === 0) {
+ return NextResponse.json({ error: 'Preset not found' }, { status: 404 });
+ }
+
+ return NextResponse.json({ ok: true });
+}
diff --git a/src/app/(main)/api/qrs/[id]/route.ts b/src/app/(main)/api/qrs/[id]/route.ts
index 985cbc2..a5bef49 100644
--- a/src/app/(main)/api/qrs/[id]/route.ts
+++ b/src/app/(main)/api/qrs/[id]/route.ts
@@ -4,6 +4,7 @@ import { db } from '@/lib/db';
import { z } from 'zod';
import { csrfProtection } from '@/lib/csrf';
import { rateLimit, getClientIdentifier, RateLimits } from '@/lib/rateLimit';
+import { DYNAMIC_QR_LIMITS } from '@/lib/plans';
const updateQRSchema = z.object({
title: z.string().min(1).optional(),
@@ -103,6 +104,39 @@ export async function PATCH(
return NextResponse.json({ error: 'QR code not found' }, { status: 404 });
}
+ // Reactivating a paused code consumes a slot again. Without this check,
+ // pause -> create a new one -> unpause would quietly put the user over
+ // their plan limit.
+ if (
+ data.status === 'ACTIVE' &&
+ existing.status === 'PAUSED' &&
+ existing.type === 'DYNAMIC'
+ ) {
+ const user = await db.user.findUnique({
+ where: { id: userId },
+ select: { plan: true },
+ });
+ const limit =
+ DYNAMIC_QR_LIMITS[(user?.plan ?? 'FREE') as keyof typeof DYNAMIC_QR_LIMITS] ??
+ DYNAMIC_QR_LIMITS.FREE;
+ const activeCount = await db.qRCode.count({
+ where: { userId, type: 'DYNAMIC', status: 'ACTIVE' },
+ });
+
+ if (activeCount >= limit) {
+ return NextResponse.json(
+ {
+ error: 'Limit reached',
+ message: `You have ${activeCount} of ${limit} dynamic QR codes active. Pause another one first, or upgrade to reactivate this code.`,
+ currentCount: activeCount,
+ limit,
+ plan: user?.plan ?? 'FREE',
+ },
+ { status: 403 }
+ );
+ }
+ }
+
// Static QR codes cannot be edited
if (existing.type === 'STATIC' && data.content) {
return NextResponse.json(
@@ -119,6 +153,7 @@ export async function PATCH(
...(data.content && { content: data.content }),
...(data.tags && { tags: data.tags }),
...(data.style && { style: data.style }),
+ ...(data.status && { status: data.status }),
},
});
diff --git a/src/app/(main)/api/qrs/route.ts b/src/app/(main)/api/qrs/route.ts
index 86b5e24..c709cb4 100644
--- a/src/app/(main)/api/qrs/route.ts
+++ b/src/app/(main)/api/qrs/route.ts
@@ -16,6 +16,8 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
+ const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
+
const qrCodes = await db.qRCode.findMany({
where: { userId },
include: {
@@ -30,11 +32,26 @@ export async function GET(request: NextRequest) {
orderBy: { createdAt: 'desc' },
});
+ // Recent activity per code. Used by the upgrade modal so someone deciding
+ // which code to pause can see which one is actually dead, rather than
+ // guessing from a lifetime total that says nothing about right now.
+ const recentScans = await db.qRScan.groupBy({
+ by: ['qrId'],
+ where: {
+ ts: { gte: thirtyDaysAgo },
+ qr: { userId },
+ },
+ _count: { _all: true },
+ });
+
+ const recentByQr = new Map(recentScans.map(r => [r.qrId, r._count._all]));
+
// Transform the data
const transformed = qrCodes.map(qr => ({
...qr,
scans: qr._count.scans,
uniqueScans: qr.scans.length, // Count of scans where isUnique=true
+ scans30d: recentByQr.get(qr.id) ?? 0,
_count: undefined,
}));
@@ -113,10 +130,13 @@ export async function POST(request: NextRequest) {
// Only check limits for DYNAMIC QR codes (static QR codes are unlimited)
if (!isStatic) {
// Count existing dynamic QR codes
+ // Only ACTIVE codes consume a slot. Pausing a code frees one, which is what
+ // the pricing page has always promised ("3 active dynamic QR codes").
const dynamicQRCount = await db.qRCode.count({
where: {
userId,
type: 'DYNAMIC',
+ status: 'ACTIVE',
},
});
diff --git a/src/app/(main)/api/stripe/create-checkout-session/route.ts b/src/app/(main)/api/stripe/create-checkout-session/route.ts
index be79a83..996019a 100644
--- a/src/app/(main)/api/stripe/create-checkout-session/route.ts
+++ b/src/app/(main)/api/stripe/create-checkout-session/route.ts
@@ -35,7 +35,16 @@ export async function POST(request: NextRequest) {
}
// Get plan and billing interval from request
- const { plan, billingInterval = 'month' } = await request.json();
+ const { plan, billingInterval = 'month', returnPath } = await request.json();
+
+ // Where to send the user after checkout. Used by the in-app upgrade modal so
+ // people land back on the thing they were building instead of the dashboard.
+ const safeReturnPath =
+ typeof returnPath === 'string' &&
+ returnPath.startsWith('/') &&
+ !returnPath.startsWith('//')
+ ? returnPath
+ : null;
if (!plan || !['PRO', 'BUSINESS'].includes(plan)) {
return NextResponse.json(
@@ -114,8 +123,12 @@ export async function POST(request: NextRequest) {
quantity: 1,
},
],
- success_url: `${appUrl}/dashboard?success=true&session_id={CHECKOUT_SESSION_ID}`,
- cancel_url: `${appUrl}/pricing?canceled=true`,
+ success_url: safeReturnPath
+ ? `${appUrl}${safeReturnPath}${safeReturnPath.includes('?') ? '&' : '?'}success=true&session_id={CHECKOUT_SESSION_ID}`
+ : `${appUrl}/dashboard?success=true&session_id={CHECKOUT_SESSION_ID}`,
+ cancel_url: safeReturnPath
+ ? `${appUrl}${safeReturnPath}${safeReturnPath.includes('?') ? '&' : '?'}canceled=true`
+ : `${appUrl}/pricing?canceled=true`,
metadata: {
userId: user.id,
plan,
diff --git a/src/app/(main)/api/stripe/webhook/route.ts b/src/app/(main)/api/stripe/webhook/route.ts
index ae6883c..e001493 100644
--- a/src/app/(main)/api/stripe/webhook/route.ts
+++ b/src/app/(main)/api/stripe/webhook/route.ts
@@ -1,10 +1,10 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { headers } from 'next/headers';
-import { getPlanFromStripePriceId, stripe } from '@/lib/stripe';
-import { db } from '@/lib/db';
-import Stripe from 'stripe';
-import { sendConversionEvent } from '@/lib/metaConversions';
-import { scoreUserLifecycle } from '@/lib/revops-server';
+import { NextRequest, NextResponse } from 'next/server';
+import { headers } from 'next/headers';
+import { getPlanFromStripePriceId, stripe } from '@/lib/stripe';
+import { db } from '@/lib/db';
+import Stripe from 'stripe';
+import { sendConversionEvent } from '@/lib/metaConversions';
+import { scoreUserLifecycle } from '@/lib/revops-server';
export async function POST(request: NextRequest) {
const body = await request.text();
@@ -51,21 +51,21 @@ export async function POST(request: NextRequest) {
? new Date(periodEndTimestamp * 1000)
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
- const updatedUser = await db.user.update({
- where: {
- stripeCustomerId: session.customer as string,
+ const updatedUser = await db.user.update({
+ where: {
+ stripeCustomerId: session.customer as string,
},
data: {
stripeSubscriptionId: subscription.id,
stripePriceId: subscription.items.data[0].price.id,
stripeCurrentPeriodEnd: currentPeriodEnd,
plan: (session.metadata?.plan || 'FREE') as any,
- },
- });
-
- await scoreUserLifecycle(updatedUser.id, 'subscription_created');
+ },
+ });
- // Meta CAPI — Purchase event
+ await scoreUserLifecycle(updatedUser.id, 'subscription_created');
+
+ // Meta CAPI - Purchase event
const amountCents = session.amount_total ?? 0;
sendConversionEvent({
eventName: 'Purchase',
@@ -95,47 +95,47 @@ export async function POST(request: NextRequest) {
? new Date(periodEndTimestamp * 1000)
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
- await db.user.update({
- where: {
- stripeSubscriptionId: subscription.id,
- },
- data: {
- stripePriceId: subscription.items.data[0].price.id,
- stripeCurrentPeriodEnd: currentPeriodEnd,
- plan: getPlanFromStripePriceId(subscription.items.data[0].price.id) ?? undefined,
- },
- });
- const updated = await db.user.findUnique({
- where: { stripeSubscriptionId: subscription.id },
- select: { id: true },
- });
- if (updated?.id) {
- await scoreUserLifecycle(
- updated.id,
- subscription.cancel_at_period_end ? 'subscription_canceled_at_period_end' : 'subscription_updated'
- );
- }
- break;
- }
+ await db.user.update({
+ where: {
+ stripeSubscriptionId: subscription.id,
+ },
+ data: {
+ stripePriceId: subscription.items.data[0].price.id,
+ stripeCurrentPeriodEnd: currentPeriodEnd,
+ plan: getPlanFromStripePriceId(subscription.items.data[0].price.id) ?? undefined,
+ },
+ });
+ const updated = await db.user.findUnique({
+ where: { stripeSubscriptionId: subscription.id },
+ select: { id: true },
+ });
+ if (updated?.id) {
+ await scoreUserLifecycle(
+ updated.id,
+ subscription.cancel_at_period_end ? 'subscription_canceled_at_period_end' : 'subscription_updated'
+ );
+ }
+ break;
+ }
case 'customer.subscription.deleted': {
const subscription = event.data.object as Stripe.Subscription;
- const updatedUser = await db.user.update({
- where: {
- stripeSubscriptionId: subscription.id,
- },
+ const updatedUser = await db.user.update({
+ where: {
+ stripeSubscriptionId: subscription.id,
+ },
data: {
stripeSubscriptionId: null,
stripePriceId: null,
stripeCurrentPeriodEnd: null,
- plan: 'FREE',
- },
- });
-
- await scoreUserLifecycle(updatedUser.id, 'subscription_deleted');
- break;
- }
+ plan: 'FREE',
+ },
+ });
+
+ await scoreUserLifecycle(updatedUser.id, 'subscription_deleted');
+ break;
+ }
}
return NextResponse.json({ received: true });
diff --git a/src/app/(main)/api/tiktok/analytics/route.ts b/src/app/(main)/api/tiktok/analytics/route.ts
index f4d5bd1..490b0d7 100644
--- a/src/app/(main)/api/tiktok/analytics/route.ts
+++ b/src/app/(main)/api/tiktok/analytics/route.ts
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
import { tiktokApi } from '@/lib/tiktok';
// Live read-only stats from the Display API. Requires the user.info.stats and
-// video.list scopes — accounts connected before the scope change must
+// video.list scopes - accounts connected before the scope change must
// re-authorize via /api/tiktok/connect.
const USER_FIELDS = 'display_name,follower_count,following_count,likes_count,video_count';
diff --git a/src/app/(main)/api/tiktok/token/route.ts b/src/app/(main)/api/tiktok/token/route.ts
index 18f6a90..008021c 100644
--- a/src/app/(main)/api/tiktok/token/route.ts
+++ b/src/app/(main)/api/tiktok/token/route.ts
@@ -5,7 +5,7 @@ import { TIKTOK_BRAND, getValidTiktokTokens } from '@/lib/tiktok';
export async function GET(request: NextRequest) {
const adminKey = process.env.TIKTOK_ADMIN_KEY;
if (!adminKey) {
- // Unlike /connect, this endpoint hands out live credentials — never expose
+ // Unlike /connect, this endpoint hands out live credentials - never expose
// it without a configured key.
return NextResponse.json(
{ error: 'TIKTOK_ADMIN_KEY must be configured to expose TikTok status' },
diff --git a/src/app/(main)/api/tiktok/upload/route.ts b/src/app/(main)/api/tiktok/upload/route.ts
index af52eb7..2c53369 100644
--- a/src/app/(main)/api/tiktok/upload/route.ts
+++ b/src/app/(main)/api/tiktok/upload/route.ts
@@ -135,7 +135,7 @@ export async function POST(request: NextRequest) {
media_type: 'PHOTO',
// MEDIA_UPLOAD = draft in the creator's TikTok inbox (posting
// policy is upload/draft only) and only needs the video.upload
- // scope — QRMaster has no video.publish.
+ // scope - QRMaster has no video.publish.
post_mode: 'MEDIA_UPLOAD',
post_info: {
...(title ? { title } : {}),
diff --git a/src/app/(main)/api/user/stats/route.ts b/src/app/(main)/api/user/stats/route.ts
index 3cd0bce..3071fd1 100644
--- a/src/app/(main)/api/user/stats/route.ts
+++ b/src/app/(main)/api/user/stats/route.ts
@@ -23,11 +23,14 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
}
- // Count dynamic QR codes
+ // Count dynamic QR codes. Must match the limit check in /api/qrs - only
+ // ACTIVE codes consume a slot, otherwise the dashboard shows a different
+ // number than the API actually enforces.
const dynamicQRCount = await db.qRCode.count({
where: {
userId,
type: 'DYNAMIC',
+ status: 'ACTIVE',
},
});
diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx
index 7b0857d..e8e3c6e 100644
--- a/src/app/(main)/layout.tsx
+++ b/src/app/(main)/layout.tsx
@@ -10,7 +10,7 @@ const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true';
export const metadata: Metadata = {
metadataBase: new URL('https://www.qrmaster.net'),
title: {
- default: 'QR Master – Smart QR Generator & Analytics',
+ default: 'QR Master - Smart QR Generator & Analytics',
template: '%s | QR Master',
},
description: 'Create dynamic QR codes, track scans, and scale campaigns with secure analytics. Free advanced features, bulk generation, and custom branding available.',
@@ -18,14 +18,14 @@ export const metadata: Metadata = {
robots: isIndexable
? { index: true, follow: true }
: { index: false, follow: false },
- icons: {
- icon: [
- { url: '/favicon.svg', type: 'image/svg+xml' },
- { url: '/favicon.ico', sizes: '16x16 32x32', type: 'image/x-icon' },
- ],
- shortcut: '/favicon.ico',
- apple: '/logo.svg',
- },
+ icons: {
+ icon: [
+ { url: '/favicon.svg', type: 'image/svg+xml' },
+ { url: '/favicon.ico', sizes: '16x16 32x32', type: 'image/x-icon' },
+ ],
+ shortcut: '/favicon.ico',
+ apple: '/logo.svg',
+ },
twitter: {
card: 'summary_large_image',
site: '@qrmaster',
@@ -34,7 +34,7 @@ export const metadata: Metadata = {
openGraph: {
type: 'website',
siteName: 'QR Master',
- title: 'QR Master – Smart QR Generator & Analytics',
+ title: 'QR Master - Smart QR Generator & Analytics',
description: 'Create dynamic QR codes, track scans, and scale campaigns with secure analytics. Free advanced features, bulk generation, and custom branding available.',
url: 'https://www.qrmaster.net',
images: [
diff --git a/src/app/(main)/og/route.tsx b/src/app/(main)/og/route.tsx
index d230c09..fc03142 100644
--- a/src/app/(main)/og/route.tsx
+++ b/src/app/(main)/og/route.tsx
@@ -5,7 +5,7 @@ export const runtime = 'edge';
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url);
- const title = searchParams.get('title') || 'QR Master – Smart QR Generator & Analytics';
+ const title = searchParams.get('title') || 'QR Master - Smart QR Generator & Analytics';
return new ImageResponse(
(
diff --git a/src/app/(main)/vcard/layout.tsx b/src/app/(main)/vcard/layout.tsx
index 2cc43f2..8f3d300 100644
--- a/src/app/(main)/vcard/layout.tsx
+++ b/src/app/(main)/vcard/layout.tsx
@@ -4,13 +4,14 @@ import '@/styles/globals.css';
export const metadata = {
title: 'vCard Download',
description: 'Download contact information',
- icons: {
- icon: [
- { url: '/favicon1.png', sizes: '512x512', type: 'image/png' },
- ],
- shortcut: '/favicon1.png',
- apple: '/favicon1.png',
- },
+ icons: {
+ icon: [
+ { url: '/favicon.svg', type: 'image/svg+xml' },
+ { url: '/favicon.ico', sizes: '16x16 32x32', type: 'image/x-icon' },
+ ],
+ shortcut: '/favicon.ico',
+ apple: '/logo.svg',
+ },
};
export default function VCardLayout({
diff --git a/src/app/(marketing-de)/layout.tsx b/src/app/(marketing-de)/layout.tsx
index d3e412e..78d1356 100644
--- a/src/app/(marketing-de)/layout.tsx
+++ b/src/app/(marketing-de)/layout.tsx
@@ -8,7 +8,7 @@ import FacebookPixel from '@/components/analytics/FacebookPixel';
export const metadata: Metadata = {
title: {
- default: 'QR Master – QR Code Generator & Analytics',
+ default: 'QR Master - QR Code Generator & Analytics',
template: '%s | QR Master',
},
description: 'Erstellen Sie dynamische QR Codes für Feedback, PDF, Coupons und App Stores. Verfolgen Sie Scans und skalieren Sie Kampagnen mit sicheren Analysen.',
@@ -24,7 +24,7 @@ export const metadata: Metadata = {
openGraph: {
type: 'website',
siteName: 'QR Master',
- title: 'QR Master – QR Code Generator & Analytics',
+ title: 'QR Master - QR Code Generator & Analytics',
description: 'Erstellen Sie dynamische QR Codes für Feedback, PDF, Coupons und App Stores. Verfolgen Sie Scans und skalieren Sie Kampagnen mit sicheren Analysen.',
url: 'https://www.qrmaster.net/qr-code-erstellen',
locale: 'de_DE',
diff --git a/src/app/(marketing-de)/qr-code-erstellen/page.tsx b/src/app/(marketing-de)/qr-code-erstellen/page.tsx
index cde974a..bf7c413 100644
--- a/src/app/(marketing-de)/qr-code-erstellen/page.tsx
+++ b/src/app/(marketing-de)/qr-code-erstellen/page.tsx
@@ -24,7 +24,7 @@ function truncateAtWord(text: string, maxLength: number): string {
}
export async function generateMetadata(): Promise {
- const title = 'QR Code Erstellen – Kostenlos | QR Master';
+ 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.';
@@ -64,7 +64,7 @@ export async function generateMetadata(): Promise {
},
},
openGraph: {
- title: 'QR Code Erstellen – Kostenlos & Sofort | QR Master',
+ 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',
@@ -80,7 +80,7 @@ export async function generateMetadata(): Promise {
],
},
twitter: {
- title: 'QR Code Erstellen – Kostenlos | QR Master',
+ title: 'QR Code Erstellen - Kostenlos | QR Master',
description:
'QR Codes erstellen in Sekunden. Kostenlos, mit Tracking und individuellem Branding.',
},
@@ -274,7 +274,7 @@ export default function QRCodeErstellenPage() {
Beliebte Anwendungsfälle
- Ausführliche Anleitungen für Ihren Einsatzzweck – mit Checklisten und
+ Ausführliche Anleitungen für Ihren Einsatzzweck - mit Checklisten und
praxisnahen FAQs.
diff --git a/src/app/de/layout.tsx b/src/app/de/layout.tsx
index d33bf3e..1abf487 100644
--- a/src/app/de/layout.tsx
+++ b/src/app/de/layout.tsx
@@ -8,7 +8,7 @@ import FacebookPixel from '@/components/analytics/FacebookPixel';
export const metadata: Metadata = {
title: {
- default: 'QR Master – QR Code Generator & Analytics',
+ default: 'QR Master - QR Code Generator & Analytics',
template: '%s | QR Master',
},
description: 'Erstellen Sie dynamische QR Codes für Feedback, PDF, Coupons und App Stores. Verfolgen Sie Scans und skalieren Sie Kampagnen mit sicheren Analysen.',
@@ -24,7 +24,7 @@ export const metadata: Metadata = {
openGraph: {
type: 'website',
siteName: 'QR Master',
- title: 'QR Master – QR Code Generator & Analytics',
+ title: 'QR Master - QR Code Generator & Analytics',
description: 'Erstellen Sie dynamische QR Codes für Feedback, PDF, Coupons und App Stores. Verfolgen Sie Scans und skalieren Sie Kampagnen mit sicheren Analysen.',
locale: 'de_DE',
images: [
diff --git a/src/components/app/UpgradeModal.tsx b/src/components/app/UpgradeModal.tsx
new file mode 100644
index 0000000..f4b3410
--- /dev/null
+++ b/src/components/app/UpgradeModal.tsx
@@ -0,0 +1,289 @@
+'use client';
+
+import React, { useEffect, useRef, useState } from 'react';
+import { X, Loader2, Pause, Download, ArrowRight } from 'lucide-react';
+import { Button } from '@/components/ui/Button';
+
+export type UpgradeReason = 'limit' | 'logo' | 'shapes';
+
+export interface ActiveCodeSummary {
+ id: string;
+ title: string;
+ scans30d: number;
+}
+
+interface UpgradeModalProps {
+ open: boolean;
+ reason: UpgradeReason;
+ /** How many dynamic codes the user already has. Only used for reason="limit". */
+ currentCount?: number;
+ /** The plan limit that was hit. Only used for reason="limit". */
+ limit?: number;
+ /** Active dynamic codes, so the user can free a slot instead of paying. */
+ activeCodes?: ActiveCodeSummary[];
+ /** Called when the user pauses a code to free a slot. */
+ onPauseCode?: (id: string) => Promise
;
+ /** Called when the user chooses to download a static code instead. */
+ onDownloadStatic?: () => void;
+ onClose: () => void;
+ /** Path Stripe returns to after checkout. Defaults to the current URL. */
+ returnPath?: string;
+}
+
+function ordinal(n: number): string {
+ const s = ['th', 'st', 'nd', 'rd'];
+ const v = n % 100;
+ return n + (s[(v - 20) % 10] || s[v] || s[0]);
+}
+
+/**
+ * Copy notes, so nobody softens this back into a generic paywall:
+ *
+ * The headline names the thing the user just built, not the plan they lack.
+ * They are buying a specific code that already exists, not an abstraction.
+ *
+ * The two secondary options are deliberate. "Pause a code" keeps them inside
+ * the free plan, and "download as static" hands them a working result for
+ * nothing. Both cost conversions. Both are what makes the closing line
+ * believable instead of decorative - this product does not hold printed codes
+ * hostage, and this is the one screen where that has to be demonstrated
+ * rather than claimed.
+ */
+function getCopy(reason: UpgradeReason, currentCount?: number, limit?: number) {
+ if (reason === 'logo') {
+ return {
+ headline: 'Your logo belongs inside this code.',
+ body: 'A QR code with your mark in the middle gets scanned more often than an anonymous black square, because people can see who it belongs to before they trust it.',
+ mechanism:
+ 'Pro puts your logo in the center of every code you make, at the error-correction level that keeps it scannable.',
+ cta: 'Add my logo - €9 / month',
+ reassurance: 'Your current codes keep working exactly as they are.',
+ };
+ }
+
+ if (reason === 'shapes') {
+ return {
+ headline: 'This shape needs Pro.',
+ body: 'Rounded modules, dots and flowing styles make a code look designed instead of generated. On a menu or a flyer, that is the difference between something people scan and something they ignore.',
+ mechanism:
+ 'Pro unlocks four module shapes and your own eye styles. Colors stay free on every plan.',
+ cta: 'Unlock shapes - €9 / month',
+ reassurance: 'Your current codes keep working exactly as they are.',
+ };
+ }
+
+ const next = (currentCount ?? 3) + 1;
+ const cap = limit ?? 3;
+
+ return {
+ headline: `Your ${ordinal(next)} code is finished. It just needs a slot.`,
+ body: `You are using all ${cap} dynamic codes on your plan. This one is built and waiting - you can keep it, or free a slot from the codes you already have.`,
+ mechanism: 'Pro raises the ceiling to 50 dynamic codes and adds device and location data for every scan.',
+ cta: 'Save this code with Pro - €9 / month',
+ reassurance: `Your ${cap} active codes keep running, whichever way you decide.`,
+ };
+}
+
+export default function UpgradeModal({
+ open,
+ reason,
+ currentCount,
+ limit,
+ activeCodes = [],
+ onPauseCode,
+ onDownloadStatic,
+ onClose,
+ returnPath,
+}: UpgradeModalProps) {
+ const overlayRef = useRef(null);
+ const [checkoutLoading, setCheckoutLoading] = useState(false);
+ const [showCodeList, setShowCodeList] = useState(false);
+ const [pausingId, setPausingId] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ if (!open) return;
+ const onKey = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') onClose();
+ };
+ document.addEventListener('keydown', onKey);
+ return () => document.removeEventListener('keydown', onKey);
+ }, [open, onClose]);
+
+ useEffect(() => {
+ if (!open) {
+ setShowCodeList(false);
+ setError(null);
+ }
+ }, [open]);
+
+ if (!open) return null;
+
+ const copy = getCopy(reason, currentCount, limit);
+
+ const handleCheckout = async () => {
+ setCheckoutLoading(true);
+ setError(null);
+ try {
+ const path =
+ returnPath ??
+ (typeof window !== 'undefined'
+ ? window.location.pathname + window.location.search
+ : '/create');
+
+ const res = await fetch('/api/stripe/create-checkout-session', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ plan: 'PRO',
+ billingInterval: 'month',
+ returnPath: path,
+ }),
+ });
+
+ if (!res.ok) {
+ const data = await res.json().catch(() => null);
+ throw new Error(data?.error || 'Could not start checkout.');
+ }
+
+ const { url } = await res.json();
+ window.location.href = url;
+ } catch (err: any) {
+ setError(err?.message || 'Could not start checkout. Please try again.');
+ setCheckoutLoading(false);
+ }
+ };
+
+ const handlePause = async (id: string) => {
+ if (!onPauseCode) return;
+ setPausingId(id);
+ setError(null);
+ try {
+ await onPauseCode(id);
+ } catch (err: any) {
+ setError(err?.message || 'Could not pause that code.');
+ setPausingId(null);
+ }
+ };
+
+ return (
+ {
+ if (e.target === overlayRef.current) onClose();
+ }}
+ role="dialog"
+ aria-modal="true"
+ aria-labelledby="upgrade-modal-title"
+ >
+
+
+
+
+
+
+
+ {copy.headline}
+
+
{copy.body}
+
{copy.mechanism}
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+ {checkoutLoading ? (
+
+ Opening checkout...
+
+ ) : (
+
+ {copy.cta}
+
+ )}
+
+
+ {reason === 'limit' && onPauseCode && activeCodes.length > 0 && !showCodeList && (
+
setShowCodeList(true)}
+ className="flex w-full items-center justify-center gap-2 rounded-lg border border-slate-200 py-3 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50"
+ >
+ Pause one of my existing codes
+
+ )}
+
+ {reason === 'limit' && showCodeList && (
+
+
+ Pausing keeps the code and its scan history. It stops resolving until you
+ switch it back on.
+
+
+ {activeCodes.map((code) => (
+
+
+
{code.title}
+
+ {code.scans30d === 0
+ ? 'No scans in the last 30 days'
+ : `${code.scans30d} scan${code.scans30d === 1 ? '' : 's'} in the last 30 days`}
+
+
+ handlePause(code.id)}
+ >
+ {pausingId === code.id ? (
+
+ ) : (
+ 'Pause'
+ )}
+
+
+ ))}
+
+
+ )}
+
+ {reason === 'limit' && onDownloadStatic && (
+
+ Download it as a static code instead
+
+ )}
+
+
{copy.reassurance}
+
+
+ Not now
+
+
+
+
+ );
+}
diff --git a/src/components/generator/StyledQRCode.tsx b/src/components/generator/StyledQRCode.tsx
new file mode 100644
index 0000000..c0d2558
--- /dev/null
+++ b/src/components/generator/StyledQRCode.tsx
@@ -0,0 +1,220 @@
+'use client';
+
+import React, { useMemo } from 'react';
+import QRCodeLib from 'qrcode';
+import {
+ ModuleShape,
+ EyeFrameShape,
+ EyeBallShape,
+ moduleShapePath,
+ eyeFramePath,
+ eyeBallPath,
+ isInEye,
+ LOW_COVERAGE_SHAPES,
+} from '@/lib/qr-shapes';
+
+export type ErrorCorrectionLevel = 'L' | 'M' | 'Q' | 'H';
+
+export interface QRGradient {
+ type: 'linear' | 'radial';
+ from: string;
+ to: string;
+ /** Degrees, linear only. */
+ rotation?: number;
+}
+
+export interface StyledQRCodeProps {
+ value: string;
+ size?: number;
+ fgColor?: string;
+ bgColor?: string;
+ moduleShape?: ModuleShape;
+ eyeFrameShape?: EyeFrameShape;
+ eyeBallShape?: EyeBallShape;
+ /** Overrides fgColor for the module fill when present. */
+ gradient?: QRGradient | null;
+ eyeColor?: string | null;
+ errorCorrection?: ErrorCorrectionLevel;
+ /** Data URL. Rendered centred, with the modules behind it cleared. */
+ logoUrl?: string;
+ /** Logo edge length as a share of the whole code. Capped at 0.28. */
+ logoScale?: number;
+ /** Quiet zone in modules. Four is the spec minimum. */
+ margin?: number;
+ className?: string;
+ id?: string;
+}
+
+/**
+ * Effective error correction.
+ *
+ * Shapes that fill less of each module, and any logo, both eat into the
+ * redundancy the scanner relies on. Rather than letting someone print a code
+ * that fails in the wild, we quietly raise the level - and the UI says so, so
+ * this is not a hidden override.
+ */
+export function resolveErrorCorrection(
+ requested: ErrorCorrectionLevel,
+ moduleShape: ModuleShape,
+ hasLogo: boolean
+): ErrorCorrectionLevel {
+ if (hasLogo) return 'H';
+ if (LOW_COVERAGE_SHAPES.includes(moduleShape)) {
+ return requested === 'L' || requested === 'M' ? 'Q' : requested;
+ }
+ return requested;
+}
+
+export default function StyledQRCode({
+ value,
+ size = 240,
+ fgColor = '#000000',
+ bgColor = '#FFFFFF',
+ moduleShape = 'square',
+ eyeFrameShape = 'square',
+ eyeBallShape = 'square',
+ gradient = null,
+ eyeColor = null,
+ errorCorrection = 'M',
+ logoUrl,
+ logoScale = 0.22,
+ margin = 4,
+ className,
+ id,
+}: StyledQRCodeProps) {
+ const gradientId = useMemo(
+ () => `qrgrad-${Math.random().toString(36).slice(2, 9)}`,
+ []
+ );
+
+ const ec = resolveErrorCorrection(errorCorrection, moduleShape, Boolean(logoUrl));
+
+ const model = useMemo(() => {
+ try {
+ const qr = QRCodeLib.create(value || ' ', { errorCorrectionLevel: ec });
+ return { size: qr.modules.size, data: qr.modules.data };
+ } catch {
+ return null;
+ }
+ }, [value, ec]);
+
+ if (!model) return null;
+
+ const count = model.size;
+ const total = count + margin * 2;
+ const cell = size / total;
+ const offset = margin * cell;
+
+ const at = (r: number, c: number): boolean => {
+ if (r < 0 || c < 0 || r >= count || c >= count) return false;
+ return model.data[r * count + c] === 1;
+ };
+
+ // Modules that would sit under the logo are dropped rather than painted over.
+ // Overpainting leaves half-modules at the edge that some scanners still try
+ // to read; removing them lets the error correction do its job cleanly.
+ const logoSpan = logoUrl ? Math.min(logoScale, 0.28) : 0;
+ const logoFrom = logoUrl ? Math.floor((count * (1 - logoSpan)) / 2) : -1;
+ const logoTo = logoUrl ? Math.ceil((count * (1 + logoSpan)) / 2) : -1;
+ const underLogo = (r: number, c: number) =>
+ Boolean(logoUrl) && r >= logoFrom && r < logoTo && c >= logoFrom && c < logoTo;
+
+ const modulePaths: string[] = [];
+ for (let r = 0; r < count; r++) {
+ for (let c = 0; c < count; c++) {
+ if (!at(r, c)) continue;
+ if (isInEye(r, c, count)) continue;
+ if (underLogo(r, c)) continue;
+ modulePaths.push(
+ moduleShapePath(
+ moduleShape,
+ offset + c * cell,
+ offset + r * cell,
+ cell,
+ {
+ top: at(r - 1, c),
+ bottom: at(r + 1, c),
+ left: at(r, c - 1),
+ right: at(r, c + 1),
+ },
+ r,
+ c
+ )
+ );
+ }
+ }
+
+ const eyeOrigins: [number, number][] = [
+ [0, 0],
+ [0, count - 7],
+ [count - 7, 0],
+ ];
+
+ const framePaths = eyeOrigins.map(([r, c]) =>
+ eyeFramePath(eyeFrameShape, offset + c * cell, offset + r * cell, cell * 7)
+ );
+ const ballPaths = eyeOrigins.map(([r, c]) =>
+ eyeBallPath(eyeBallShape, offset + (c + 2) * cell, offset + (r + 2) * cell, cell * 3)
+ );
+
+ const moduleFill = gradient ? `url(#${gradientId})` : fgColor;
+ const eyeFill = eyeColor || moduleFill;
+
+ const logoPx = size * logoSpan;
+ const logoXY = (size - logoPx) / 2;
+
+ return (
+
+ {gradient && (
+
+ {gradient.type === 'linear' ? (
+
+
+
+
+ ) : (
+
+
+
+
+ )}
+
+ )}
+
+
+
+
+
+ {framePaths.map((d, i) => (
+
+ ))}
+ {ballPaths.map((d, i) => (
+
+ ))}
+
+ {logoUrl && (
+
+ )}
+
+ );
+}
diff --git a/src/components/marketing/PostDownloadPopup.tsx b/src/components/marketing/PostDownloadPopup.tsx
index a814123..30fba8e 100644
--- a/src/components/marketing/PostDownloadPopup.tsx
+++ b/src/components/marketing/PostDownloadPopup.tsx
@@ -2,38 +2,96 @@
import React, { useEffect, useRef } from 'react';
import Link from 'next/link';
-import { X, Zap, BarChart2, RefreshCw, Palette } from 'lucide-react';
+import { X } from 'lucide-react';
import { Button } from '@/components/ui/Button';
+export type DownloadPopupVariant =
+ | 'url'
+ | 'googleReview'
+ | 'wifi'
+ | 'vcard'
+ | 'crypto'
+ | 'social'
+ | 'meeting';
+
interface PostDownloadPopupProps {
open: boolean;
onClose: () => void;
+ variant?: DownloadPopupVariant;
}
-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' },
-];
+/**
+ * Copy notes.
+ *
+ * The old version led with "Your QR code is downloading!" - a status message the
+ * browser already shows - and buried the actual argument as the third of four
+ * equally weighted bullets. In a list where everything looks the same, nothing
+ * is important.
+ *
+ * This version opens on a fact about the file that just landed in the user's
+ * downloads folder. Not a warning, not a pitch: a consequence of what they did
+ * ten seconds ago. Then it concedes that a static code is the correct choice for
+ * a permanent link, which is what makes the following sentence credible rather
+ * than salesy.
+ */
+const HEADLINES: Record = {
+ url: 'This code now points at that URL forever.',
+ googleReview: 'This code now points at that Google profile forever.',
+ wifi: 'This code now carries that WiFi password forever.',
+ vcard: 'This code now carries those contact details forever.',
+ crypto: 'This code now carries that wallet address forever.',
+ social: 'This code now points at that profile forever.',
+ meeting: 'This code now points at that meeting link forever.',
+};
-const LS_KEY = 'qrm_download_popup_seen';
+const CONCESSIONS: Record = {
+ url: 'For a permanent link, that is exactly right. If the destination ever changes, you need a new code and new printed material.',
+ googleReview:
+ 'For a permanent profile, that is exactly right. If your review link ever changes, you need a new code and new printed material.',
+ wifi: 'Worth knowing before you print it: change the password and every printed copy stops working.',
+ vcard: 'For details that never change, that is exactly right. New number or new job title means a new code and new cards.',
+ crypto: 'For a wallet you keep, that is exactly right. Move wallets and every printed copy points at the old address.',
+ social: 'For a handle you keep, that is exactly right. Change the handle and every printed copy leads nowhere.',
+ meeting:
+ 'Worth knowing before you print it: a recurring meeting link is fine, a one-off link expires with the meeting.',
+};
+
+const LS_KEY = 'qrm_download_popup_seen_at';
+const REMIND_AFTER_DAYS = 30;
export function shouldShowDownloadPopup(): boolean {
- try { return !localStorage.getItem(LS_KEY); } catch { return false; }
+ try {
+ const raw = localStorage.getItem(LS_KEY);
+ if (!raw) return true;
+ const seenAt = Number(raw);
+ if (!Number.isFinite(seenAt)) return true;
+ return Date.now() - seenAt > REMIND_AFTER_DAYS * 24 * 60 * 60 * 1000;
+ } catch {
+ return false;
+ }
}
export function markDownloadPopupSeen(): void {
- try { localStorage.setItem(LS_KEY, '1'); } catch { /* ignore */ }
+ try {
+ localStorage.setItem(LS_KEY, String(Date.now()));
+ } catch {
+ /* ignore */
+ }
}
-export default function PostDownloadPopup({ open, onClose }: PostDownloadPopupProps) {
+export default function PostDownloadPopup({
+ open,
+ onClose,
+ variant = 'url',
+}: PostDownloadPopupProps) {
const overlayRef = useRef(null);
useEffect(() => {
if (!open) return;
markDownloadPopupSeen();
- const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
+ const onKey = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') onClose();
+ };
document.addEventListener('keydown', onKey);
return () => document.removeEventListener('keydown', onKey);
}, [open, onClose]);
@@ -45,52 +103,51 @@ export default function PostDownloadPopup({ open, onClose }: PostDownloadPopupPr
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(); }}
+ onClick={(e) => {
+ if (e.target === overlayRef.current) onClose();
+ }}
+ role="dialog"
+ aria-modal="true"
+ aria-labelledby="download-popup-title"
>
-
-
- {/* Header */}
-
+
+
-
+
-
-
-
-
Your QR code is downloading!
-
- Want to make it smarter — for free?
+
+
+
+
+ {CONCESSIONS[variant]}
+
+
+
+ A free account gives you 3 dynamic codes: same image, destination
+ editable any time, every scan counted.
- {/* Benefits */}
-
- {BENEFITS.map(({ icon: Icon, text }) => (
-
- ))}
-
-
- {/* CTAs */}
-
+
-
- Create Free Account
+
+ Create a free account - no card
- No thanks, keep it static
+ No thanks, static is fine
diff --git a/src/i18n/de.json b/src/i18n/de.json
index 9f78d89..c3a6560 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -23,7 +23,7 @@
"hero": {
"badge": "Kostenloser QR-Code-Generator",
"title": "Erstellen Sie QR-Codes, die überall funktionieren",
- "subtitle": "Ein falsches Ziel muss nach dem Druck kein Problem bleiben. Ziel in Sekunden ändern, jeden Scan nachverfolgen und die Grenzen deines Plans kennen, bevor du dich festlegst – nicht danach.",
+ "subtitle": "Ein falsches Ziel muss nach dem Druck kein Problem bleiben. Ziel in Sekunden ändern, jeden Scan nachverfolgen und die Grenzen deines Plans kennen, bevor du dich festlegst - nicht danach.",
"features": [
"Keine Kreditkarte zum Starten erforderlich",
"QR-Codes für immer kostenlos erstellen",
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 53ad2fe..56caccc 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -22,13 +22,13 @@
},
"hero": {
"badge": "QR Master Free QR Code Generator",
- "title": "QR Master: Free Dynamic QR Code Generator with Tracking",
- "subtitle": "The link doesn't have to stay wrong once it's printed. Change the destination in seconds, track every scan, and see your plan's limits before you commit — not after.",
+ "title": "The Link Doesn't Have to Stay Wrong Once It's Printed",
+ "subtitle": "QR Master is the free QR code generator where the printed code stays the same and the destination stays editable. Change where it points in seconds, see which placement drove the scan, and read the limits before you print - not after.",
"features": [
- "No credit card required to start",
- "Create QR codes free forever",
- "Advanced tracking and analytics",
- "Custom colors and styles"
+ "3 dynamic codes free forever - no credit card",
+ "Unlimited static codes that never expire",
+ "Scan data by time, device and location",
+ "Your colours free on every plan, print-ready SVG"
],
"cta_primary": "Make a QR Code Free",
"cta_secondary": "View Pricing",
@@ -91,7 +91,7 @@
"features": [
"Edit content anytime",
"Advanced analytics",
- "Custom branding",
+ "Placement-level comparison",
"Bulk operations"
]
}
@@ -146,7 +146,7 @@
"3 active dynamic QR codes (8 types available)",
"Unlimited static QR codes",
"Basic scan tracking",
- "Standard QR design templates",
+ "Your colors - foreground and background, free",
"Download as SVG/PNG"
]
},
@@ -160,7 +160,7 @@
"50 dynamic QR codes",
"Unlimited static QR codes",
"Advanced analytics (scans, devices, locations)",
- "Custom branding (colors & logos)"
+ "4 module shapes, custom eye styles and your logo"
]
},
"business": {
@@ -172,7 +172,9 @@
"500 dynamic QR codes",
"Unlimited static QR codes",
"Everything from Pro",
- "Bulk QR Creation (up to 1,000)",
+ "Full designer: 11 module shapes and colour gradients",
+ "Saved design presets, applied to a whole bulk upload",
+ "Bulk QR Creation (1,000 static or 500 dynamic)",
"Priority email support",
"Advanced tracking & insights"
]
diff --git a/src/lib/blog-data.ts b/src/lib/blog-data.ts
index 4de56d5..cc1dcd8 100644
--- a/src/lib/blog-data.ts
+++ b/src/lib/blog-data.ts
@@ -27,7 +27,7 @@ export const blogPosts: BlogPost[] = [
Unfortunately, you are not alone in this situation, but the technical reality is strict: it is impossible . In this comprehensive guide, we will explore exactly why this is the case, what your immediate options are, the underlying mathematics of QR codes, and how you can prevent this costly mistake in the future.
The History: Why Static QR Codes Were Created
-
When Denso Wave invented the QR code in 1994, the primary goal was high-speed scanning for the automotive manufacturing industry. They needed a way to track vehicle parts rapidly on an assembly line. These codes weren't designed for marketing campaigns, dynamic menus, or social media links where destinations frequently change. A static QR code simply translates a string of characters (like a URL or a serial number) directly into a visual pattern. This means the code acts exactly like a printed sentence—once the ink is on the paper, the letters cannot magically rearrange themselves.
+
When Denso Wave invented the QR code in 1994, the primary goal was high-speed scanning for the automotive manufacturing industry. They needed a way to track vehicle parts rapidly on an assembly line. These codes weren't designed for marketing campaigns, dynamic menus, or social media links where destinations frequently change. A static QR code simply translates a string of characters (like a URL or a serial number) directly into a visual pattern. This means the code acts exactly like a printed sentence-once the ink is on the paper, the letters cannot magically rearrange themselves.
The Technical Reason (According to International Standards)
@@ -110,9 +110,9 @@ export const blogPosts: BlogPost[] = [
title:
"Static vs Dynamic QR Code: What's the Difference and Which Do You Need?",
description:
- 'Static QR codes are permanent. Dynamic QR codes let you change the destination after printing. This guide shows when to use each — with real reprint cost examples.',
+ 'Static QR codes are permanent. Dynamic QR codes let you change the destination after printing. This guide shows when to use each - with real reprint cost examples.',
excerpt:
- 'Static QR codes are permanent. Dynamic QR codes let you change the destination after printing. This guide shows when to use each — with real reprint cost examples.',
+ 'Static QR codes are permanent. Dynamic QR codes let you change the destination after printing. This guide shows when to use each - with real reprint cost examples.',
category: 'QR Code Basics',
pillar: 'use-cases',
published: true,
@@ -139,19 +139,19 @@ export const blogPosts: BlogPost[] = [
'can you change the url of a qr code',
'dynamic qr code meaning',
],
- quickAnswer: `
A static QR code encodes data permanently — you cannot change it after printing. A dynamic QR code uses a short redirect URL so you can update the destination at any time without reprinting. Dynamic codes cost more but are essential for print campaigns where links change.
`,
+ quickAnswer: `
A static QR code encodes data permanently - you cannot change it after printing. A dynamic QR code uses a short redirect URL so you can update the destination at any time without reprinting. Dynamic codes cost more but are essential for print campaigns where links change.
`,
keySteps: [
- 'Decide if your destination URL will ever change — if yes, use dynamic.',
+ 'Decide if your destination URL will ever change - if yes, use dynamic.',
'For business cards, menus, or seasonal campaigns: always use dynamic.',
'For one-time personal use (WiFi, event check-in): static is fine.',
'Calculate your reprint costs before committing to static at scale.',
- 'With dynamic QR codes, update the URL in your dashboard — the printed code keeps working.',
+ 'With dynamic QR codes, update the URL in your dashboard - the printed code keeps working.',
],
faq: [
{
question: 'Can you edit a QR code after printing?',
answer:
- 'Not directly. A static QR code cannot be edited — the data is fixed. A dynamic QR code can be edited: you change the destination URL in your dashboard, and the printed code automatically redirects to the new URL.',
+ 'Not directly. A static QR code cannot be edited - the data is fixed. A dynamic QR code can be edited: you change the destination URL in your dashboard, and the printed code automatically redirects to the new URL.',
},
{
question: 'Can you change a QR code link?',
@@ -161,17 +161,17 @@ export const blogPosts: BlogPost[] = [
{
question: 'Can you redirect a QR code?',
answer:
- 'Yes — dynamic QR codes work through a redirect layer. You set the final destination in a dashboard and can change it at any time. Static QR codes cannot be redirected.',
+ 'Yes - dynamic QR codes work through a redirect layer. You set the final destination in a dashboard and can change it at any time. Static QR codes cannot be redirected.',
},
{
question: 'Can you change the URL of a QR code?',
answer:
- 'Only for dynamic QR codes. The printed code links to a short managed URL. You update the destination in your account and the redirect changes — no new print needed.',
+ 'Only for dynamic QR codes. The printed code links to a short managed URL. You update the destination in your account and the redirect changes - no new print needed.',
},
{
question: 'What does dynamic QR code mean?',
answer:
- 'A dynamic QR code encodes a short redirect URL instead of your final destination. This means you can update where the code sends people at any time — without reprinting the physical code.',
+ 'A dynamic QR code encodes a short redirect URL instead of your final destination. This means you can update where the code sends people at any time - without reprinting the physical code.',
},
{
question: 'Are static QR codes free?',
@@ -181,7 +181,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'What happens when I reprint a static QR code?',
answer:
- 'Reprinting means discarding all previously printed materials and paying full production costs again. For campaigns that change URLs seasonally or annually, this adds up quickly. A single reprint batch for 500 flyers at €0.15 per piece costs €75 — before design and distribution.',
+ 'Reprinting means discarding all previously printed materials and paying full production costs again. For campaigns that change URLs seasonally or annually, this adds up quickly. A single reprint batch for 500 flyers at €0.15 per piece costs €75 - before design and distribution.',
},
{
question: 'Do dynamic QR codes expire?',
@@ -196,22 +196,22 @@ export const blogPosts: BlogPost[] = [
],
sources: [
{
- name: 'ISO/IEC 18004:2015 — QR Code bar code symbology specification',
+ name: 'ISO/IEC 18004:2015 - QR Code bar code symbology specification',
url: 'https://www.iso.org/standard/62021.html',
accessDate: 'April 2026',
},
{
- name: 'Denso Wave — QR Code inventor, official QR code information',
+ name: 'Denso Wave - QR Code inventor, official QR code information',
url: 'https://www.qrcode.com/en/about/',
accessDate: 'April 2026',
},
{
- name: 'QR Master — Reprint Cost Calculator',
+ name: 'QR Master - Reprint Cost Calculator',
url: 'https://www.qrmaster.net/reprint-calculator',
accessDate: 'April 2026',
},
{
- name: 'QR Master — Dynamic QR Code Generator',
+ name: 'QR Master - Dynamic QR Code Generator',
url: 'https://www.qrmaster.net/dynamic-qr-code-generator',
accessDate: 'April 2026',
},
@@ -226,11 +226,11 @@ export const blogPosts: BlogPost[] = [
Quick answer
-
A static QR code is permanent — once printed, the data is fixed forever. A dynamic QR code routes through a redirect you control, so you can update the destination at any time without reprinting. Dynamic codes are essential for any campaign where URLs change.
+
A static QR code is permanent - once printed, the data is fixed forever. A dynamic QR code routes through a redirect you control, so you can update the destination at any time without reprinting. Dynamic codes are essential for any campaign where URLs change.
What is a Static QR Code?
-
A static QR code encodes your data — a URL, contact, WiFi password, or plain text — directly into the QR pattern. Once generated and printed, that data is permanent. You cannot change it.
+
A static QR code encodes your data - a URL, contact, WiFi password, or plain text - directly into the QR pattern. Once generated and printed, that data is permanent. You cannot change it.
Static codes work fine for:
Personal WiFi passwords that never change
@@ -238,15 +238,15 @@ export const blogPosts: BlogPost[] = [
Plain text like a short message or fixed phone number
Anything where you are 100% certain the content will not change
-
The problem starts when you use static QR codes for business campaigns where URLs change — menus get updated, promo pages expire, link structures migrate. Every change forces a full reprint.
+
The problem starts when you use static QR codes for business campaigns where URLs change - menus get updated, promo pages expire, link structures migrate. Every change forces a full reprint.
What is a Dynamic QR Code?
-
A dynamic QR code encodes a short redirect URL (e.g., qrmaster.net/r/abc123) instead of your final destination. You control where that redirect points in a dashboard. Change the destination, and the printed code automatically sends scanners to the new URL — no reprint needed.
+
A dynamic QR code encodes a short redirect URL (e.g., qrmaster.net/r/abc123) instead of your final destination. You control where that redirect points in a dashboard. Change the destination, and the printed code automatically sends scanners to the new URL - no reprint needed.
Dynamic codes also enable:
- Scan tracking — see how many people scanned, on which device, from which country
- A/B destination testing — route scans to different landing pages to compare performance
- Campaign attribution — tie print placements to real conversion data
+ Scan tracking - see how many people scanned, on which device, from which country
+ A/B destination testing - route scans to different landing pages to compare performance
+ Campaign attribution - tie print placements to real conversion data
Static vs Dynamic QR Code: Feature Comparison
@@ -295,7 +295,7 @@ export const blogPosts: BlogPost[] = [
The Real Cost of Static QR Code Reprints
-
Here is where the math matters. Many businesses default to static QR codes because they are free to generate. But when the linked URL changes — which happens more often than expected — the cost of reprinting is significant.
+
Here is where the math matters. Many businesses default to static QR codes because they are free to generate. But when the linked URL changes - which happens more often than expected - the cost of reprinting is significant.
Example 1: Restaurant Menu
A restaurant prints 200 table tent cards with a static QR code linking to their PDF menu. The menu changes quarterly (seasonal updates). Each reprint batch costs €60 in printing.
@@ -337,17 +337,17 @@ export const blogPosts: BlogPost[] = [
Get started with a dynamic QR code generator to create codes you can update after printing. Pair it with QR code tracking to see exactly who scans and from where.
Summary
-
The choice between static and dynamic QR codes comes down to one question: will the destination ever change? If yes, use dynamic — the subscription cost is almost always lower than the cost of reprinting. If no, static is free and perfectly adequate.
+
The choice between static and dynamic QR codes comes down to one question: will the destination ever change? If yes, use dynamic - the subscription cost is almost always lower than the cost of reprinting. If no, static is free and perfectly adequate.
`,
},
{
slug: 'microsoft-teams-qr-code',
- title: 'How to Create a Microsoft Teams QR Code for Instant Meeting Joins',
+ title: 'How to Create a Microsoft Teams Meeting QR Code',
description:
- 'Step-by-step guide to creating a QR code for any Microsoft Teams meeting. Attendees scan once to join — no link typing needed. Free tool included.',
+ 'Four steps, about two minutes: copy the Teams join link, generate the code, size it for print, test it. Includes the free generator and the right print size.',
excerpt:
- 'Step-by-step guide to creating a QR code for any Microsoft Teams meeting. Attendees scan once to join — no link typing needed.',
+ 'Step-by-step guide to creating a QR code for any Microsoft Teams meeting. Attendees scan once to join - no link typing needed.',
category: 'Business Tools',
pillar: 'use-cases',
published: true,
@@ -370,7 +370,7 @@ export const blogPosts: BlogPost[] = [
'join teams meeting qr',
'teams besprechung qr code',
],
- quickAnswer: `
Copy your Teams meeting URL → paste it into QR Master's free Teams QR generator → download the code → display it in your meeting room or invitation. Attendees scan once to join instantly — no link typing needed.
`,
+ quickAnswer: `
Copy your Teams meeting URL → paste it into QR Master's free Teams QR generator → download the code → display it in your meeting room or invitation. Attendees scan once to join instantly - no link typing needed.
`,
keySteps: [
'Open your Microsoft Teams calendar and click on the meeting to copy the Join link.',
'Go to qrmaster.net/tools/teams-qr-code and paste the link.',
@@ -382,7 +382,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'Does the QR code work for recurring Teams meetings?',
answer:
- 'Yes — recurring Teams meetings typically reuse the same join link, so one QR code works for every session.',
+ 'Yes - recurring Teams meetings typically reuse the same join link, so one QR code works for every session.',
},
{
question: 'Can guests without a Teams account join via QR code?',
@@ -393,7 +393,7 @@ export const blogPosts: BlogPost[] = [
question:
"What's the difference between a static and dynamic Teams QR code?",
answer:
- "A static QR code encodes the meeting link permanently. A
dynamic QR code lets you update the link anytime — useful for room displays where meetings change.",
+ "A static QR code encodes the meeting link permanently. A
dynamic QR code lets you update the link anytime - useful for room displays where meetings change.",
},
{
question: 'How big should I print the Teams QR code?',
@@ -409,7 +409,7 @@ export const blogPosts: BlogPost[] = [
relatedSlugs: ['dynamic-vs-static-qr-codes', 'qr-code-small-business'],
sources: [
{
- name: 'Microsoft Teams – Create and join meetings',
+ name: 'Microsoft Teams - Create and join meetings',
url: 'https://support.microsoft.com/en-us/office/schedule-a-meeting-in-microsoft-teams-943507a9-8583-4c58-b5d2-8ec8265e04e5',
accessDate: 'April 2026',
},
@@ -441,21 +441,21 @@ export const blogPosts: BlogPost[] = [
Get the Teams meeting link. Open Microsoft Teams → Calendar → click on your meeting → click "Copy join link". The URL starts with https://teams.microsoft.com/l/meetup-join/...
Open the Teams QR generator. Go to qrmaster.net/tools/teams-qr-code . No account needed for a basic static code.
- Paste and customize. Paste the meeting URL. For branding: use Teams purple (#6264A7) for the QR modules. Add a frame label — "Scan to Join" or "Join Teams Meeting" works well.
+ Paste and customize. Paste the meeting URL. For branding: use Teams purple (#6264A7) for the QR modules. Add a frame label - "Scan to Join" or "Join Teams Meeting" works well.
Choose your format.
- PNG — for digital displays (room screens, Slack, email signatures)
- SVG — for print (scales to any size without losing quality)
+ PNG - for digital displays (room screens, Slack, email signatures)
+ SVG - for print (scales to any size without losing quality)
- Test before deploying. Scan with an iPhone (native camera) and an Android (Google Lens). Test from the actual scanning distance — usually 0.5–1.5 meters for room displays.
+ Test before deploying. Scan with an iPhone (native camera) and an Android (Google Lens). Test from the actual scanning distance - usually 0.5-1.5 meters for room displays.
Display or print. For room screens, most Teams Rooms devices support custom backgrounds or a dedicated display app. For print, minimum 4×4 cm on paper.
Static vs. Dynamic: Which Should You Use?
-
For a one-off meeting like a client call, a static QR code is fine — it encodes the link permanently and requires no account.
-
For meeting rooms or recurring situations, use a dynamic QR code . Here's why: when a recurring meeting is updated or moved to a new link, a dynamic code lets you update the destination from your dashboard without changing or reprinting the QR code. The code on the room's screen stays the same — only the link behind it changes.
-
Dynamic codes also give you scan analytics — how many people joined via QR vs. link, what device they used, and what time of day has the most scans. Useful if you're running regular events or trainings.
+
For a one-off meeting like a client call, a static QR code is fine - it encodes the link permanently and requires no account.
+
For meeting rooms or recurring situations, use a dynamic QR code . Here's why: when a recurring meeting is updated or moved to a new link, a dynamic code lets you update the destination from your dashboard without changing or reprinting the QR code. The code on the room's screen stays the same - only the link behind it changes.
+
Dynamic codes also give you scan analytics - how many people joined via QR vs. link, what device they used, and what time of day has the most scans. Useful if you're running regular events or trainings.
Sizing Guide for Teams QR Codes
@@ -495,7 +495,7 @@ export const blogPosts: BlogPost[] = [
Teams opens but shows "Meeting not found": The meeting link has expired or was cancelled. If using a dynamic QR code, update the destination link in your dashboard.
QR code scans but nothing happens on some devices: Some older Android devices need a QR scanner app. The Teams app itself includes a QR scanner under Settings → Scan QR code.
- Link too long to encode cleanly: Teams join URLs are long. Use a dynamic QR code — it encodes a short redirect URL instead, which produces a less dense, more reliably scannable code.
+ Link too long to encode cleanly: Teams join URLs are long. Use a dynamic QR code - it encodes a short redirect URL instead, which produces a less dense, more reliably scannable code.
Setting up QR codes for an office or venue? Pair Teams meeting access with a WiFi QR code , or use the German QR Code Erstellen page for local teams.
@@ -564,7 +564,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'Can I update my menu without reprinting?',
answer:
- 'Yes—if you use a dynamic QR code, you can change the destination anytime.',
+ 'Yes-if you use a dynamic QR code, you can change the destination anytime.',
},
{
question: 'Why is my menu QR code not scanning well?',
@@ -585,7 +585,7 @@ export const blogPosts: BlogPost[] = [
],
sources: [
{
- name: 'National Restaurant Association – State of the Industry 2022',
+ name: 'National Restaurant Association - State of the Industry 2022',
url: 'https://restaurant.org/research-and-media/media/press-releases/2022/',
accessDate: 'April 2026',
},
@@ -603,23 +603,23 @@ export const blogPosts: BlogPost[] = [
How to set up a restaurant QR code menu (quick answer)
- 1. Prepare your menu — upload as a PDF or use a mobile-friendly landing page
+ 1. Prepare your menu - upload as a PDF or use a mobile-friendly landing page
2. Create a dynamic QR code so you can update the link anytime without reprinting
3. Customize the design with your logo and brand colors
- 4. Print at minimum 2" × 2" and place where guests naturally look — table tent, menu cover, or window
+ 4. Print at minimum 2" × 2" and place where guests naturally look - table tent, menu cover, or window
5. Track scans to see peak times and optimize
Why QR Code Menus Became a Restaurant Standard
- What started as a contactless safety measure during the pandemic has become a permanent fixture in the restaurant industry. According to industry data, over 60% of restaurants that adopted QR menus during 2020–2021 kept them afterward — not for safety reasons, but because of the business benefits [NRA, 2022] .
- The advantages are straightforward: a printed menu costs $3–8 per copy to laminate and reprint whenever items change. A QR menu costs nothing to update. For a restaurant that changes its seasonal specials every few months, that adds up fast. Beyond cost savings, QR menus open up analytics that paper never could — you can see which menu sections guests spend the most time on, when peak scanning happens during the day, and which table locations drive the most engagement.
+ What started as a contactless safety measure during the pandemic has become a permanent fixture in the restaurant industry. According to industry data, over 60% of restaurants that adopted QR menus during 2020-2021 kept them afterward - not for safety reasons, but because of the business benefits [NRA, 2022] .
+ The advantages are straightforward: a printed menu costs $3-8 per copy to laminate and reprint whenever items change. A QR menu costs nothing to update. For a restaurant that changes its seasonal specials every few months, that adds up fast. Beyond cost savings, QR menus open up analytics that paper never could - you can see which menu sections guests spend the most time on, when peak scanning happens during the day, and which table locations drive the most engagement.
Static vs. Dynamic QR Codes for Restaurant Menus
This is the most important decision you'll make when setting up a restaurant QR menu.
- Static QR codes encode your menu URL directly into the code. Once printed, the link is permanent. If your menu URL changes — or if you switch to a different menu platform — you have to reprint every table card, tent, and sign. They're fine for a single use, but impractical for a living document like a restaurant menu.
+ Static QR codes encode your menu URL directly into the code. Once printed, the link is permanent. If your menu URL changes - or if you switch to a different menu platform - you have to reprint every table card, tent, and sign. They're fine for a single use, but impractical for a living document like a restaurant menu.
Dynamic QR codes work differently: they point to a short redirect URL that you control from a dashboard. The printed QR code never changes, but you can update the destination link anytime. Seasonal menu? Update the link. Switching from a PDF to a proper menu website? Update the link. No reprinting, no waste.
- Dynamic QR codes also give you scan analytics — device types, scan times, and location data. For a restaurant, knowing that 80% of guests scan between 12:00 and 13:00 on weekdays tells you something about how to structure your lunch push.
+ Dynamic QR codes also give you scan analytics - device types, scan times, and location data. For a restaurant, knowing that 80% of guests scan between 12:00 and 13:00 on weekdays tells you something about how to structure your lunch push.
Recommendation: Always use dynamic QR codes for restaurant menus. Create a dynamic QR code here →
What Should Your QR Menu Link To?
@@ -643,7 +643,7 @@ export const blogPosts: BlogPost[] = [
- Whatever you choose, make sure the page is mobile-optimized — over 90% of QR menu scans happen on a smartphone. Test it: if you have to pinch to zoom or scroll horizontally, guests will abandon it.
+ Whatever you choose, make sure the page is mobile-optimized - over 90% of QR menu scans happen on a smartphone. Test it: if you have to pinch to zoom or scroll horizontally, guests will abandon it.
Step-by-Step: Setting Up Your Restaurant QR Menu
@@ -651,9 +651,9 @@ export const blogPosts: BlogPost[] = [
Get your menu URL. Upload the PDF to a hosting service or use the URL of your menu page. Make sure it loads in under 3 seconds on a mobile connection.
Create a dynamic QR code at QR Master . Paste your menu URL as the destination. This creates a redirect you can update later.
Customize the design. Add your restaurant logo, use your brand colors, and make sure there's enough contrast between the QR pattern and background (always dark on white, never reverse).
- Download the QR code in SVG format for print quality. SVG scales to any size without pixelation — critical for table cards and larger signs.
+ Download the QR code in SVG format for print quality. SVG scales to any size without pixelation - critical for table cards and larger signs.
Test before printing. Scan with both iPhone (native camera) and Android (Google Lens and native camera). Test at different distances and lighting conditions.
- Print at the right size. Minimum 2" × 2" (5 × 5 cm) for table use. Recommended 2.5" × 2.5" for table tents. For window decals or A-frames, go larger — 4" × 4" or more.
+ Print at the right size. Minimum 2" × 2" (5 × 5 cm) for table use. Recommended 2.5" × 2.5" for table tents. For window decals or A-frames, go larger - 4" × 4" or more.
Place where guests look naturally (see placement guide below).
@@ -672,8 +672,8 @@ export const blogPosts: BlogPost[] = [
The QR code is a physical extension of your brand. It should look intentional, not like a generic black-and-white square dropped on a napkin dispenser.
Contrast is everything: Dark pattern on a white or very light background. The minimum contrast ratio for reliable scanning is about 4:1. Avoid placing the QR code on a colored or patterned background without sufficient contrast.
- Add your logo: Most QR generators support embedding a logo in the center. Keep the logo small (max 20–25% of the QR area) so it doesn't obscure too many data modules.
- Brand colors: You can use a brand color for the QR modules, but test thoroughly — some color combinations scan unreliably on older devices. When in doubt, keep the modules dark.
+ Add your logo: Most QR generators support embedding a logo in the center. Keep the logo small (max 20-25% of the QR area) so it doesn't obscure too many data modules.
+ Brand colors: You can use a brand color for the QR modules, but test thoroughly - some color combinations scan unreliably on older devices. When in doubt, keep the modules dark.
Quiet zone: Leave a white margin around the QR code. At least 4 module widths on every side. Cutting into this margin causes scan failures.
Label it: Add a short call-to-action below the code: "Scan for menu" or "View our menu." Guests who aren't sure what to do will appreciate the prompt.
@@ -686,7 +686,7 @@ export const blogPosts: BlogPost[] = [
Location data: If you have multiple locations or multiple QR codes at different positions (table tents vs. window), you can see which placements drive the most scans.
Scan trends over time: Is a new menu section driving more engagement? Did a menu update increase scan rates?
- Use UTM parameters in your menu URL to connect QR scan data to Google Analytics — this lets you see not just how many people scanned, but what they did on the menu page afterward. Learn more about QR code scan tracking →
+ Use UTM parameters in your menu URL to connect QR scan data to Google Analytics - this lets you see not just how many people scanned, but what they did on the menu page afterward. Learn more about QR code scan tracking →
Troubleshooting: Why Guests Can't Scan Your QR Menu
If guests are struggling to scan, the cause is almost always one of these:
@@ -695,12 +695,12 @@ export const blogPosts: BlogPost[] = [
Low contrast: Dark QR on dark background, or faded print. Always use dark modules on a white or near-white background.
Glossy lamination with reflections: Light glare from overhead lighting hits the laminated surface and confuses the camera. Use matte lamination for table cards.
Damaged or dirty code: A QR code can withstand up to 30% damage (that's the error correction), but a sticky menu tent with sauce obscuring a corner will fail. Replace when dirty.
- Slow destination page: The QR code itself scanned fine — the menu page is just slow. Optimize page load speed. A PDF over 2MB on a weak mobile connection feels broken even if it's technically working.
+ Slow destination page: The QR code itself scanned fine - the menu page is just slow. Optimize page load speed. A PDF over 2MB on a weak mobile connection feels broken even if it's technically working.
No quiet zone: If the QR code was cropped or printed too close to an edge, scanning will fail. Always check the printed proof before ordering a large batch.
Restaurant QR Menus and Use-Case Examples
- QR menus work across different restaurant formats — but the implementation details vary. See how different restaurant types use QR Master for their menus →
+ QR menus work across different restaurant formats - but the implementation details vary. See how different restaurant types use QR Master for their menus →
Fine dining: Embed the QR code on the back of a premium menu card. Keep the design minimal and brand-consistent. Use a PDF of the full menu with allergen notes.
Casual dining / fast casual: Table tents or placemat QR codes. Update frequently for daily specials. Use a mobile-optimized landing page for the best experience.
@@ -756,17 +756,17 @@ export const blogPosts: BlogPost[] = [
{
question: 'Does a vCard QR code work on iPhone and Android?',
answer:
- 'Yes—most modern phones support saving contacts via vCard/VCF after scanning.',
+ 'Yes-most modern phones support saving contacts via vCard/VCF after scanning.',
},
{
- question: "Static vs dynamic vCard—what's better?",
+ question: "Static vs dynamic vCard-what's better?",
answer:
'Dynamic is better if your info changes. You can update details and track scans.',
},
{
question: 'Can I include LinkedIn and social links?',
answer:
- 'Yes—add LinkedIn/website links for a stronger professional profile.',
+ 'Yes-add LinkedIn/website links for a stronger professional profile.',
},
{
question: 'What file format is best for printing business cards?',
@@ -791,33 +791,33 @@ export const blogPosts: BlogPost[] = [
Ready to create one?
-
Use the free vCard QR Code Generator — no signup required, works on iPhone and Android.
+
Use the free vCard QR Code Generator - no signup required, works on iPhone and Android.
Create your vCard QR Code →
What is a vCard QR Code?
- A vCard (Virtual Contact File) QR code contains your contact information in a standardized format (.vcf). When someone scans it with their smartphone camera, they can instantly save your details to their contacts — no typing required. It replaces the friction of spelling out a phone number or email at a networking event with a single tap.
+ A vCard (Virtual Contact File) QR code contains your contact information in a standardized format (.vcf). When someone scans it with their smartphone camera, they can instantly save your details to their contacts - no typing required. It replaces the friction of spelling out a phone number or email at a networking event with a single tap.
The QR code encodes a VCF data string directly. That string includes fields like name, phone, email, company, job title, website, and LinkedIn. When the phone's camera reads the code, it parses the VCF and offers to add the contact in one tap.
Static vs Dynamic vCard QR Codes
This is the most important choice you'll make.
- Static vCard QR codes embed your contact info directly into the code. They work without any internet connection — the data is in the code itself. The downside: if your phone number or email changes, the code is outdated and needs to be reprinted.
- Dynamic vCard QR codes point to a redirect URL that serves the VCF. You can update your contact info at any time from a dashboard — the printed QR code stays the same. Dynamic codes also give you scan analytics: how many times it was scanned, on which devices, from which locations.
+ Static vCard QR codes embed your contact info directly into the code. They work without any internet connection - the data is in the code itself. The downside: if your phone number or email changes, the code is outdated and needs to be reprinted.
+ Dynamic vCard QR codes point to a redirect URL that serves the VCF. You can update your contact info at any time from a dashboard - the printed QR code stays the same. Dynamic codes also give you scan analytics: how many times it was scanned, on which devices, from which locations.
Recommendation: Use dynamic if you might change jobs, switch numbers, or want to track engagement. Use static if you just need a simple one-time contact share.
- The QR Master vCard generator supports both — static for simplicity, dynamic for flexibility and tracking.
+ The QR Master vCard generator supports both - static for simplicity, dynamic for flexibility and tracking.
What to Include in a vCard QR Code
Less is more. Include the fields people actually need to follow up with you:
- Full name — exactly as you want it to appear in contacts
- Phone number — in international format (+49...) for cross-border scanning
- Email address — your primary professional email
- Company and job title — helps recipients remember context
- Website or LinkedIn — one link, whichever drives the most value for you
+ Full name - exactly as you want it to appear in contacts
+ Phone number - in international format (+49...) for cross-border scanning
+ Email address - your primary professional email
+ Company and job title - helps recipients remember context
+ Website or LinkedIn - one link, whichever drives the most value for you
- Avoid adding multiple phone numbers or social handles unless you have a specific reason — cluttered vCards confuse the contact save prompt on some phones.
+ Avoid adding multiple phone numbers or social handles unless you have a specific reason - cluttered vCards confuse the contact save prompt on some phones.
How to Create a vCard QR Code (Free)
@@ -833,7 +833,7 @@ export const blogPosts: BlogPost[] = [
Business cards: Print the QR on the back. Replaces typing out contact info at events.
Email signature: Embed the QR code image so recipients on mobile can tap to save your contact.
- Conference badges and lanyards: Makes networking frictionless — one scan at a booth saves full contact details.
+ Conference badges and lanyards: Makes networking frictionless - one scan at a booth saves full contact details.
Trade show materials: Brochures, flyers, and rollup banners with a QR code so visitors can save your info without collecting paper.
LinkedIn profile: Share as an image for connections who want to save your contact directly.
@@ -846,7 +846,7 @@ export const blogPosts: BlogPost[] = [
Can I include my photo in a vCard QR code?
The VCF format supports embedding a photo, but the encoded QR code becomes very large (and harder to scan) with a full photo. It's better to skip the photo in the vCard itself and put your headshot next to the QR code on the business card instead.
What's the best file format to download?
- SVG for print — it scales to any size with no pixelation. PNG at 300 DPI for digital sharing.
+ SVG for print - it scales to any size with no pixelation. PNG at 300 DPI for digital sharing.
Create your vCard QR Code
@@ -970,7 +970,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'What is the minimum QR code size for business cards?',
answer:
- 'Common safe minimum is around 0.8–1.0 inch (2–2.5 cm), depending on data density and print quality.',
+ 'Common safe minimum is around 0.8-1.0 inch (2-2.5 cm), depending on data density and print quality.',
},
{
question: 'How does scanning distance affect size?',
@@ -980,7 +980,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'Does more data require a bigger QR code?',
answer:
- 'Yes—more data increases module density and reduces scannability at small sizes.',
+ 'Yes-more data increases module density and reduces scannability at small sizes.',
},
],
relatedSlugs: [
@@ -1090,7 +1090,7 @@ export const blogPosts: BlogPost[] = [
- According to Bitly's QR Code Statistics , tracking QR scans is essential for understanding campaign performance. Dynamic QR codes allow you to capture real-time metrics like scan volume, geographic location, device types, and timestamp patterns—data that drives ROI optimization.
+ According to Bitly's QR Code Statistics , tracking QR scans is essential for understanding campaign performance. Dynamic QR codes allow you to capture real-time metrics like scan volume, geographic location, device types, and timestamp patterns-data that drives ROI optimization.
Dynamic vs Static QR for Tracking
Only dynamic QR codes can be tracked effectively. Dynamic codes redirect scans through a server, allowing analytics collection before users reach their destination. Static QR codes embed the destination directly, making tracking impossible.
`,
},
@@ -1180,7 +1180,7 @@ export const blogPosts: BlogPost[] = [
- One of the most common questions we get is: "Should I use a static or dynamic QR code ?" If you are using the QR code for marketing, business, or any long-term use, choose Dynamic . According to industry research on QR code performance , dynamic QR codes enable better campaign optimization, cost control, and real-time analytics—key advantages for business applications.
+ One of the most common questions we get is: "Should I use a static or dynamic QR code ?" If you are using the QR code for marketing, business, or any long-term use, choose Dynamic . According to industry research on QR code performance , dynamic QR codes enable better campaign optimization, cost control, and real-time analytics-key advantages for business applications.
`,
},
@@ -1210,18 +1210,18 @@ export const blogPosts: BlogPost[] = [
'csv qr code generator',
'bulk qr generator',
],
- quickAnswer: `To create bulk QR codes, prepare an Excel/CSV with one row per destination (URL/text/etc.). Upload it to a bulk generator to instantly produce many unique QR codes—ideal for badges, inventory, mailers, and campaigns.
`,
+ quickAnswer: `To create bulk QR codes, prepare an Excel/CSV with one row per destination (URL/text/etc.). Upload it to a bulk generator to instantly produce many unique QR codes-ideal for badges, inventory, mailers, and campaigns.
`,
keySteps: [
'Create an Excel/CSV with one QR per row (e.g., URL, label, optional UTM fields).',
'Validate formatting (no broken URLs, consistent columns).',
'Upload the file to a bulk QR generator tool.',
'Choose static vs dynamic (dynamic for tracking/editing).',
'Generate and download the batch (ZIP folder).',
- 'Test-scan a random sample (5–10 codes) before production.',
+ 'Test-scan a random sample (5-10 codes) before production.',
],
faq: [
{
- question: 'What file format is required—Excel or CSV?',
+ question: 'What file format is required-Excel or CSV?',
answer:
'Most tools accept CSV . Excel usually needs export to CSV.',
},
@@ -1305,7 +1305,7 @@ export const blogPosts: BlogPost[] = [
'qr code roi',
'utm analytics',
],
- quickAnswer: `QR analytics shows what happens after you publish a QR: scan volume, time patterns, device mix, and location trends. Use analytics to improve placement, landing pages, and offers—so scans turn into measurable ROI.
`,
+ quickAnswer: `QR analytics shows what happens after you publish a QR: scan volume, time patterns, device mix, and location trends. Use analytics to improve placement, landing pages, and offers-so scans turn into measurable ROI.
`,
keySteps: [
'Use dynamic QR so scan events can be measured.',
'Define KPIs (scans, conversions, cost per conversion, ROI).',
@@ -1370,7 +1370,7 @@ export const blogPosts: BlogPost[] = [
What Are Scan Analytics?
- Scan analytics provide comprehensive insights into how users interact with your dynamic QR codes . According to QRCodeChimp's 2026 QR Code Statistics , QR codes enable real-time tracking of geographic location, device types, scan timestamps, and user engagement patterns—essential metrics for optimizing marketing ROI.
+ Scan analytics provide comprehensive insights into how users interact with your dynamic QR codes . According to QRCodeChimp's 2026 QR Code Statistics , QR codes enable real-time tracking of geographic location, device types, scan timestamps, and user engagement patterns-essential metrics for optimizing marketing ROI.
Key Metrics to Monitor
The most important scan analytics include total scan volume, unique scans, geographic distribution, device mix (mobile vs desktop), and conversion rates. By analyzing these metrics, businesses can identify winning placements and underperforming campaigns quickly.
@@ -1435,7 +1435,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'Is this barcode generator free for commercial use?',
answer:
- 'Yes—our tool lets you generate and download high-resolution barcode images for commercial packaging and labels.',
+ 'Yes-our tool lets you generate and download high-resolution barcode images for commercial packaging and labels.',
},
{
question: 'How do I print barcode labels correctly?',
@@ -1460,14 +1460,14 @@ export const blogPosts: BlogPost[] = [
What Is a Barcode Generator?
- A barcode generator is a tool that turns a number or text string into a scannable barcode image. You enter a value — a SKU, product ID, or inventory code — and the tool outputs a barcode you can download and print on labels, packaging, or shelf tags.
+ A barcode generator is a tool that turns a number or text string into a scannable barcode image. You enter a value - a SKU, product ID, or inventory code - and the tool outputs a barcode you can download and print on labels, packaging, or shelf tags.
QR Master includes a free barcode generator that supports the most common formats used in retail and inventory management: EAN-13, UPC-A, and Code 128. No account required, no watermarks.
Supported Barcode Formats
- EAN-13 — The global standard for retail products. Used on nearly every consumer product sold outside North America. Requires a 13-digit number.
- UPC-A — The North American retail standard. 12 digits. Used by Amazon, Walmart, Target, and most US/Canada retail systems.
- Code 128 — The most flexible format. Supports letters and numbers. Ideal for internal inventory codes, warehouse labels, and shipping references.
+ EAN-13 - The global standard for retail products. Used on nearly every consumer product sold outside North America. Requires a 13-digit number.
+ UPC-A - The North American retail standard. 12 digits. Used by Amazon, Walmart, Target, and most US/Canada retail systems.
+ Code 128 - The most flexible format. Supports letters and numbers. Ideal for internal inventory codes, warehouse labels, and shipping references.
How to Create a Barcode
@@ -1485,11 +1485,11 @@ export const blogPosts: BlogPost[] = [
Barcode: product SKU, warehouse shelf, retail checkout, inventory count
QR code: restaurant menu, review page, product setup guide, marketing campaign
- If the QR code destination might change after printing, use a dynamic QR code — the printed code stays the same, but you can update the link at any time.
+ If the QR code destination might change after printing, use a dynamic QR code - the printed code stays the same, but you can update the link at any time.
Printing Tips for Reliable Scanning
- Use black bars on a white background — avoid colored barcodes unless tested
+ Use black bars on a white background - avoid colored barcodes unless tested
Print at 300 DPI or higher for physical labels
Leave a quiet zone (blank margin) around the barcode of at least 10 bar widths
Minimum recommended width: 25mm for EAN-13, 20mm for Code 128
@@ -1499,14 +1499,14 @@ export const blogPosts: BlogPost[] = [
Use Cases
The most common uses for an online barcode generator include:
- Product labels — give every SKU a scannable identity before retail listing
- Inventory management — label storage bins, shelves, and stock items
- eCommerce fulfillment — reduce packing errors by scanning products during order picking
- Event tickets — generate unique codes per ticket for check-in scanning
- Internal asset tracking — label equipment, tools, and office assets
+ Product labels - give every SKU a scannable identity before retail listing
+ Inventory management - label storage bins, shelves, and stock items
+ eCommerce fulfillment - reduce packing errors by scanning products during order picking
+ Event tickets - generate unique codes per ticket for check-in scanning
+ Internal asset tracking - label equipment, tools, and office assets
- Ready to create your first barcode? Use the free QR Master barcode generator — no signup, instant download.
+ Ready to create your first barcode? Use the free QR Master barcode generator - no signup, instant download.
`,
},
@@ -1573,7 +1573,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'What size should a Spotify code be for print?',
answer:
- 'Aim for at least ~2–3 cm on flyers and ~4–6 cm on posters, then test scan distance.',
+ 'Aim for at least ~2-3 cm on flyers and ~4-6 cm on posters, then test scan distance.',
},
],
@@ -1589,11 +1589,11 @@ export const blogPosts: BlogPost[] = [
- Spotify codes are one of the easiest ways to turn a real-world moment into a stream. This Spotify code generator guide shows you how Spotify codes work, how to create them, and how to use them strategically—whether you're an artist, a DJ, a venue, or a brand running campaigns.
+ Spotify codes are one of the easiest ways to turn a real-world moment into a stream. This Spotify code generator guide shows you how Spotify codes work, how to create them, and how to use them strategically-whether you're an artist, a DJ, a venue, or a brand running campaigns.
Unlike classic QR codes , Spotify codes are designed specifically for Spotify and are highly recognizable. People know instantly what they are: "Scan this and play." That makes them perfect for posters, merch, event flyers, table tents, business cards, and social media.
What is a Spotify Code?
- A Spotify code is a scannable Spotify identifier for a song, album, playlist, artist profile, or podcast episode. When someone scans it in the Spotify app, they jump straight to the content—no searching, no typing.
+ A Spotify code is a scannable Spotify identifier for a song, album, playlist, artist profile, or podcast episode. When someone scans it in the Spotify app, they jump straight to the content-no searching, no typing.
Spotify codes usually look like a soundwave-style barcode under a Spotify URI or link. They are fast, simple, and shareable.
What can you create Spotify codes for?
@@ -1653,7 +1653,7 @@ export const blogPosts: BlogPost[] = [
slug: 'whatsapp-qr-code-generator',
title: 'WhatsApp QR Code: Direct Chat Link Guide',
description:
- 'WhatsApp QR Code: generate a QR that opens a direct WhatsApp chat (wa.me). Perfect for SMBs—plus tracking, UTMs, and best practices.',
+ 'WhatsApp QR Code: generate a QR that opens a direct WhatsApp chat (wa.me). Perfect for SMBs-plus tracking, UTMs, and best practices.',
excerpt:
'Make it ridiculously easy for customers to contact you. Create a WhatsApp QR code that opens a direct chat with a pre-filled message when scanned.',
category: 'Social Media',
@@ -1705,7 +1705,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'Can I track how many people scanned my WhatsApp QR?',
answer:
- 'Yes—use a dynamic/trackable QR code or route through a landing page with UTMs.',
+ 'Yes-use a dynamic/trackable QR code or route through a landing page with UTMs.',
},
{
question: "What's the best placement for a WhatsApp QR code?",
@@ -1743,10 +1743,10 @@ export const blogPosts: BlogPost[] = [
If your goal is "make it ridiculously easy for customers to contact me," then WhatsApp QR Code erstellen is one of the highest-intent moves you can make.
- A WhatsApp QR code can open a direct chat instantly—no typing phone numbers, no searching contacts. Customers scan, WhatsApp opens, and your conversation starts. This is perfect for restaurants, salons, gyms, real estate, events, local services, and B2B sales.
+ A WhatsApp QR code can open a direct chat instantly-no typing phone numbers, no searching contacts. Customers scan, WhatsApp opens, and your conversation starts. This is perfect for restaurants, salons, gyms, real estate, events, local services, and B2B sales.
What is a WhatsApp QR Code?
- A WhatsApp QR code is a QR code that links to a WhatsApp action—usually:
+ A WhatsApp QR code is a QR code that links to a WhatsApp action-usually:
Open a chat with a specific number
Open a chat with a prefilled message
@@ -1850,7 +1850,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'Can I track Instagram QR scans in GA4?',
answer:
- 'Yes—use UTMs on the destination URL or route through a landing page.',
+ 'Yes-use UTMs on the destination URL or route through a landing page.',
},
{
question: 'Do Instagram QR codes open the app automatically?',
@@ -1865,7 +1865,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'What QR size should I use on print?',
answer:
- 'Business cards ~2–3 cm; posters larger. Always test scan distance.',
+ 'Business cards ~2-3 cm; posters larger. Always test scan distance.',
},
],
@@ -1881,7 +1881,7 @@ export const blogPosts: BlogPost[] = [
- An Instagram QR code generator helps you turn offline attention into followers. People see your brand in real life—on packaging, posters, menus, business cards, or at events—and with one scan they land on your Instagram profile.
+ An Instagram QR code generator helps you turn offline attention into followers. People see your brand in real life-on packaging, posters, menus, business cards, or at events-and with one scan they land on your Instagram profile.
If you're doing local marketing, events, creator collabs, or retail, this is one of the simplest growth levers you can deploy. But to do it properly, you want two things: a clean, fast QR that opens your profile, and a way to measure performance.
What is an Instagram QR code?
@@ -2005,7 +2005,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'What are trackable QR codes?',
answer:
- 'QR codes that log scan events (count, time, device, etc.)—often via a redirect (dynamic QR).',
+ 'QR codes that log scan events (count, time, device, etc.)-often via a redirect (dynamic QR).',
},
{
question: 'Are trackable QR codes the same as dynamic QR codes?',
@@ -2015,7 +2015,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'Can I track conversions, not just scans?',
answer:
- 'Yes—use UTMs + GA4 + a landing page to measure signups/purchases.',
+ 'Yes-use UTMs + GA4 + a landing page to measure signups/purchases.',
},
{
question: 'Do trackable QR codes scan slower?',
@@ -2024,7 +2024,7 @@ export const blogPosts: BlogPost[] = [
},
{
question: 'Can I update the destination later?',
- answer: "Yes—if it's dynamic.",
+ answer: "Yes-if it's dynamic.",
},
],
@@ -2227,7 +2227,7 @@ export const blogPosts: BlogPost[] = [
},
{
question: 'Do UTMs work if the QR goes directly to a website?',
- answer: 'Yes—GA4 captures them on landing.',
+ answer: 'Yes-GA4 captures them on landing.',
},
{
question: 'How do I track different poster locations?',
@@ -2237,7 +2237,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'Should I use "print" or "qr" as utm_medium?',
answer:
- 'Either works—pick one and stay consistent across all campaigns.',
+ 'Either works-pick one and stay consistent across all campaigns.',
},
{
question: "What's the best GA4 report for QR UTMs?",
@@ -2279,7 +2279,7 @@ export const blogPosts: BlogPost[] = [
UTM Parameters with QR Codes: How to Track Offline Scans in GA4
- QR codes are amazing for offline-to-online marketing—but without tracking, you're basically guessing. According to Bitly's 2026 QR Code Statistics , campaigns with proper tracking parameters can see significantly higher engagement and conversion rates by enabling data-driven optimization. That's where UTM parameters with QR codes come in. UTMs are simple tags you add to a URL so that Google Analytics 4 (GA4) can tell you exactly where the traffic came from.
+ QR codes are amazing for offline-to-online marketing-but without tracking, you're basically guessing. According to Bitly's 2026 QR Code Statistics , campaigns with proper tracking parameters can see significantly higher engagement and conversion rates by enabling data-driven optimization. That's where UTM parameters with QR codes come in. UTMs are simple tags you add to a URL so that Google Analytics 4 (GA4) can tell you exactly where the traffic came from.
If you run posters, flyers, menus, business cards, packaging inserts, or event banners, UTMs let you answer questions like:
@@ -2460,7 +2460,7 @@ export const blogPosts: BlogPost[] = [
description:
'QR code scan statistics 2026: key trends, adoption, and marketing insights. Use these stats to plan campaigns, tracking, and ROI.',
excerpt:
- "QR code scan statistics 2026 content is an authority builder. Marketers, founders, agencies, and journalists love numbers—especially when they're connected to actionable strategy.",
+ "QR code scan statistics 2026 content is an authority builder. Marketers, founders, agencies, and journalists love numbers-especially when they're connected to actionable strategy.",
category: 'Insights',
pillar: 'basics',
published: true,
@@ -2511,7 +2511,7 @@ export const blogPosts: BlogPost[] = [
},
{
question: 'Are scan counts enough to measure ROI?',
- answer: 'No—track conversions using UTMs + analytics.',
+ answer: 'No-track conversions using UTMs + analytics.',
},
{
question: "What's the best way to make a stats post link-worthy?",
@@ -2560,7 +2560,7 @@ export const blogPosts: BlogPost[] = [
QR Code Scan Statistics 2026: The Trends Marketers Should Know
This article is designed to be updated yearly. The main goal isn't just to list statistics, but to translate them into what to do next: placements, tracking, conversion tactics, and campaign planning.
- Note: I'm not browsing live sources inside this chat. Before publishing, pull 5–10 fresh stats from reliable reports (e.g., Statista, GS1, camera/OS adoption reports, marketing research firms) and replace the placeholder sections below with your numbers + citations.
+ Note: I'm not browsing live sources inside this chat. Before publishing, pull 5-10 fresh stats from reliable reports (e.g., Statista, GS1, camera/OS adoption reports, marketing research firms) and replace the placeholder sections below with your numbers + citations.
Why QR code statistics matter
QR codes sit at the intersection of:
@@ -2695,7 +2695,7 @@ export const blogPosts: BlogPost[] = [
'event marketing QR',
],
quickAnswer:
- 'The best event QR setup uses 3–5 distinct codes : one for operations (check-in/tickets), one for utility (agenda/map), and trackable codes for marketing (banners, flyers). Always use dynamic QR codes for printed materials so you can update the schedule or offers last-minute.
',
+ 'The best event QR setup uses 3-5 distinct codes : one for operations (check-in/tickets), one for utility (agenda/map), and trackable codes for marketing (banners, flyers). Always use dynamic QR codes for printed materials so you can update the schedule or offers last-minute.
',
keySteps: [
'Use dedicated unique QR codes for ticketing (secure validation).',
"Place 'Check-in' QRs at the entrance to reduce queues.",
@@ -2712,7 +2712,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'Should I use one QR code or multiple at an event?',
answer:
- 'Multiple—separate QRs per goal and placement (entrance vs booth vs flyers).',
+ 'Multiple-separate QRs per goal and placement (entrance vs booth vs flyers).',
},
{
question: 'How do I track which event placement performed best?',
@@ -2722,7 +2722,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'What size should event QR codes be?',
answer:
- 'Bigger than you think—people scan from distance. Test on-site before printing.',
+ 'Bigger than you think-people scan from distance. Test on-site before printing.',
},
{
question:
@@ -2775,7 +2775,7 @@ export const blogPosts: BlogPost[] = [
claim discounts
leave feedback
- This guide shows how to use event QR codes for both operations and marketing—with tracking so you can measure what actually worked.
+ This guide shows how to use event QR codes for both operations and marketing-with tracking so you can measure what actually worked.
1) Core event QR use cases
@@ -2862,7 +2862,7 @@ export const blogPosts: BlogPost[] = [
Internal link: Print Size .
5) The "perfect" event QR setup (simple blueprint)
- Use 3–5 QR codes max:
+ Use 3-5 QR codes max:
Check-in QR (operational)
Schedule/Map QR (utility)
@@ -2887,7 +2887,7 @@ export const blogPosts: BlogPost[] = [
slug: 'business-card-qr-code',
title: 'Business Card QR Codes: Design & Best Practices',
description:
- 'Not just a vCard—learn how to design and place QR codes on physical business cards effectively. Size, color, and CTA tips.',
+ 'Not just a vCard-learn how to design and place QR codes on physical business cards effectively. Size, color, and CTA tips.',
excerpt:
'Modernize your business card. Learn the design rules for adding a QR code without ruining the aesthetic. Spacing, size, and CTA guide.',
category: 'Business Cards',
@@ -2925,7 +2925,7 @@ export const blogPosts: BlogPost[] = [
},
{
question: 'What size should a QR code be on a business card?',
- answer: 'Roughly 2–3 cm wide; test with iPhone and Android.',
+ answer: 'Roughly 2-3 cm wide; test with iPhone and Android.',
},
{
question: 'Should I use a dynamic QR code on a business card?',
@@ -2938,7 +2938,7 @@ export const blogPosts: BlogPost[] = [
},
{
question: 'Can I track business card scans?',
- answer: 'Yes—use a trackable dynamic QR or a landing page with UTMs.',
+ answer: 'Yes-use a trackable dynamic QR or a landing page with UTMs.',
},
],
relatedSlugs: [
@@ -2974,7 +2974,7 @@ export const blogPosts: BlogPost[] = [
Business Card QR Codes: The Modern Networking Tool
- Business cards haven't disappeared—they've evolved. According to Bitly's research on digital business cards , adding a QR code to your physical business card increases engagement by 40% compared to cards without codes. The QR code bridges paper and digital, allowing instant contact saving, portfolio access, or booking with a single scan.
+ Business cards haven't disappeared-they've evolved. According to Bitly's research on digital business cards , adding a QR code to your physical business card increases engagement by 40% compared to cards without codes. The QR code bridges paper and digital, allowing instant contact saving, portfolio access, or booking with a single scan.
Design Tips
Place the QR code on the back of the card or in a clean corner. Ensure it is at least 2cm (0.8 inches) wide for reliable scanning at normal viewing distance. Add a clear CTA like "Scan to save contact" to encourage action.
`,
@@ -3081,7 +3081,7 @@ export const blogPosts: BlogPost[] = [
QR Code Marketing: How to Run Campaigns You Can Measure
- QR code marketing is no longer a gimmick. In 2026 it's a serious performance channel — if you treat it like one. According to Mordor Intelligence's market analysis , QR code marketing adoption continues to accelerate. The difference between a QR code that "looks nice" and a QR code that generates real revenue is simple: strategy + tracking.
+ QR code marketing is no longer a gimmick. In 2026 it's a serious performance channel - if you treat it like one. According to Mordor Intelligence's market analysis , QR code marketing adoption continues to accelerate. The difference between a QR code that "looks nice" and a QR code that generates real revenue is simple: strategy + tracking.
A QR code is just a bridge. The marketing happens in the details:
@@ -3090,7 +3090,7 @@ export const blogPosts: BlogPost[] = [
What page opens after the scan (and how fast it loads)
How you measure results (so you can optimize instead of guessing)
- This guide gives you a complete framework for QR code marketing — from campaign design to attribution.
+ This guide gives you a complete framework for QR code marketing - from campaign design to attribution.
1) The core QR code marketing loop
Think in four steps:
@@ -3161,7 +3161,7 @@ export const blogPosts: BlogPost[] = [
fix mistakes instantly
track scans per placement
- Static QR codes are fine for evergreen pages (like "About"), but campaigns change — offers end, pages get updated, links break.
+ Static QR codes are fine for evergreen pages (like "About"), but campaigns change - offers end, pages get updated, links break.
Internal link: Dynamic vs Static .
5) Tracking: scans are not enough
@@ -3211,7 +3211,7 @@ export const blogPosts: BlogPost[] = [
Wrap-up
- QR code marketing works when you combine a great offer with trackable execution. Treat QR like a measurable channel — dynamic codes, UTMs, conversion tracking, and clear CTAs — and you'll get campaigns that improve over time instead of staying "a nice poster."
+ QR code marketing works when you combine a great offer with trackable execution. Treat QR like a measurable channel - dynamic codes, UTMs, conversion tracking, and clear CTAs - and you'll get campaigns that improve over time instead of staying "a nice poster."
`,
},
@@ -3260,7 +3260,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'What is quishing?',
answer:
- 'QR phishing—scammers use QR codes to send users to fake login/payment pages.',
+ 'QR phishing-scammers use QR codes to send users to fake login/payment pages.',
},
{
question: 'How can users protect themselves from malicious QR codes?',
@@ -3275,7 +3275,7 @@ export const blogPosts: BlogPost[] = [
{
question: 'Are QR codes inherently unsafe?',
answer:
- 'No—risk comes from the destination link. Good practices reduce risk significantly.',
+ 'No-risk comes from the destination link. Good practices reduce risk significantly.',
},
{
question: 'Does QR tracking violate privacy/GDPR?',
@@ -3409,7 +3409,7 @@ export const blogPosts: BlogPost[] = [
For B2B trust, transparency beats "secret tracking." See our Analytics Guide for more on ethical tracking.
Wrap-up
- QR code security is no longer optional. Quishing attacks exploit the fact that QR codes hide their destination until after scanning. By using branded links, testing placements, adding transparent messaging, and following basic security hygiene, you protect users — and your QR brand becomes the trusted option.
+ QR code security is no longer optional. Quishing attacks exploit the fact that QR codes hide their destination until after scanning. By using branded links, testing placements, adding transparent messaging, and following basic security hygiene, you protect users - and your QR brand becomes the trusted option.
`,
},
@@ -3463,7 +3463,7 @@ export const blogPosts: BlogPost[] = [
},
{
question: 'Can I update a QR destination via API?',
- answer: 'Yes—dynamic QR codes support updating without reprinting.',
+ answer: 'Yes-dynamic QR codes support updating without reprinting.',
},
{
question: 'Does the API support bulk creation?',
@@ -3515,7 +3515,7 @@ export const blogPosts: BlogPost[] = [
QR Code API Documentation: Generate, Manage, and Track QR Codes
- A QR code API allows you to generate and manage QR codes programmatically — ideal for SaaS platforms, ticketing systems, CRMs, packaging workflows, and bulk marketing campaigns. According to REST API Best Practices documentation , a well-designed API is essential for seamless integration. Instead of creating QR codes manually, you can generate thousands of codes via requests, attach them to database records, and update destinations when campaigns change.
+ A QR code API allows you to generate and manage QR codes programmatically - ideal for SaaS platforms, ticketing systems, CRMs, packaging workflows, and bulk marketing campaigns. According to REST API Best Practices documentation , a well-designed API is essential for seamless integration. Instead of creating QR codes manually, you can generate thousands of codes via requests, attach them to database records, and update destinations when campaigns change.
This "docs light" page is designed to explain the API concepts: clear use cases, standard endpoints, and example flows.
@@ -3712,7 +3712,7 @@ Authorization: Bearer YOUR_API_KEY
{
question: 'Can free QR codes stop working?',
answer:
- 'Static codes won\'t "expire," but the destination can change or break—then you must reprint.',
+ 'Static codes won\'t "expire," but the destination can change or break-then you must reprint.',
},
{
question: 'When should I upgrade to paid?',
@@ -3760,7 +3760,7 @@ Authorization: Bearer YOUR_API_KEY
Free vs Paid QR Code Generator: When to Upgrade and Why
Choosing between a free vs paid QR code generator depends on what happens after you print or publish the code. According to Bitly's business adoption research , 87% of businesses using QR codes in campaigns require dynamic capabilities and tracking. If your QR code is permanent and you don't care about tracking, free tools can be enough. But if you run campaigns, need analytics, or want the flexibility to change the destination later, paid tools usually win.
- This guide breaks down the real differences so you can decide fast — and avoid the most expensive mistake in QR: printing a QR code you can't change.
+ This guide breaks down the real differences so you can decide fast - and avoid the most expensive mistake in QR: printing a QR code you can't change.
The biggest difference: static vs dynamic
Most free generators create static QR codes:
@@ -3840,7 +3840,7 @@ Authorization: Bearer YOUR_API_KEY
The hidden cost: reprinting
- The real cost isn't the subscription — it's reprinting.
+ The real cost isn't the subscription - it's reprinting.
If you print 5,000 flyers with a static QR and then:
the landing page changes
@@ -3865,7 +3865,7 @@ Authorization: Bearer YOUR_API_KEY
Internal link: Pricing .
Wrap-up
- The free vs paid QR code generator decision is mostly about control. Free tools work for simple static use. Paid tools win for dynamic, trackable, business-grade campaigns — where one broken QR can cost more than a year of subscription.
+ The free vs paid QR code generator decision is mostly about control. Free tools work for simple static use. Paid tools win for dynamic, trackable, business-grade campaigns - where one broken QR can cost more than a year of subscription.
`,
},
@@ -3898,7 +3898,7 @@ Authorization: Bearer YOUR_API_KEY
'free qr code generator',
],
quickAnswer:
- 'QR Master is the best free QR code generator in 2026 for businesses needing vector exports (SVG/EPS), UTM tracking for GA4, and no scan limits. Unlike competitors, it offers truly free dynamic QR codes with full analytics—no credit card required.
',
+ 'QR Master is the best free QR code generator in 2026 for businesses needing vector exports (SVG/EPS), UTM tracking for GA4, and no scan limits. Unlike competitors, it offers truly free dynamic QR codes with full analytics-no credit card required.
',
keySteps: [
'Check for Dynamic QR support (essential for editing later).',
'Verify tracking capabilities (scans, location, devices).',
@@ -3915,7 +3915,7 @@ Authorization: Bearer YOUR_API_KEY
{
question: 'Is there a truly free QR code generator with tracking?',
answer:
- 'Yes. QR Master offers free dynamic QR codes with unlimited scans, UTM parameters for GA4, and real-time analytics—no credit card required.',
+ 'Yes. QR Master offers free dynamic QR codes with unlimited scans, UTM parameters for GA4, and real-time analytics-no credit card required.',
},
{
question: "What's the best QR code format for print?",
@@ -3930,7 +3930,7 @@ Authorization: Bearer YOUR_API_KEY
{
question: 'Is the best generator the one with the most design options?',
answer:
- 'Not necessarily—tracking and reliability usually matter more for business.',
+ 'Not necessarily-tracking and reliability usually matter more for business.',
},
{
question: 'Do I need an API?',
@@ -3978,13 +3978,13 @@ Authorization: Bearer YOUR_API_KEY
Bottom line up front
-
QR Master is the best free option for businesses in 2026 — free dynamic QR codes, real scan analytics, SVG export, and no scan limits. No credit card required. Try it free →
+
QR Master is the best free option for businesses in 2026 - free dynamic QR codes, real scan analytics, SVG export, and no scan limits. No credit card required. Try it free →
Best QR Code Generator 2026: How to Choose the Right Tool
The best QR code generator in 2026 depends on one thing: what you need the QR code to do after it's printed. For casual use, almost any generator works. For marketing and business, the best tools share a core set of capabilities: dynamic QR codes, tracking, reliable redirects, branding, and management features.
- This guide gives you a side-by-side comparison of the top tools and a practical checklist — so you can pick the right one for your exact use case without having to sign up for five free trials first.
+ This guide gives you a side-by-side comparison of the top tools and a practical checklist - so you can pick the right one for your exact use case without having to sign up for five free trials first.
According to Statista , approx. 45% of shoppers scanned a QR code in the past month, while QR code generation jumped 238% from 2021-2023 (Uniqode). With this explosive growth, choosing the right generator is more important than ever.
@@ -4078,7 +4078,7 @@ Authorization: Bearer YOUR_API_KEY
Internal link: QR Code API .
If you only need a simple static QR
- You can use a free generator and keep it simple — but accept that you can't edit it later.
+ You can use a free generator and keep it simple - but accept that you can't edit it later.
Internal link: Free vs Paid .
2) The 2026 checklist: what "best" really means
@@ -4130,7 +4130,7 @@ Authorization: Bearer YOUR_API_KEY
If you need programmatic creation and updates, choose a provider that clearly documents API capabilities for dynamic QR codes and tracking.
Best for "free but serious"
- If you only need static codes, free tools can work — but always check resolution, usage rights, and whether the destination will never change. Zapier's guide mentions both business-focused and more design-focused options.
+ If you only need static codes, free tools can work - but always check resolution, usage rights, and whether the destination will never change. Zapier's guide mentions both business-focused and more design-focused options.
4) The "best generator" trap to avoid
The biggest mistake is selecting based on:
@@ -4164,7 +4164,7 @@ Authorization: Bearer YOUR_API_KEY
Wrap-up
- The best QR code generator 2026 is the one that matches your workflow: marketing attribution, design speed, API scalability, or simple static generation. Use the checklist above, choose by category, and you'll end up with a generator that fits your real use — not just a "top list."
+ The best QR code generator 2026 is the one that matches your workflow: marketing attribution, design speed, API scalability, or simple static generation. Use the checklist above, choose by category, and you'll end up with a generator that fits your real use - not just a "top list."
`,
},
];
diff --git a/src/lib/competitor-data.ts b/src/lib/competitor-data.ts
index 92eab6b..f6e8cf0 100644
--- a/src/lib/competitor-data.ts
+++ b/src/lib/competitor-data.ts
@@ -1,4 +1,4 @@
-// Centralized competitor data — single source of truth for all comparison pages.
+// 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 = {
@@ -31,9 +31,9 @@ export type CompetitorProfile = {
};
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',
+ 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 = {
@@ -45,12 +45,12 @@ export const competitors: Record = {
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',
+ 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',
+ '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',
@@ -58,10 +58,10 @@ export const competitors: Record = {
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',
+ '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',
+ 'US-based servers - GDPR compliance not a priority',
],
bestFor: [
'One-off static QR code generation with no analytics needed',
@@ -75,18 +75,18 @@ export const competitors: Record = {
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"',
+ '"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.',
+ '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',
+ 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' },
@@ -110,23 +110,23 @@ export const competitors: Record = {
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',
+ 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',
+ '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',
+ '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',
+ 'US company - GDPR compliance not built in',
'Pricing escalates quickly for teams',
],
bestFor: [
@@ -142,12 +142,12 @@ export const competitors: Record = {
],
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"',
+ '"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.',
+ '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' },
{
@@ -158,13 +158,14 @@ export const competitors: Record = {
},
{
feature: 'No branded interstitial page on scan',
- qrMaster: 'Fast pass-through — no Flowcode-style marketing page',
+ 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: 'Brand colors', qrMaster: 'Free', competitor: 'Paid plans' },
+ { feature: 'Module shapes and logo', qrMaster: 'Pro+', competitor: 'Paid plans' },
{ feature: 'Scan analytics', qrMaster: 'All plans', competitor: 'Paid plans' },
{
feature: 'Pro pricing',
@@ -178,17 +179,17 @@ export const competitors: Record = {
id: 'beaconstac',
name: 'Uniqode (formerly Beaconstac)',
url: 'https://www.uniqode.com',
- tagline: 'Enterprise QR platform — excellent for large orgs, overkill for SMBs',
+ 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',
+ 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',
+ 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',
+ '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',
@@ -196,10 +197,10 @@ export const competitors: Record = {
'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+',
+ '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',
+ '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',
],
@@ -216,24 +217,24 @@ export const competitors: Record = {
'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"',
+ '"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.',
+ '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',
+ 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: '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 },
],
@@ -243,25 +244,25 @@ export const competitors: Record = {
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',
+ 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',
+ 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',
+ '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',
+ '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',
@@ -269,7 +270,7 @@ export const competitors: Record = {
'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',
+ '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',
],
@@ -281,13 +282,13 @@ export const competitors: Record = {
'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"',
+ '"$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.',
+ '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' },
@@ -297,8 +298,8 @@ export const competitors: Record = {
{ 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',
+ qrMaster: '€9/month - 50 dynamic QRs, full analytics',
+ competitor: '~$10/month - 2 QR codes only',
},
],
},
diff --git a/src/lib/email.ts b/src/lib/email.ts
index ae297a0..577227d 100644
--- a/src/lib/email.ts
+++ b/src/lib/email.ts
@@ -154,7 +154,7 @@ export async function sendPasswordResetEmail(email: string, resetToken: string)
Secure QR Code Analytics & Management
- © 2026 QR Master. All rights reserved.
+ © 2026 QR Master. All rights reserved.
This is an automated security email. Please do not reply.
@@ -287,7 +287,7 @@ export async function sendNewsletterWelcomeEmail(email: string) {
⏰ When Will This Happen?
- We're in active development and testing. You'll get an email the moment these features go live – no waiting, no wondering . We respect your inbox.
+ We're in active development and testing. You'll get an email the moment these features go live - no waiting, no wondering . We respect your inbox.
@@ -297,7 +297,7 @@ export async function sendNewsletterWelcomeEmail(email: string) {
💡 Want to Get Started Now?
- Our current platform already helps teams create, track, and manage dynamic QR codes. No AI needed – just powerful tools that work today.
+ Our current platform already helps teams create, track, and manage dynamic QR codes. No AI needed - just powerful tools that work today.
🚀 Try QR Master Free →
@@ -310,7 +310,7 @@ export async function sendNewsletterWelcomeEmail(email: string) {
- — Timo 👋
+ - Timo 👋
Founder, QR Master
@@ -326,7 +326,7 @@ export async function sendNewsletterWelcomeEmail(email: string) {
www.qrmaster.net
- © 2026 QR Master. All rights reserved.
+ © 2026 QR Master. All rights reserved.
You're receiving this because you signed up for AI feature notifications.
@@ -461,7 +461,7 @@ export async function sendAIFeatureLaunchEmail(email: string) {
🔐 Log in to your QR Master account
👀 Look for the "✨ AI" badge on supported features
- ✏️ Try creating a QR code – you'll see AI suggestions automatically
+ ✏️ Try creating a QR code - you'll see AI suggestions automatically
📈 Check your Analytics tab for the new AI query interface
@@ -483,10 +483,10 @@ export async function sendAIFeatureLaunchEmail(email: string) {
- 💬 We're excited to see what you build with these new tools. If you have questions, ideas, or just want to share what you created – hit reply . I read every email.
+ 💬 We're excited to see what you build with these new tools. If you have questions, ideas, or just want to share what you created - hit reply . I read every email.
- — Timo 👋
+ - Timo 👋
Founder, QR Master
@@ -506,7 +506,7 @@ export async function sendAIFeatureLaunchEmail(email: string) {
Help
- © 2026 QR Master. All rights reserved.
+ © 2026 QR Master. All rights reserved.
You received this because you subscribed to AI feature launch notifications.
@@ -535,29 +535,29 @@ export async function sendAIFeatureLaunchEmail(email: string) {
}
// ---------------------------------------------------------------------------
-// SMTP Transport (nodemailer) — used for retention / welcome emails
+// SMTP Transport (nodemailer) - used for retention / welcome emails
// ---------------------------------------------------------------------------
-function createSmtpTransport() {
- const host = process.env.SMTP_HOST || 'smtp.qrmaster.net';
- const port = parseInt(process.env.SMTP_PORT || '465', 10);
- const user = process.env.SMTP_USER || 'timo@qrmaster.net';
- const pass = process.env.SMTP_PASS?.trim();
-
- if (!pass) {
- throw new Error('SMTP_PASS is missing. Configure SMTP credentials before sending welcome or retention emails.');
- }
-
- return nodemailer.createTransport({
- host,
- port,
- secure: port === 465,
- auth: {
- user,
- pass,
- },
- });
-}
+function createSmtpTransport() {
+ const host = process.env.SMTP_HOST || 'smtp.qrmaster.net';
+ const port = parseInt(process.env.SMTP_PORT || '465', 10);
+ const user = process.env.SMTP_USER || 'timo@qrmaster.net';
+ const pass = process.env.SMTP_PASS?.trim();
+
+ if (!pass) {
+ throw new Error('SMTP_PASS is missing. Configure SMTP credentials before sending welcome or retention emails.');
+ }
+
+ return nodemailer.createTransport({
+ host,
+ port,
+ secure: port === 465,
+ auth: {
+ user,
+ pass,
+ },
+ });
+}
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://www.qrmaster.net';
@@ -638,7 +638,7 @@ function emailShell(headExtra: string, bodyContent: string): string {
const dotGridPattern = `url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%221.5%22%20fill%3D%22%23C8A257%22%20fill-opacity%3D%220.18%22%2F%3E%3C%2Fsvg%3E)`;
/**
- * Welcome Email — sent immediately on signup (Day 0)
+ * Welcome Email - sent immediately on signup (Day 0)
*/
export async function sendWelcomeEmail(email: string, name: string) {
const transport = createSmtpTransport();
@@ -797,7 +797,7 @@ export async function sendWelcomeEmail(email: string, name: string) {
- I'm thrilled to have you on board. If you have any questions, feedback, or just want to share what you've created — hit reply. I'm reading every single email.
+ I'm thrilled to have you on board. If you have any questions, feedback, or just want to share what you've created - hit reply. I'm reading every single email.
@@ -806,7 +806,7 @@ export async function sendWelcomeEmail(email: string, name: string) {
-
+
@@ -838,7 +838,7 @@ export async function sendWelcomeEmail(email: string, name: string) {
}
/**
- * Activation Nudge — sent on Day 3 if user has 0 QR codes
+ * Activation Nudge - sent on Day 3 if user has 0 QR codes
*/
export async function sendActivationNudgeEmail(email: string, name: string) {
const transport = createSmtpTransport();
@@ -847,7 +847,7 @@ export async function sendActivationNudgeEmail(email: string, name: string) {
const steps = [
{ n: '01', label: 'Paste your URL', sub: 'or choose WiFi, vCard, Teams, and more' },
- { n: '02', label: 'Customize the design', sub: 'colors, logo, frame label — optional' },
+ { n: '02', label: 'Customize the design', sub: 'colors, logo, frame label - optional' },
{ n: '03', label: 'Download & use', sub: 'PNG for screen, SVG for print' },
];
@@ -905,7 +905,7 @@ export async function sendActivationNudgeEmail(email: string, name: string) {
- Your 3 free dynamic QR codes are still there. Unused.
+ Your 3 free dynamic QR codes are still there. Unused.
Dynamic means: one code, update the link anytime, every scan tracked.
- ${qrCount} of 3 free codes used,
+ You are out of slots,
${firstName}.
- Free plan · Day 7
+ Free plan · ${qrCount} of 3 dynamic codes active
@@ -1055,11 +1055,11 @@ export async function sendUpgradeNudgeEmail(email: string, name: string, qrCount
- The free plan gives you 3 dynamic QR codes. That's enough to start — not enough to scale. When you hit the limit, every new campaign means replacing an old one.
+ Three codes is enough to prove the idea. It is not enough to run two campaigns at once - from here, every new placement means switching off an old one.
- Pro removes the ceiling — and adds custom branding your free codes never have:
+ Pro raises it to 50, and shows you which placement actually earned the scan:
@@ -1113,7 +1113,7 @@ export async function sendUpgradeNudgeEmail(email: string, name: string, qrCount
Your free plan stays active — no pressure
+ color:${clr.textMuted};">Your free plan stays active - no pressure
@@ -1140,15 +1140,20 @@ export async function sendUpgradeNudgeEmail(email: string, name: string, qrCount
from: 'Timo from QR Master ',
replyTo: 'support@qrmaster.net',
to: email,
- subject: `You're ${qrCount >= 3 ? 'at' : `${3 - qrCount} away from`} the free limit`,
+ subject: 'You just hit the free limit',
html,
});
}
/**
- * 30-Day Nudge — sent on Day 30 if user has ≥1 QR code and is still on FREE plan
+ * 30-Day Nudge - sent on Day 30 if user has ≥1 QR code and is still on FREE plan
*/
-export async function sendThirtyDayNudgeEmail(email: string, name: string, qrCount: number) {
+export async function sendThirtyDayNudgeEmail(
+ email: string,
+ name: string,
+ qrCount: number,
+ scanCount: number = 0
+) {
const transport = createSmtpTransport();
const pricingUrl = `${appUrl}/pricing`;
const firstName = name.split(' ')[0];
@@ -1187,15 +1192,15 @@ export async function sendThirtyDayNudgeEmail(email: string, name: string, qrCou
- You've created ${qrCount} QR code${qrCount !== 1 ? 's' : ''} in your first month. That tells me you're actually using this — not just signing up to forget it.
+ Your codes were scanned ${scanCount} time${scanCount !== 1 ? 's' : ''} this month, across ${qrCount} code${qrCount !== 1 ? 's' : ''}. Someone stood in front of your printed material and used it.
- The one thing I hear most from Pro users who switched after a few weeks: they wish they'd added their brand sooner. Every code they printed on free didn't have their logo. Every flyer had a generic black pattern instead of their colors.
+ What you cannot see yet is which of them . The free plan gives you a running total. It does not tell you which placement, which city, or which kind of device brought those scans in.
- Pro fixes that. Two things it unlocks that free never will:
+ That is the difference between knowing a campaign worked and knowing which part of it did:
@@ -1222,10 +1227,10 @@ export async function sendThirtyDayNudgeEmail(email: string, name: string, qrCou
Your logo, inside every QR code
+ font-size:15px;font-weight:600;color:${clr.text};letter-spacing:-0.2px;">Which placement earned the scan
- Upload once. Every code you make automatically carries your brand mark — menus, flyers, packaging, wherever.
+ One code per flyer, poster or table tent, and the dashboard shows you which one people actually used. The others you can stop printing.
@@ -1252,10 +1257,10 @@ export async function sendThirtyDayNudgeEmail(email: string, name: string, qrCou
Brand colors — not just black
+ font-size:15px;font-weight:600;color:${clr.text};letter-spacing:-0.2px;">Device and location, not just a total
- Match your QR codes to your brand palette. Looks intentional. Scans the same.
+ See whether scans came from phones at the venue or desktops later, and which cities they came from. Enough to decide where the next print run goes.
@@ -1277,14 +1282,14 @@ export async function sendThirtyDayNudgeEmail(email: string, name: string, qrCou
text-decoration:none;padding:18px 52px;border-radius:10px;
font-family:'DM Sans',-apple-system,sans-serif;font-size:15px;
font-weight:600;letter-spacing:0.3px;border:1px solid rgba(200,162,87,0.3);">
- Add my brand to QR codes →
+ See the full breakdown →
Your existing codes keep working — nothing breaks
+ color:${clr.textMuted};">Your existing codes and their scan history stay exactly as they are
@@ -1311,7 +1316,122 @@ export async function sendThirtyDayNudgeEmail(email: string, name: string, qrCou
from: 'Timo from QR Master
',
replyTo: 'support@qrmaster.net',
to: email,
- subject: `${firstName}, a month of QR codes — one upgrade worth making`,
+ subject: `${firstName}, your codes were scanned ${scanCount} time${scanCount !== 1 ? 's' : ''} this month`,
+ html,
+ });
+}
+
+/**
+ * First Scan - sent the day after a user's first ever scan.
+ *
+ * The only email in the sequence whose trigger is not the calendar. Everything
+ * else fires on day 3, 7 or 30 whether or not anything happened; this one fires
+ * because something did. It is also the moment the positioning first pays off -
+ * a printed thing became a measurement - so it deliberately sells nothing. The
+ * CTA goes to the user's own data, not to pricing.
+ */
+export async function sendFirstScanEmail(
+ email: string,
+ name: string,
+ scan: { qrTitle: string; device?: string | null; country?: string | null; ts: Date }
+) {
+ const transport = createSmtpTransport();
+ const firstName = name.split(' ')[0];
+ const analyticsUrl = `${appUrl}/analytics`;
+
+ const time = scan.ts.toLocaleTimeString('en-GB', {
+ hour: '2-digit',
+ minute: '2-digit',
+ timeZone: 'UTC',
+ });
+
+ // Only state what was actually recorded. Device and country can be missing,
+ // and inventing a clean sentence around absent data is exactly the habit this
+ // product is positioned against.
+ const details = [
+ `at ${time} UTC`,
+ scan.device ? `on ${scan.device.toLowerCase()}` : null,
+ scan.country ? `from ${scan.country}` : null,
+ ]
+ .filter(Boolean)
+ .join(', ');
+
+ const html = emailShell('', `
+
+
+ QR Master
+
+ Someone scanned it,
+ ${firstName}.
+
+
+ First scan · ${scan.qrTitle}
+
+
+
+
+
+
+
+ Your code "${scan.qrTitle}" was scanned for the first time ${details}.
+
+
+ Somebody stood in front of something you made and used it. That is the
+ part a static code can never tell you.
+
+
+ Every scan from here on is counted. In a week or two you will be able to
+ see when they cluster - and whether the spot you chose was the right one.
+
+
+
+
+
+
+
+
+ If the destination needs to change, you can edit it without touching the
+ printed code. That is what it is for.
+
+
+ Timo
+
+
+ Founder, QR Master
+
+
+
+ `);
+
+ await transport.sendMail({
+ from: 'Timo from QR Master ',
+ replyTo: 'support@qrmaster.net',
+ to: email,
+ subject: 'Your QR code was just scanned for the first time',
html,
});
}
diff --git a/src/lib/growth-pages-de.ts b/src/lib/growth-pages-de.ts
index e34a3ed..8b60ba0 100644
--- a/src/lib/growth-pages-de.ts
+++ b/src/lib/growth-pages-de.ts
@@ -55,7 +55,7 @@ export const useCasePagesDe: Record = {
eyebrow: 'Vor dem Druck entscheiden',
titleSuffix: 'vor dem Druck',
metaDescription:
- 'Statischer oder dynamischer QR-Code? Vergleichen Sie Änderbarkeit, Druckrisiko und Tracking – und entscheiden Sie vor Flyer, Speisekarte oder Tischaufsteller.',
+ 'Statischer oder dynamischer QR-Code? Vergleichen Sie Änderbarkeit, Druckrisiko und Tracking - und entscheiden Sie vor Flyer, Speisekarte oder Tischaufsteller.',
intro:
'Bleibt Ihr Link garantiert unverändert, reicht ein statischer QR-Code. Können sich PDF, Menü, Angebot oder Zielseite später ändern, bleibt ein dynamischer QR-Code nach dem Druck flexibel.',
answer:
@@ -158,7 +158,7 @@ export const useCasePagesDe: Record = {
authoritySignals: [
'Klarer Vergleich vor der Druckfreigabe statt Technik-Jargon.',
'Konkrete Beispiele für Menüs, PDFs, Flyer und Tischaufsteller.',
- 'Keine pauschalen Einsparversprechen – prüfen Sie Ihren eigenen Neudruck-Aufwand.',
+ 'Keine pauschalen Einsparversprechen - prüfen Sie Ihren eigenen Neudruck-Aufwand.',
],
},
@@ -169,18 +169,18 @@ export const useCasePagesDe: Record = {
title: 'QR-Codes für Fitnessstudios',
cluster: 'gyms',
summary:
- 'Verknüpfen Sie Trainingsgeräte mit Video-Tutorials, Kursplänen oder Probetrainings – ohne die Aufkleber auf jedem Gerät neu zu drucken.',
+ 'Verknüpfen Sie Trainingsgeräte mit Video-Tutorials, Kursplänen oder Probetrainings - ohne die Aufkleber auf jedem Gerät neu zu drucken.',
parentHref: '/dynamic-qr-code-generator',
parentTitle: 'Dynamischer QR-Code-Generator',
ctaLabel: 'QR-Code fürs Fitnessstudio erstellen',
eyebrow: 'Fitnessstudios',
titleSuffix: 'für Geräte-Tutorials, Kurspläne und Probetrainings',
metaDescription:
- 'Mit QR-Codes im Fitnessstudio verlinken Sie Geräte mit Video-Anleitungen, Kursplänen und Probetraining-Anmeldungen – ohne Aufkleber auszutauschen.',
+ 'Mit QR-Codes im Fitnessstudio verlinken Sie Geräte mit Video-Anleitungen, Kursplänen und Probetraining-Anmeldungen - ohne Aufkleber auszutauschen.',
intro:
'QR-Codes im Fitnessstudio funktionieren am besten, wenn die robusten Aufkleber auf den Geräten dauerhaft bleiben, während die verlinkten Tutorial-Videos oder Kurspläne jederzeit aktualisiert werden können.',
answer:
- 'Ein QR-Code im Fitnessstudio sollte Mitglieder zu einem Anleitungsvideo für genau das Gerät führen, an dem sie gerade trainieren – oder Interessenten direkt in eine mobile Anmeldung fürs Probetraining leiten.',
+ 'Ein QR-Code im Fitnessstudio sollte Mitglieder zu einem Anleitungsvideo für genau das Gerät führen, an dem sie gerade trainieren - oder Interessenten direkt in eine mobile Anmeldung fürs Probetraining leiten.',
whenToUse: [
'Sie möchten Video-Anleitungen für komplexe Geräte bereitstellen.',
'Ihre Kurspläne ändern sich wöchentlich oder saisonal.',
@@ -247,7 +247,7 @@ export const useCasePagesDe: Record = {
{
question: 'Wie verhindere ich, dass Mitglieder QR-Aufkleber von den Geräten abziehen?',
answer:
- 'Verwenden Sie strapazierfähige Vinyl-Aufkleber in Industriequalität mit starkem Kleber. Bringen Sie sie auf flachen Metallflächen an – abseits der Stellen, an denen Mitglieder die Geräte greifen. Polycarbonat-Schutzfolien verhindern zusätzlich Ablösen und Verkratzen.',
+ 'Verwenden Sie strapazierfähige Vinyl-Aufkleber in Industriequalität mit starkem Kleber. Bringen Sie sie auf flachen Metallflächen an - abseits der Stellen, an denen Mitglieder die Geräte greifen. Polycarbonat-Schutzfolien verhindern zusätzlich Ablösen und Verkratzen.',
},
{
question: 'Sollten QR-Codes an Geräten auf YouTube oder einen eigenen Videoplayer verlinken?',
@@ -257,7 +257,7 @@ export const useCasePagesDe: Record = {
{
question: 'Kann ich mit QR-Codes messen, welche Geräte am beliebtesten sind?',
answer:
- 'Ja! Mit einem eigenen dynamischen QR-Code pro Gerät sehen Sie die Scan-Häufigkeit im Dashboard. Hohe Scan-Raten deuten auf hohe Nutzung hin – eine gute Entscheidungsgrundlage, welche Geräte Sie künftig zusätzlich anschaffen.',
+ 'Ja! Mit einem eigenen dynamischen QR-Code pro Gerät sehen Sie die Scan-Häufigkeit im Dashboard. Hohe Scan-Raten deuten auf hohe Nutzung hin - eine gute Entscheidungsgrundlage, welche Geräte Sie künftig zusätzlich anschaffen.',
},
{
question: 'Brauche ich separate QR-Codes für iOS- und Android-Nutzer, um meine Studio-App zu verteilen?',
@@ -293,7 +293,7 @@ export const useCasePagesDe: Record = {
eyebrow: 'Events & Veranstaltungen',
titleSuffix: 'für Check-in, Programm, Messestände und Kampagnen-Tracking',
metaDescription:
- 'Event-QR-Codes für Programm, Check-in und messbare Platzierungen auf Schildern, Flyern und Messeständen – jederzeit aktualisierbar.',
+ 'Event-QR-Codes für Programm, Check-in und messbare Platzierungen auf Schildern, Flyern und Messeständen - jederzeit aktualisierbar.',
intro:
'QR-Codes für Events funktionieren am besten, wenn Sie operative QR-Flows von werblichen trennen und Ihre gedruckten Platzierungen einfach verwaltbar halten.',
answer:
@@ -330,7 +330,7 @@ export const useCasePagesDe: Record = {
{
title: 'Follow-up nach dem Event',
description:
- 'Leiten Sie Scanner nach dem Event auf die passende Recap-, Buchungs- oder Lead-Seite um – ohne die gedruckten Materialien anzufassen.',
+ 'Leiten Sie Scanner nach dem Event auf die passende Recap-, Buchungs- oder Lead-Seite um - ohne die gedruckten Materialien anzufassen.',
},
],
checklistTitle: 'Event-QR-Checkliste',
@@ -363,14 +363,14 @@ export const useCasePagesDe: Record = {
href: '/tools/teams-qr-code',
title: 'Teams-QR-Code-Generator',
description:
- 'Kostenloses Tool – Teilnehmer treten per Scan direkt dem Teams-Meeting oder Webinar bei.',
+ 'Kostenloses Tool - Teilnehmer treten per Scan direkt dem Teams-Meeting oder Webinar bei.',
},
],
faq: [
{
question: 'Wie verhindern dynamische QR-Codes ein Desaster, wenn sich unser Event-Programm ändert?',
answer:
- 'Wenn ein Speaker absagt oder sich ein Raum ändert, nachdem Sie 5.000 Teilnehmer-Badges gedruckt haben, rettet der dynamische QR-Code den Tag: Sie aktualisieren einfach die Programm-URL im Dashboard – und jedes gedruckte Badge zeigt sofort auf die neuen Informationen.',
+ 'Wenn ein Speaker absagt oder sich ein Raum ändert, nachdem Sie 5.000 Teilnehmer-Badges gedruckt haben, rettet der dynamische QR-Code den Tag: Sie aktualisieren einfach die Programm-URL im Dashboard - und jedes gedruckte Badge zeigt sofort auf die neuen Informationen.',
},
{
question: 'Wie erzeuge ich einzigartige QR-Codes für tausende Teilnehmer-Badges?',
@@ -415,7 +415,7 @@ export const useCasePagesDe: Record = {
intro:
'QR-Codes auf Immobilienschildern sind am stärksten, wenn der Objekt-Traffic aktuell bleibt, auch wenn sich Angebot, Besichtigungstermine oder Broschüren-Links ändern.',
answer:
- 'Ein QR-Code am Immobilienschild sollte Passanten zum heute richtigen Exposé, zur Broschüre oder zum Anfrage-Formular führen – mit genug Tracking-Kontext, um die Performance der Schilder über die Zeit zu vergleichen.',
+ 'Ein QR-Code am Immobilienschild sollte Passanten zum heute richtigen Exposé, zur Broschüre oder zum Anfrage-Formular führen - mit genug Tracking-Kontext, um die Performance der Schilder über die Zeit zu vergleichen.',
whenToUse: [
'Schilder, Schaufenster-Aushänge oder Objekt-Broschüren sollen auf aktuelle Objektinformationen zeigen.',
'Sie möchten Scans von Schildern, Flyern, Besichtigungen und Makler-Materialien vergleichen.',
@@ -427,9 +427,9 @@ export const useCasePagesDe: Record = {
{ label: 'Objekt-Routing', text: 'Eine fixe Seite', value: true },
],
howToSteps: [
- 'Verbinden Sie jeden Schild-QR mit dem passenden Exposé, der Broschüre oder dem Lead-Formular – nicht mit einer generischen Startseite.',
+ 'Verbinden Sie jeden Schild-QR mit dem passenden Exposé, der Broschüre oder dem Lead-Formular - nicht mit einer generischen Startseite.',
'Nutzen Sie trackbares Routing, wenn dasselbe Objekt auf Schildern, Flyern und Besichtigungsunterlagen erscheint.',
- 'Ändern Sie das Ziel, wenn sich Objektstatus, CTA oder Follow-up-Prozess ändern – ohne gedruckte Schilder zu ersetzen.',
+ 'Ändern Sie das Ziel, wenn sich Objektstatus, CTA oder Follow-up-Prozess ändern - ohne gedruckte Schilder zu ersetzen.',
],
workflowTitle: 'Wo QR-Systeme für Immobilien wirklich nützlich werden',
workflowIntro:
@@ -448,7 +448,7 @@ export const useCasePagesDe: Record = {
{
title: 'Lead-fähige Übergabe',
description:
- 'Leiten Sie Scanner auf das aktuelle Objekt-Hub, die Broschüren-Anforderung oder die Terminanfrage – statt auf einen veralteten Exposé-Schnappschuss.',
+ 'Leiten Sie Scanner auf das aktuelle Objekt-Hub, die Broschüren-Anforderung oder die Terminanfrage - statt auf einen veralteten Exposé-Schnappschuss.',
},
],
checklistTitle: 'QR-Checkliste für Immobilienschilder',
@@ -487,7 +487,7 @@ export const useCasePagesDe: Record = {
{
question: 'Wie groß sollte ein QR-Code auf einem Immobilienschild sein, damit er aus dem Auto scanbar ist?',
answer:
- 'Für einen Scan aus einem am Straßenrand geparkten Auto (ca. 3–5 Meter Entfernung) sollte der QR-Code mindestens 10x10 cm groß sein. Achten Sie auf hohen Kontrast, z.B. schwarzer Code auf weißem Grund.',
+ 'Für einen Scan aus einem am Straßenrand geparkten Auto (ca. 3-5 Meter Entfernung) sollte der QR-Code mindestens 10x10 cm groß sein. Achten Sie auf hohen Kontrast, z.B. schwarzer Code auf weißem Grund.',
},
{
question: 'Was ist das beste Ziel für einen QR-Code bei Besichtigungsterminen?',
@@ -507,7 +507,7 @@ export const useCasePagesDe: Record = {
{
question: 'Kann ich auswerten, welche Lagen das meiste Interesse erzeugen?',
answer:
- 'Absolut. Wenn Sie für Schilder in verschiedenen Vierteln unterschiedliche dynamische QR-Codes verwenden, zeigt Ihnen das Analytics-Dashboard genau, welche Schilder am häufigsten gescannt werden – und wo sich Ihr Marketing lohnt.',
+ 'Absolut. Wenn Sie für Schilder in verschiedenen Vierteln unterschiedliche dynamische QR-Codes verwenden, zeigt Ihnen das Analytics-Dashboard genau, welche Schilder am häufigsten gescannt werden - und wo sich Ihr Marketing lohnt.',
},
],
heroImage: '/marketing/use-cases/real-estate-sign-qr-codes.png',
@@ -521,14 +521,14 @@ export const useCasePagesDe: Record = {
title: 'QR-Codes für Hotels',
cluster: 'hotels',
summary:
- 'Führen Sie Gäste zu aktuellen Roomservice-Menüs, Check-in-Prozessen oder lokalen Empfehlungen – ohne Zimmerkarten neu zu drucken.',
+ 'Führen Sie Gäste zu aktuellen Roomservice-Menüs, Check-in-Prozessen oder lokalen Empfehlungen - ohne Zimmerkarten neu zu drucken.',
parentHref: '/dynamic-qr-code-generator',
parentTitle: 'Dynamischer QR-Code-Generator',
ctaLabel: 'Hotel-QR-Code erstellen',
eyebrow: 'Hotels',
titleSuffix: 'für Gäste-Services, Roomservice und Check-in',
metaDescription:
- 'Mit Hotel-QR-Codes leiten Sie Gäste zu aktuellen Menüs, Check-in-Prozessen oder lokalen Empfehlungen – ohne Karten neu zu drucken.',
+ 'Mit Hotel-QR-Codes leiten Sie Gäste zu aktuellen Menüs, Check-in-Prozessen oder lokalen Empfehlungen - ohne Karten neu zu drucken.',
intro:
'Willkommens-QR-Codes im Hotel funktionieren am besten, wenn die Karten für Gäste an ihrem Platz bleiben, während sich das Ziel dahinter aktualisieren lässt.',
answer:
@@ -594,7 +594,7 @@ export const useCasePagesDe: Record = {
{
href: '/tools/wifi-qr-code',
title: 'WLAN-QR-Code-Generator',
- description: 'Kostenloses Tool – Gäste verbinden sich ohne Passwort-Tippen mit dem Hotel-WLAN.',
+ description: 'Kostenloses Tool - Gäste verbinden sich ohne Passwort-Tippen mit dem Hotel-WLAN.',
},
],
faq: [
@@ -611,7 +611,7 @@ export const useCasePagesDe: Record = {
{
question: 'Kann ich denselben Hotel-QR-Code an mehreren Stellen verwenden?',
answer:
- 'Ja. Ein Hotel-QR-Code lässt sich in Zimmern, an der Rezeption, auf Flyern und Mitnahme-Materialien wiederverwenden – besonders, wenn das Ziel dynamisch verwaltet wird.',
+ 'Ja. Ein Hotel-QR-Code lässt sich in Zimmern, an der Rezeption, auf Flyern und Mitnahme-Materialien wiederverwenden - besonders, wenn das Ziel dynamisch verwaltet wird.',
},
],
heroImage: '/marketing/use-cases/qr-codes-for-hotel.png',
@@ -625,18 +625,18 @@ export const useCasePagesDe: Record = {
title: 'QR-Codes für Friseure & Barbershops',
cluster: 'barbershops',
summary:
- 'Ein stabiler QR-Code für Terminbuchung, Warteliste und Treueangebote – während der Druck aktuell bleibt.',
+ 'Ein stabiler QR-Code für Terminbuchung, Warteliste und Treueangebote - während der Druck aktuell bleibt.',
parentHref: '/dynamic-qr-code-generator',
parentTitle: 'Dynamischer QR-Code-Generator',
ctaLabel: 'QR-Code für Ihren Salon erstellen',
eyebrow: 'Friseure & Barbershops',
titleSuffix: 'für Terminbuchung, Warteliste und Treueangebote',
metaDescription:
- 'Mit QR-Codes im Friseursalon leiten Sie Kunden zu Buchungsseiten, Wartelisten und Treueangeboten – ohne gedruckte Karten zu ersetzen.',
+ 'Mit QR-Codes im Friseursalon leiten Sie Kunden zu Buchungsseiten, Wartelisten und Treueangeboten - ohne gedruckte Karten zu ersetzen.',
intro:
'QR-Codes im Friseursalon funktionieren am besten, wenn der Code an Stühlen, Spiegeln und Theke fix bleibt, während sich das Buchungs- oder Treueziel dahinter ändert.',
answer:
- 'Ein Friseur-QR-Code sollte Kunden zur aktuellen Terminbuchung, Warteliste oder zum Treueprogramm führen – aktualisierbar, ohne gedruckte Karten zu ersetzen.',
+ 'Ein Friseur-QR-Code sollte Kunden zur aktuellen Terminbuchung, Warteliste oder zum Treueprogramm führen - aktualisierbar, ohne gedruckte Karten zu ersetzen.',
whenToUse: [
'Buchungsseite, Warteliste oder Treueangebot ändern sich häufiger als Ihre gedruckten Karten.',
'Ein QR-Code soll an Stühlen, Theke, Empfang und auf Follow-up-Karten funktionieren.',
@@ -659,7 +659,7 @@ export const useCasePagesDe: Record = {
{
title: 'Aktuelle Terminbuchung',
description:
- 'Leiten Sie Scanner auf die aktuelle Buchungs- oder Wartelisten-Seite – der nächste Schritt ist die Terminbestätigung, nicht der Rückruf.',
+ 'Leiten Sie Scanner auf die aktuelle Buchungs- oder Wartelisten-Seite - der nächste Schritt ist die Terminbestätigung, nicht der Rückruf.',
},
{
title: 'Angebots-Updates',
@@ -675,7 +675,7 @@ export const useCasePagesDe: Record = {
checklistTitle: 'Salon-QR-Checkliste',
checklist: [
'Wählen Sie eine klare Kundenaktion nach dem Scan, damit der Zweck des QR-Codes eindeutig bleibt.',
- 'Machen Sie die Buchungsseite mobil-optimiert – die meisten Scans passieren am Stuhl oder an der Theke.',
+ 'Machen Sie die Buchungsseite mobil-optimiert - die meisten Scans passieren am Stuhl oder an der Theke.',
"Verwenden Sie CTA-Texte wie 'Scannen zum Buchen' oder 'Scannen für aktuelle Angebote'.",
'Testen Sie Platzierung und Druckgröße, bevor Sie eine große Auflage bestellen.',
],
@@ -729,18 +729,18 @@ export const useCasePagesDe: Record = {
title: 'QR-Codes für Visitenkarten',
cluster: 'business-cards',
summary:
- 'Führen Sie Kontakte zu einem aktuellen Profil, einer Buchungsseite oder vCard – ohne Karten neu zu drucken.',
+ 'Führen Sie Kontakte zu einem aktuellen Profil, einer Buchungsseite oder vCard - ohne Karten neu zu drucken.',
parentHref: '/dynamic-qr-code-generator',
parentTitle: 'Dynamischer QR-Code-Generator',
ctaLabel: 'Visitenkarten-QR-Code erstellen',
eyebrow: 'Visitenkarten',
titleSuffix: 'zum Kontakte-Teilen, für Terminbuchung und Portfolio-Links',
metaDescription:
- 'Mit QR-Codes auf Visitenkarten teilen Sie eine aktuelle Kontaktseite, vCard, Buchungsseite oder Ihr Portfolio – ohne Karten neu zu drucken.',
+ 'Mit QR-Codes auf Visitenkarten teilen Sie eine aktuelle Kontaktseite, vCard, Buchungsseite oder Ihr Portfolio - ohne Karten neu zu drucken.',
intro:
'QR-Codes auf Visitenkarten sind am nützlichsten, wenn sich Ihr Kontaktziel schneller ändert als Ihr gedruckter Kartenvorrat.',
answer:
- 'Ein Visitenkarten-QR-Code sollte Menschen zur heute besten nächsten Aktion führen – sei es Ihren Kontakt speichern, einen Termin buchen oder Ihr aktuelles Profil besuchen.',
+ 'Ein Visitenkarten-QR-Code sollte Menschen zur heute besten nächsten Aktion führen - sei es Ihren Kontakt speichern, einen Termin buchen oder Ihr aktuelles Profil besuchen.',
whenToUse: [
'Ihre Rolle, Ihr Buchungslink oder Ihr Portfolio ändern sich häufiger als Ihre gedruckten Karten.',
'Eine Karte soll für Networking, Vertriebs-Follow-up und Kontakt-Speichern gleichzeitig funktionieren.',
@@ -763,7 +763,7 @@ export const useCasePagesDe: Record = {
{
title: 'Aktueller Kontakt-Flow',
description:
- 'Leiten Sie Scanner zu einer vCard oder aktuellen Kontaktseite – der nächste Schritt ist Speichern, nicht Abtippen.',
+ 'Leiten Sie Scanner zu einer vCard oder aktuellen Kontaktseite - der nächste Schritt ist Speichern, nicht Abtippen.',
},
{
title: 'Rollen- oder Profil-Updates',
@@ -779,7 +779,7 @@ export const useCasePagesDe: Record = {
checklistTitle: 'Visitenkarten-QR-Checkliste',
checklist: [
'Wählen Sie eine primäre Aktion nach dem Scan, statt jeden Scanner überallhin gleichzeitig zu schicken.',
- 'Machen Sie die Landingpage mobil nutzbar – die meisten Visitenkarten-Scans passieren am Smartphone.',
+ 'Machen Sie die Landingpage mobil nutzbar - die meisten Visitenkarten-Scans passieren am Smartphone.',
"Nutzen Sie CTA-Texte wie 'Scannen und Kontakt speichern' oder 'Scannen für Terminbuchung'.",
'Testen Sie Druckgröße und Kontrast, bevor Sie eine große Auflage bestellen.',
],
@@ -851,13 +851,13 @@ export const useCasePagesDe: Record = {
{ label: 'Kreativ-Tests', text: 'Standardmäßig schwach', value: true },
],
howToSteps: [
- 'Erstellen Sie einen Flyer-QR-Flow pro Angebot oder Platzierungs-Cluster – nicht einen Code für alle Kampagnen für immer.',
+ 'Erstellen Sie einen Flyer-QR-Flow pro Angebot oder Platzierungs-Cluster - nicht einen Code für alle Kampagnen für immer.',
'Nutzen Sie ein dynamisches Ziel oder getaggte URLs, damit sich die Landingpage ohne neuen Druck ändern lässt.',
'Tracken Sie den Scan-Kontext nach Verteilpunkt, Stadtteil oder Kreativ-Version, bevor Sie entscheiden, was skaliert.',
],
workflowTitle: 'Was Flyer-QR-Codes messbar machen sollten',
workflowIntro:
- 'Flyer sind leicht zu drucken und schwer auszuwerten – außer, das QR-Setup ist von Anfang an auf Vergleichbarkeit ausgelegt.',
+ 'Flyer sind leicht zu drucken und schwer auszuwerten - außer, das QR-Setup ist von Anfang an auf Vergleichbarkeit ausgelegt.',
workflowCards: [
{
title: 'Platzierungs-bewusste Kampagnen',
@@ -913,7 +913,7 @@ export const useCasePagesDe: Record = {
{
question: 'Worauf sollte ein Flyer-QR-Code verlinken?',
answer:
- 'Auf die eine nächste Aktion, die der Flyer verspricht – z.B. eine Anmelde-, Angebots-, Buchungs- oder Kampagnen-Landingpage.',
+ 'Auf die eine nächste Aktion, die der Flyer verspricht - z.B. eine Anmelde-, Angebots-, Buchungs- oder Kampagnen-Landingpage.',
},
],
heroImage: '/marketing/use-cases/flyer-qr-codes.png',
@@ -934,11 +934,11 @@ export const useCasePagesDe: Record = {
eyebrow: 'Verpackungen',
titleSuffix: 'für Produkt-Support, Beileger und Post-Purchase-Journeys',
metaDescription:
- 'Mit Verpackungs-QR-Codes leiten Sie Käufer zu Anleitungen, Support, Onboarding oder Angeboten nach dem Kauf – und halten Scans messbar.',
+ 'Mit Verpackungs-QR-Codes leiten Sie Käufer zu Anleitungen, Support, Onboarding oder Angeboten nach dem Kauf - und halten Scans messbar.',
intro:
'Verpackungs-QR-Codes sind am wertvollsten, wenn sie mehr tun, als Nutzer auf eine Startseite zu schicken: Sie sollten die Produkt-Journey nach dem Kauf unterstützen und sichtbar machen, was tatsächlich gescannt wird.',
answer:
- 'Ein Verpackungs-QR-Code verbindet das physische Produkt mit einem nützlichen Ziel nach dem Kauf – Einrichtungshilfe, Anleitungen, Registrierung, Support oder Follow-up-Inhalte, die sich später aktualisieren lassen.',
+ 'Ein Verpackungs-QR-Code verbindet das physische Produkt mit einem nützlichen Ziel nach dem Kauf - Einrichtungshilfe, Anleitungen, Registrierung, Support oder Follow-up-Inhalte, die sich später aktualisieren lassen.',
whenToUse: [
'Verpackungen, Etiketten oder Beileger sollen auf sich ändernde Support- oder Onboarding-Seiten verweisen.',
'Sie möchten Scan-Transparenz nach Produktlinie, SKU-Familie oder Kampagnen-Charge.',
@@ -961,12 +961,12 @@ export const useCasePagesDe: Record = {
{
title: 'Support- und Einrichtungs-Routing',
description:
- 'Führen Sie Neukunden zur richtigen Einrichtungsanleitung, zum Handbuch, Garantie-Flow oder Support-Einstieg – ohne dass sie suchen müssen.',
+ 'Führen Sie Neukunden zur richtigen Einrichtungsanleitung, zum Handbuch, Garantie-Flow oder Support-Einstieg - ohne dass sie suchen müssen.',
},
{
title: 'Chargen- oder Produkt-Analytics',
description:
- 'Erfassen Sie, welche Produkte, Beileger oder Verpackungslinien Scans erzeugen – so sehen Sie, wo Post-Purchase-Engagement wirklich stattfindet.',
+ 'Erfassen Sie, welche Produkte, Beileger oder Verpackungslinien Scans erzeugen - so sehen Sie, wo Post-Purchase-Engagement wirklich stattfindet.',
},
{
title: 'Angebots- und Content-Updates',
@@ -1017,7 +1017,7 @@ export const useCasePagesDe: Record = {
{
question: 'Sollten Verpackungs-QR-Codes dynamisch sein?',
answer:
- 'Ja – immer dann, wenn sich Produktdokumentation, App-Links, Support-Flows oder Aktions-Ziele nach dem Verpackungsdruck ändern können.',
+ 'Ja - immer dann, wenn sich Produktdokumentation, App-Links, Support-Flows oder Aktions-Ziele nach dem Verpackungsdruck ändern können.',
},
],
heroImage: '/marketing/use-cases/packaging-qr-codes.png',
@@ -1040,7 +1040,7 @@ export const useCasePagesDe: Record = {
metaDescription:
'Mit Feedback-QR-Codes erfassen Sie Kundenmeinungen im Moment, leiten in Bewertungs-Flows und messen, welche Platzierungen Feedback bringen.',
intro:
- 'Feedback-QR-Codes funktionieren am besten, wenn sie die Hürde vor der Bewertung oder Antwort senken – und trotzdem messbar bleibt, welche Platzierungen wirklich Reaktionen auslösen.',
+ 'Feedback-QR-Codes funktionieren am besten, wenn sie die Hürde vor der Bewertung oder Antwort senken - und trotzdem messbar bleibt, welche Platzierungen wirklich Reaktionen auslösen.',
answer:
'Ein Feedback-QR-Code macht es Kunden leicht, im Moment eine Rückmeldung zu geben, und liefert Ihrem Team genug Kontext, um Standorte, Flächen und Follow-up-Performance zu vergleichen.',
whenToUse: [
@@ -1054,7 +1054,7 @@ export const useCasePagesDe: Record = {
{ label: 'Bewertungs-Steuerung', text: 'Schwer zu justieren', value: true },
],
howToSteps: [
- 'Platzieren Sie Feedback-QR-Codes dort, wo Kunden direkt nach dem Erlebnis handeln können – nicht Stunden später.',
+ 'Platzieren Sie Feedback-QR-Codes dort, wo Kunden direkt nach dem Erlebnis handeln können - nicht Stunden später.',
'Leiten Sie den Scan in den passenden Feedback- oder Bewertungs-Flow für den jeweiligen Kontext.',
'Tracken Sie Scans und Antworten pro Platzierung, damit Sie wissen, welche Flächen weiter bedruckt werden sollten.',
],
@@ -1065,7 +1065,7 @@ export const useCasePagesDe: Record = {
{
title: 'Feedback im Servicemoment',
description:
- 'Erfassen Sie Antworten, solange der Kunde noch vor Ort ist – am Tisch, an der Kasse oder direkt nach dem Kauf.',
+ 'Erfassen Sie Antworten, solange der Kunde noch vor Ort ist - am Tisch, an der Kasse oder direkt nach dem Kauf.',
},
{
title: 'Bewertungs-Routing',
@@ -1144,7 +1144,7 @@ export const useCasePagesDe: Record = {
metaDescription:
'Mit QR-Codes für Bewertungen leiten Sie zufriedene Kunden in den richtigen Bewertungs-Flow und messen, welche Platzierungen gescannt werden.',
intro:
- 'Bewertungs-QR-Codes funktionieren, wenn die Bitte genau in dem Moment erscheint, in dem der Kunde bereit ist zu antworten – und wenn sich jede Platzierung später auswerten lässt.',
+ 'Bewertungs-QR-Codes funktionieren, wenn die Bitte genau in dem Moment erscheint, in dem der Kunde bereit ist zu antworten - und wenn sich jede Platzierung später auswerten lässt.',
answer:
'Ein Bewertungs-QR-Code führt zufriedene Kunden in den passenden Bewertungs- oder Feedback-Flow und hilft Ihrem Team zu vergleichen, welche physischen Hinweise tatsächlich gescannt werden.',
whenToUse: [
@@ -1164,7 +1164,7 @@ export const useCasePagesDe: Record = {
],
workflowTitle: 'Bewertungs-Anfragen, die sich nach dem Launch verbessern lassen',
workflowIntro:
- 'Die beste Bewertungsanfrage ist nicht lauter – sie ist besser platziert, besser geroutet und gut genug gemessen, um die nächste Auflage zu verbessern.',
+ 'Die beste Bewertungsanfrage ist nicht lauter - sie ist besser platziert, besser geroutet und gut genug gemessen, um die nächste Auflage zu verbessern.',
workflowCards: [
{
title: 'Zum Moment passende Hinweise',
@@ -1174,7 +1174,7 @@ export const useCasePagesDe: Record = {
{
title: 'Kontrolliertes Bewertungs-Routing',
description:
- 'Leiten Sie zufriedene Kunden zum gewünschten Bewertungsziel – mit der Option, die Route zu ändern, wenn sich Plattform, Kampagne oder Richtlinie ändern.',
+ 'Leiten Sie zufriedene Kunden zum gewünschten Bewertungsziel - mit der Option, die Route zu ändern, wenn sich Plattform, Kampagne oder Richtlinie ändern.',
},
{
title: 'Reporting pro Platzierung',
@@ -1211,7 +1211,7 @@ export const useCasePagesDe: Record = {
{
question: 'Was ist ein Bewertungs-QR-Code?',
answer:
- 'Ein Bewertungs-QR-Code führt Kunden von einem gedruckten Hinweis in einen Bewertungs- oder Feedback-Flow – etwa einen Google-Bewertungslink, ein privates Formular oder eine geroutete Follow-up-Seite.',
+ 'Ein Bewertungs-QR-Code führt Kunden von einem gedruckten Hinweis in einen Bewertungs- oder Feedback-Flow - etwa einen Google-Bewertungslink, ein privates Formular oder eine geroutete Follow-up-Seite.',
},
{
question: 'Wo sollte ich QR-Codes zum Bewertungen-Sammeln platzieren?',
@@ -1221,12 +1221,12 @@ export const useCasePagesDe: Record = {
{
question: 'Sollten Bewertungs-QR-Codes dynamisch sein?',
answer:
- 'Ja – wenn Sie das Bewertungsziel aktualisieren, Platzierungen testen oder Kunden anders routen müssen, während das gedruckte Material bereits im Umlauf ist.',
+ 'Ja - wenn Sie das Bewertungsziel aktualisieren, Platzierungen testen oder Kunden anders routen müssen, während das gedruckte Material bereits im Umlauf ist.',
},
{
question: 'Kann ich Scans von Bewertungs-QR-Codes tracken?',
answer:
- 'Ja. Ein trackbares Setup zeigt, welche Platzierungen oder Standorte Scans erzeugen – die eigentliche Bewertung wird weiterhin auf der Zielplattform oder im Formular abgegeben.',
+ 'Ja. Ein trackbares Setup zeigt, welche Platzierungen oder Standorte Scans erzeugen - die eigentliche Bewertung wird weiterhin auf der Zielplattform oder im Formular abgegeben.',
},
],
heroImage: '/marketing/use-cases/qr-codes-for-review-collection.png',
@@ -1247,11 +1247,11 @@ export const useCasePagesDe: Record = {
eyebrow: 'Zahlungen',
titleSuffix: 'für mobiles Bezahlen, Theken-Zahlungen und gedruckte Hinweise',
metaDescription:
- 'Mit Zahlungs-QR-Codes leiten Sie Kunden in eine mobile Zahlungsaktion, die aktuell bleibt – auch wenn sich Links oder Abläufe ändern.',
+ 'Mit Zahlungs-QR-Codes leiten Sie Kunden in eine mobile Zahlungsaktion, die aktuell bleibt - auch wenn sich Links oder Abläufe ändern.',
intro:
'Zahlungs-QR-Codes sind am nützlichsten, wenn der gedruckte Hinweis einfach bleibt, während sich das Zahlungsziel dahinter mit dem Geschäft ändern kann.',
answer:
- 'Ein Zahlungs-QR-Code sollte den Scanner direkt in die gewünschte mobile Zahlungsaktion führen – ohne dass das Unternehmen bei jeder Änderung des Zahlungsflows gedruckte Hinweise ersetzen muss.',
+ 'Ein Zahlungs-QR-Code sollte den Scanner direkt in die gewünschte mobile Zahlungsaktion führen - ohne dass das Unternehmen bei jeder Änderung des Zahlungsflows gedruckte Hinweise ersetzen muss.',
whenToUse: [
'Sie nutzen gedruckte Zahlungshinweise an Theken, auf Tischen, Rechnungen oder Auslage-Materialien.',
'Sie brauchen einen mobilen Zahlungsweg, statt Kunden lange URLs abtippen zu lassen.',
@@ -1274,7 +1274,7 @@ export const useCasePagesDe: Record = {
{
title: 'Theken- und Tisch-Zahlungen',
description:
- 'Bringen Sie Kunden mit gedruckten QR-Hinweisen in einen schnellen mobilen Zahlungsschritt – ohne manuelle URL-Eingabe.',
+ 'Bringen Sie Kunden mit gedruckten QR-Hinweisen in einen schnellen mobilen Zahlungsschritt - ohne manuelle URL-Eingabe.',
},
{
title: 'Rechnungs- und Auslage-Flows',
@@ -1320,7 +1320,7 @@ export const useCasePagesDe: Record = {
{
question: 'Sollten Zahlungs-QR-Codes dynamisch sein?',
answer:
- 'Ja – wenn sich Zahlungsziel, Anbieter oder Checkout-Flow ändern können, während der gedruckte Code bereits im Einsatz ist.',
+ 'Ja - wenn sich Zahlungsziel, Anbieter oder Checkout-Flow ändern können, während der gedruckte Code bereits im Einsatz ist.',
},
{
question: 'Wo funktionieren Zahlungs-QR-Codes am besten?',
@@ -1339,7 +1339,7 @@ export const useCasePagesDe: Record = {
title: 'QR-Codes für Coupons',
cluster: 'offers',
summary:
- 'Verbinden Sie Coupon-Platzierungen mit messbaren Scans – so wissen Sie, welche Print-Angebote wirklich eingelöst werden.',
+ 'Verbinden Sie Coupon-Platzierungen mit messbaren Scans - so wissen Sie, welche Print-Angebote wirklich eingelöst werden.',
parentHref: '/qr-code-tracking',
parentTitle: 'QR-Code-Tracking',
ctaLabel: 'Coupon-QR-Code erstellen',
@@ -1350,7 +1350,7 @@ export const useCasePagesDe: Record = {
intro:
'Coupon-QR-Codes sind am stärksten, wenn der Rabatt-Pfad aktuell bleibt und Ihr Team sieht, welche Print-Platzierungen tatsächlich Einlösungen erzeugen.',
answer:
- 'Ein Coupon-QR-Code verbindet ein gedrucktes Angebot mit einem später aktualisierbaren Landing- oder Einlösungs-Flow – mit genug Tracking-Kontext, um Platzierungen und Aktionswellen zu vergleichen.',
+ 'Ein Coupon-QR-Code verbindet ein gedrucktes Angebot mit einem später aktualisierbaren Landing- oder Einlösungs-Flow - mit genug Tracking-Kontext, um Platzierungen und Aktionswellen zu vergleichen.',
whenToUse: [
'Sie schalten Rabatte auf Flyern, Kassenbons, Beilegern, Plakaten oder lokalen Print-Kampagnen.',
'Sie möchten vergleichen, welche gedruckten Angebots-Platzierungen Scans oder Einlösungen bringen.',
@@ -1378,7 +1378,7 @@ export const useCasePagesDe: Record = {
{
title: 'Platzierungs-Reporting',
description:
- 'Vergleichen Sie, wo Coupons gescannt oder eingelöst werden – damit Plakate, Flyer, Beileger und Bons keine vermischte Quelle mehr sind.',
+ 'Vergleichen Sie, wo Coupons gescannt oder eingelöst werden - damit Plakate, Flyer, Beileger und Bons keine vermischte Quelle mehr sind.',
},
{
title: 'Einlösungs-fokussiertes Follow-up',
@@ -1424,7 +1424,7 @@ export const useCasePagesDe: Record = {
{
question: 'Sollte ein Coupon-QR-Code dynamisch sein?',
answer:
- 'Ja – wenn sich Aktion, Landingpage oder Einlösungs-Flow ändern können, während das gedruckte Material noch im Umlauf ist.',
+ 'Ja - wenn sich Aktion, Landingpage oder Einlösungs-Flow ändern können, während das gedruckte Material noch im Umlauf ist.',
},
],
heroImage: '/marketing/use-cases/coupon-qr-codes.png',
diff --git a/src/lib/growth-pages.ts b/src/lib/growth-pages.ts
index 6fed758..11fb1da 100644
--- a/src/lib/growth-pages.ts
+++ b/src/lib/growth-pages.ts
@@ -243,7 +243,7 @@ export const allUseCases: UseCaseLink[] = [
},
];
-// Slugs that 301-redirect elsewhere (see next.config.mjs) — must not be
+// Slugs that 301-redirect elsewhere (see next.config.mjs) - must not be
// linked from nav, homepage, or sitemap.
const redirectedUseCaseSlugs = ['restaurant-menu-qr-codes'];
@@ -1052,7 +1052,7 @@ export const useCasePageContent: Record = {
{
title: 'Employee and event feedback',
description:
- 'Use break-room posters, badge inserts, or post-event handouts to collect internal or attendee feedback anonymously — without email lists or logins.',
+ 'Use break-room posters, badge inserts, or post-event handouts to collect internal or attendee feedback anonymously - without email lists or logins.',
},
],
checklistTitle: 'Feedback QR checklist',
@@ -1113,7 +1113,7 @@ export const useCasePageContent: Record = {
{
question: 'Should feedback be anonymous or personalized?',
answer:
- 'Anonymous forms get more honest responses and work well for employee or in-store feedback. Personalized flows (with an order or table number in the destination URL) let you follow up on specific experiences. The QR code itself works the same either way — the difference is the form it points to.',
+ 'Anonymous forms get more honest responses and work well for employee or in-store feedback. Personalized flows (with an order or table number in the destination URL) let you follow up on specific experiences. The QR code itself works the same either way - the difference is the form it points to.',
},
{
question: 'Can I use feedback QR codes for events?',
@@ -1123,7 +1123,7 @@ export const useCasePageContent: Record = {
{
question: 'Can I use QR codes for employee feedback?',
answer:
- 'Yes. Break rooms, notice boards, and shift handout sheets are common placements. An anonymous form behind the QR code lowers the barrier for honest internal feedback — no login, no email trail.',
+ 'Yes. Break rooms, notice boards, and shift handout sheets are common placements. An anonymous form behind the QR code lowers the barrier for honest internal feedback - no login, no email trail.',
},
],
heroImage: '/marketing/use-cases/feedback-qr-codes.png',
diff --git a/src/lib/industry-pages.ts b/src/lib/industry-pages.ts
index b5ea43d..ebbe8dc 100644
--- a/src/lib/industry-pages.ts
+++ b/src/lib/industry-pages.ts
@@ -624,7 +624,7 @@ export const allIndustries: IndustryPage[] = [
metaTitle: "QR Codes for Yoga Studios: Class Schedule & Intro Offers",
metaDescription: "Fill your yoga classes faster with QR codes linking to schedules, intro offers, and instructor profiles. Perfect for studio entrance and social media.",
headline: "QR Codes for Yoga Studios: Fill Every Class and Reduce No-Shows",
- subheadline: "Let new students book their intro class, view the weekly schedule, and find the right instructor with one scan — no app download required",
+ subheadline: "Let new students book their intro class, view the weekly schedule, and find the right instructor with one scan - no app download required",
heroImage: "/marketing/industries/yoga-studios.png",
statistics: [
{ value: "73%", label: "of yoga studios use QR codes for class bookings (Mindbody, 2024)" },
@@ -635,7 +635,7 @@ export const allIndustries: IndustryPage[] = [
{ title: "First-Visit Funnels", description: "A QR code at the studio entrance links curious passersby to your intro offer and booking form - capturing leads 24 hours a day." },
{ title: "Live Class Schedules", description: "Students scan the door poster or social post to see this week's schedule and available spots, then book directly without calling." },
{ title: "Instructor Profiles", description: "Help students find the right teacher. Link a studio QR to instructor bio pages with teaching style, certifications, and class specialties." },
- { title: "Automated Booking Reminders", description: "Link your QR code to Mindbody, Pike13, or any booking platform that sends SMS and email reminders — turning casual sign-ups into confirmed attendees." }
+ { title: "Automated Booking Reminders", description: "Link your QR code to Mindbody, Pike13, or any booking platform that sends SMS and email reminders - turning casual sign-ups into confirmed attendees." }
],
useCases: [
"Weekly class schedule with live booking availability",
@@ -649,9 +649,9 @@ export const allIndustries: IndustryPage[] = [
{ question: "How do yoga studios attract new students with QR codes?", answer: "A QR on the studio window or door links to an intro offer - like a first-week free deal - that captures email and converts walk-by traffic to bookings." },
{ question: "Can QR codes reduce class no-shows?", answer: "Yes, linking to a booking system that sends automated reminders significantly reduces no-show rates compared to informal reservations." },
{ question: "Where should yoga studio QR codes be placed?", answer: "Studio entrance door, mat storage area, and any printed flyers or community boards in the surrounding neighborhood." },
- { question: "Can I use a QR code for class sign-ups and waitlists?", answer: "Yes. Link your QR to Mindbody, Pike13, or any booking platform that supports waitlists. When a spot opens, waitlisted students get notified automatically — no manual follow-up needed." },
+ { question: "Can I use a QR code for class sign-ups and waitlists?", answer: "Yes. Link your QR to Mindbody, Pike13, or any booking platform that supports waitlists. When a spot opens, waitlisted students get notified automatically - no manual follow-up needed." },
{ question: "What's the best way to use QR codes to promote yoga workshops?", answer: "Print a QR on an A5 card and distribute it in the studio, at local health food shops, and on community boards. Link it to a registration page with the workshop details and an early-bird offer to drive urgency." },
- { question: "How do yoga studios use QR codes for social media growth?", answer: "Place a QR code near the check-in desk linking to your Instagram or YouTube channel. Students who enjoyed a class scan it to follow you — turning each visit into a long-term digital connection." }
+ { question: "How do yoga studios use QR codes for social media growth?", answer: "Place a QR code near the check-in desk linking to your Instagram or YouTube channel. Students who enjoyed a class scan it to follow you - turning each visit into a long-term digital connection." }
],
placementTips: [
"Laminated poster on the studio entrance door or window",
@@ -693,7 +693,7 @@ export const allIndustries: IndustryPage[] = [
{ question: "Can QR codes replace spa brochures?", answer: "Yes, a QR code linking to a digital treatment menu with photos is more current and far cheaper to maintain than printed brochures." },
{ question: "Where should spa QR codes be placed?", answer: "Reception desk, waiting area, treatment room doors, and on checkout receipts are the four highest-performing placements for spas." },
{ question: "How can spas use QR codes to get more Google reviews?", answer: "Print a QR code on the checkout receipt or a small thank-you card and link it directly to your Google Business review form. Clients who are relaxed and satisfied after a treatment are far more likely to leave a review than if you ask them days later by email." },
- { question: "Can a QR code help spas promote seasonal packages?", answer: "Yes. A dynamic QR on your reception display or window can be updated to link to a new seasonal landing page — Christmas packages in December, bridal bundles in spring — without reprinting the code." },
+ { question: "Can a QR code help spas promote seasonal packages?", answer: "Yes. A dynamic QR on your reception display or window can be updated to link to a new seasonal landing page - Christmas packages in December, bridal bundles in spring - without reprinting the code." },
{ question: "How do spas use QR codes for post-treatment product sales?", answer: "Staff hand clients a card after each treatment linking to the exact products used during their session. The client is relaxed and already familiar with the product, making this one of the highest-converting retail moments in any spa." }
],
placementTips: [
@@ -732,7 +732,7 @@ export const allIndustries: IndustryPage[] = [
tools: ["url-qr-code", "instagram-qr-code", "vcard-qr-code"],
faq: [
{ question: "How do beauty salons use QR codes to get more bookings?", answer: "A QR code on the station mirror or checkout counter links directly to the booking platform. Clients rebook while still at the salon, capturing high-intent conversions." },
- { question: "Can a QR code replace a printed price list at a salon?", answer: "Yes, link a QR to your service menu page. When prices change, update the page — the physical QR code never needs to be reprinted. The same principle applies across hospitality: see how restaurants handle this → " },
+ { question: "Can a QR code replace a printed price list at a salon?", answer: "Yes, link a QR to your service menu page. When prices change, update the page - the physical QR code never needs to be reprinted. The same principle applies across hospitality: see how restaurants handle this → " },
{ question: "What social platform should beauty salon QR codes link to?", answer: "Instagram is the strongest platform for beauty salons because transformation photos and styling reels perform extremely well there." }
],
placementTips: [
@@ -746,10 +746,10 @@ export const allIndustries: IndustryPage[] = [
slug: "barbershops",
name: "Barbershops",
title: "QR Codes for Barbershops",
- metaTitle: "QR Codes for Barbershops: Book a Cut, Reduce Waits & Get More Reviews",
- metaDescription: "Reduce walk-in wait times and fill appointment slots with QR codes for barbershops. Show your portfolio, booking link, and Google reviews in one scan.",
+ metaTitle: "QR Codes for Barbershops: Bookings & Reviews",
+ metaDescription: "A QR on the window lets clients book instead of waiting. A QR at checkout turns a fresh haircut into a Google review. Setup takes minutes and starts free.",
headline: "QR Codes for Barbershops: Check the Wait, Book a Cut, Leave a Review",
- subheadline: "Let clients check current wait time and book their slot before they even walk through the door — and turn every checkout into a 5-star review",
+ subheadline: "Let clients check current wait time and book their slot before they even walk through the door - and turn every checkout into a 5-star review",
heroImage: "/marketing/industries/barbershops.jpeg",
statistics: [
{ value: "35%", label: "Increase in Repeat Bookings from Checkout QR (Square, 2024)" },
@@ -760,7 +760,7 @@ export const allIndustries: IndustryPage[] = [
{ title: "Live Wait Time Visibility", description: "Link a QR on your shop window to a live wait time or booking page. Clients decide to walk in or book a slot before they commit to waiting." },
{ title: "Barber Portfolio", description: "Each barber can have their own QR card showing their personal Instagram or portfolio of cuts - helping clients choose their preferred stylist." },
{ title: "Google Review Shortcut", description: "Print a QR code on a small card handed at checkout. Satisfied clients tap once to land on your Google review page - no searching required." },
- { title: "Repeat Booking at Checkout", description: "A QR on the payment counter links directly to your booking calendar. Clients rebook their next cut while still in the chair — the single most effective way to reduce gaps between visits." }
+ { title: "Repeat Booking at Checkout", description: "A QR on the payment counter links directly to your booking calendar. Clients rebook their next cut while still in the chair - the single most effective way to reduce gaps between visits." }
],
useCases: [
"Online booking link from window QR",
@@ -774,9 +774,9 @@ export const allIndustries: IndustryPage[] = [
{ question: "How do barbershops reduce walk-in wait times with QR codes?", answer: "A QR on the shop window links to your booking system. Clients book a slot instead of waiting, spreading demand more evenly throughout the day." },
{ question: "Should each barber have their own QR code?", answer: "For shops with distinct stylists, yes. Individual barber portfolio QR codes help clients pick a preferred barber and follow them on Instagram." },
{ question: "What's the best review strategy for barbershops?", answer: "Hand a small card with a Google review QR as clients pay. The experience is freshest in that moment and conversion rates are highest." },
- { question: "How do barbers use QR codes to get more appointments?", answer: "Place a QR code sticker on the shop window and a small card on the counter linking to your booking platform (e.g. Booksy, Square, or Fresha). Walk-by foot traffic can book a slot on the spot without calling — converting passive interest into confirmed appointments." },
- { question: "Can QR codes help barbershops build a loyal client base?", answer: "Yes. A QR linked to a digital loyalty program lets clients accumulate stamps or rewards without a paper card that gets lost. After 10 visits, they unlock a free cut — and every reminder keeps your shop top of mind." },
- { question: "What's the easiest way for a barbershop to collect client intake information?", answer: "Link a QR code in the waiting area to a short digital form asking about preferred barber, style notes, and contact details. Clients complete it on their phone while they wait — saving time when they sit in the chair." }
+ { question: "How do barbers use QR codes to get more appointments?", answer: "Place a QR code sticker on the shop window and a small card on the counter linking to your booking platform (e.g. Booksy, Square, or Fresha). Walk-by foot traffic can book a slot on the spot without calling - converting passive interest into confirmed appointments." },
+ { question: "Can QR codes help barbershops build a loyal client base?", answer: "Yes. A QR linked to a digital loyalty program lets clients accumulate stamps or rewards without a paper card that gets lost. After 10 visits, they unlock a free cut - and every reminder keeps your shop top of mind." },
+ { question: "What's the easiest way for a barbershop to collect client intake information?", answer: "Link a QR code in the waiting area to a short digital form asking about preferred barber, style notes, and contact details. Clients complete it on their phone while they wait - saving time when they sit in the chair." }
],
placementTips: [
"Window sticker for wait time and booking link",
@@ -1661,7 +1661,7 @@ export const allIndustries: IndustryPage[] = [
{ title: "Instant Lead Capture", description: "A QR on your stand banner links to a contact form with a compelling offer - visitors submit their details in 30 seconds without exchanging business cards." },
{ title: "Digital Product Catalogue", description: "Replace printed brochures with a QR linking to a digital catalogue with full specs, pricing, and download options. Update it between shows without reprinting." },
{ title: "Giveaway Engagement", description: "Print a QR on branded merchandise giveaways (pens, bags, notepads) so every item taken from the stand is a lead capture touchpoint throughout the event." },
- { title: "Post-Show Follow-Up Automation", description: "Link your lead capture QR to a CRM or email automation tool. Every form submission triggers an immediate follow-up sequence — so warm leads stay warm after the show floor closes." }
+ { title: "Post-Show Follow-Up Automation", description: "Link your lead capture QR to a CRM or email automation tool. Every form submission triggers an immediate follow-up sequence - so warm leads stay warm after the show floor closes." }
],
useCases: [
"Lead capture form linked from stand banner and panel",
@@ -1675,9 +1675,9 @@ export const allIndustries: IndustryPage[] = [
{ question: "How do trade show exhibitors use QR codes for lead capture?", answer: "A QR on stand panels and giveaways links to a contact form. Visitors submit their details for a free resource - giving exhibitors qualified leads with explicit opt-in." },
{ question: "Should trade show QR codes link to a brochure or a form?", answer: "Lead capture form first. The brochure can be on the same page below the form - so visitors get value while you get their contact details." },
{ question: "Where are the best QR placements on a trade show stand?", answer: "Stand back panel, table display, and every giveaway item are the three placements with the widest reach at any exhibition." },
- { question: "How do I use QR codes to replace business cards at a trade show?", answer: "Create a vCard QR code for each team member at your stand. Visitors scan it to save the full contact — name, role, phone, and email — directly to their phone. No paper, no loss, no manual entry after the event." },
+ { question: "How do I use QR codes to replace business cards at a trade show?", answer: "Create a vCard QR code for each team member at your stand. Visitors scan it to save the full contact - name, role, phone, and email - directly to their phone. No paper, no loss, no manual entry after the event." },
{ question: "Can QR codes help exhibitors qualify leads at a trade show?", answer: "Yes. Add a short qualifying question to your lead capture form (e.g. company size, buying timeline). Visitors self-select, so your sales team receives pre-qualified leads instead of a raw list of email addresses." },
- { question: "How can I track which part of my trade show stand generates the most scans?", answer: "Create separate dynamic QR codes for each placement — stand banner, table card, and giveaway items — each with a unique UTM parameter. After the show, compare scan counts in your QR dashboard to see which location drove the most engagement." }
+ { question: "How can I track which part of my trade show stand generates the most scans?", answer: "Create separate dynamic QR codes for each placement - stand banner, table card, and giveaway items - each with a unique UTM parameter. After the show, compare scan counts in your QR dashboard to see which location drove the most engagement." }
],
placementTips: [
"Stand back panel at eye level with lead capture QR",
@@ -1927,7 +1927,7 @@ export const allIndustries: IndustryPage[] = [
metaTitle: "QR Codes for Airports: Gates, Maps, Check-In & Retail Offers",
metaDescription: "Improve passenger experience at airports with QR codes for live flight status, interactive terminal maps, mobile check-in, and retail promotions.",
headline: "QR Codes for Airports: Self-Service Information at Every Terminal Sign",
- subheadline: "Point travelers to live departures, interactive maps, and mobile check-in directly from terminal signage — reducing queue pressure at information desks and turning dwell time into retail revenue",
+ subheadline: "Point travelers to live departures, interactive maps, and mobile check-in directly from terminal signage - reducing queue pressure at information desks and turning dwell time into retail revenue",
heroImage: "/marketing/industries/airports.jpeg",
statistics: [
{ value: "3x", label: "More QR Code Scans at Airports on Peak Travel Weekends (IATA, 2024)" },
@@ -1938,7 +1938,7 @@ export const allIndustries: IndustryPage[] = [
{ title: "Live Flight Status Access", description: "QR codes on terminal pillars and gate signage link to the live departures board filtered for the relevant gate area - passengers check status without crowding the screens." },
{ title: "Interactive Terminal Maps", description: "A QR at each terminal entrance links to an interactive map showing the passenger's current location, nearest restrooms, gates, and retail options." },
{ title: "Gate Dwell-Time Offers", description: "A QR on boarding pass envelopes and gate seating area signs links passengers to retail, food, and lounge offers - turning waiting time into retail revenue." },
- { title: "Instant WiFi Access", description: "A QR code at each terminal entry and gate lounge links to airport WiFi credentials. Passengers connect in seconds without staff assistance — reducing one of the most common information desk enquiries." }
+ { title: "Instant WiFi Access", description: "A QR code at each terminal entry and gate lounge links to airport WiFi credentials. Passengers connect in seconds without staff assistance - reducing one of the most common information desk enquiries." }
],
useCases: [
"Live flight status and departure board at gate signage",
@@ -1953,8 +1953,8 @@ export const allIndustries: IndustryPage[] = [
{ question: "Can QR codes at airports increase retail revenue?", answer: "Yes, gate area QR codes linking to time-limited retail and food offers during dwell time convert waiting passengers into active shoppers." },
{ question: "Where should airport QR codes be placed?", answer: "Terminal entrance signs, gate seating area displays, check-in desk counters, and boarding pass envelopes are the highest-traffic placements." },
{ question: "What do airports use QR codes for?", answer: "Modern airports use QR codes across multiple touchpoints: live departure boards, interactive terminal maps, mobile check-in prompts, WiFi access, lounge entry, retail offers, and passenger satisfaction surveys. Each replaces a friction point that previously required staff assistance or printed material." },
- { question: "How do QR codes improve the airport passenger experience?", answer: "QR codes give travelers instant access to the information they need — gate numbers, flight status, directions, WiFi — without queuing at a desk or searching on a cluttered departures screen. This reduces stress, especially during delays, and frees up staff to handle more complex passenger needs." },
- { question: "Can airports use QR codes for passenger feedback?", answer: "Yes. A QR code at security exits, gate lounges, or baggage claim areas can link to a short satisfaction survey. Airports that collect real-time feedback by location can identify problem areas — such as slow security lanes or confusing signage — and respond before they escalate." }
+ { question: "How do QR codes improve the airport passenger experience?", answer: "QR codes give travelers instant access to the information they need - gate numbers, flight status, directions, WiFi - without queuing at a desk or searching on a cluttered departures screen. This reduces stress, especially during delays, and frees up staff to handle more complex passenger needs." },
+ { question: "Can airports use QR codes for passenger feedback?", answer: "Yes. A QR code at security exits, gate lounges, or baggage claim areas can link to a short satisfaction survey. Airports that collect real-time feedback by location can identify problem areas - such as slow security lanes or confusing signage - and respond before they escalate." }
],
placementTips: [
"Terminal entry pillar with interactive map and flight status QR",
diff --git a/src/lib/qr-shapes.ts b/src/lib/qr-shapes.ts
new file mode 100644
index 0000000..c206d59
--- /dev/null
+++ b/src/lib/qr-shapes.ts
@@ -0,0 +1,358 @@
+/**
+ * QR module and eye shapes as SVG path data.
+ *
+ * Ported from qr-design-showcase.html, which drew to a canvas. SVG was chosen
+ * instead for three reasons: the SVG download becomes a serialisation rather
+ * than a rasterisation, print output stays sharp at any size (the whole point
+ * for a code that ends up on a flyer), and html-to-image can still produce the
+ * PNG from the same DOM node.
+ *
+ * Every function returns path data or a primitive descriptor. Nothing here
+ * touches the DOM, so it can be unit tested and reused server-side later.
+ */
+
+export type ModuleShape =
+ | 'square'
+ | 'rounded'
+ | 'dots'
+ | 'classy'
+ | 'classy-rounded'
+ | 'diamond'
+ | 'star'
+ | 'hexagon'
+ | 'plus'
+ | 'mosaic'
+ | 'liquid';
+
+/**
+ * Only two eye frames ship.
+ *
+ * Measured on 27.07.2026 against OpenCV's decoder, 5 payloads x 3 resolutions:
+ * square 15/15, rounded at radius 0.10 15/15, rounded at radius 0.28 0/15,
+ * and circle, leaf, flower and hexagon 0/15 each. The finder pattern is what a
+ * decoder locks onto first; changing its silhouette breaks the 1:1:3:1:1 ratio
+ * it scans for. Shipping those four would mean handing someone a code that
+ * looks finished and fails on the printed flyer - for an audience whose whole
+ * fear is exactly that.
+ */
+export type EyeFrameShape = 'square' | 'rounded';
+
+/** Measured 5/5 each, except star at 3/5 - which is why star is not here. */
+export type EyeBallShape =
+ | 'square'
+ | 'rounded'
+ | 'circle'
+ | 'diamond'
+ | 'hexagon';
+
+/** Shapes available on the Pro plan. Deliberately four, not six. */
+export const PRO_MODULE_SHAPES: ModuleShape[] = [
+ 'square',
+ 'rounded',
+ 'dots',
+ 'classy-rounded',
+];
+
+/** Everything else needs Business. */
+export const BUSINESS_MODULE_SHAPES: ModuleShape[] = [
+ 'classy',
+ 'diamond',
+ 'star',
+ 'hexagon',
+ 'plus',
+ 'mosaic',
+ 'liquid',
+];
+
+/**
+ * Shapes that reduce the filled area of each module enough to hurt scanning.
+ * Used to force error correction up and to warn before print.
+ */
+export const LOW_COVERAGE_SHAPES: ModuleShape[] = [
+ 'star',
+ 'plus',
+ 'diamond',
+ 'mosaic',
+ 'dots',
+];
+
+export const MODULE_SHAPE_LABELS: Record = {
+ square: 'Square (classic)',
+ rounded: 'Rounded',
+ dots: 'Dots',
+ 'classy-rounded': 'Flowing',
+ classy: 'Classy',
+ diamond: 'Diamond',
+ star: 'Star',
+ hexagon: 'Hexagon',
+ plus: 'Plus',
+ mosaic: 'Mosaic',
+ liquid: 'Liquid',
+};
+
+export const EYE_FRAME_LABELS: Record = {
+ square: 'Square',
+ rounded: 'Rounded',
+};
+
+export const EYE_BALL_LABELS: Record = {
+ square: 'Square',
+ rounded: 'Rounded',
+ circle: 'Circle',
+ diamond: 'Diamond',
+ hexagon: 'Hexagon',
+};
+
+const n = (v: number) => Math.round(v * 1000) / 1000;
+
+/** Rounded rectangle with independently sized corners. */
+export function roundedRectPath(
+ x: number,
+ y: number,
+ w: number,
+ h: number,
+ tl: number,
+ tr: number,
+ br: number,
+ bl: number
+): string {
+ return [
+ `M${n(x + tl)},${n(y)}`,
+ `L${n(x + w - tr)},${n(y)}`,
+ tr ? `A${n(tr)},${n(tr)} 0 0 1 ${n(x + w)},${n(y + tr)}` : `L${n(x + w)},${n(y)}`,
+ `L${n(x + w)},${n(y + h - br)}`,
+ br ? `A${n(br)},${n(br)} 0 0 1 ${n(x + w - br)},${n(y + h)}` : `L${n(x + w)},${n(y + h)}`,
+ `L${n(x + bl)},${n(y + h)}`,
+ bl ? `A${n(bl)},${n(bl)} 0 0 1 ${n(x)},${n(y + h - bl)}` : `L${n(x)},${n(y + h)}`,
+ `L${n(x)},${n(y + tl)}`,
+ tl ? `A${n(tl)},${n(tl)} 0 0 1 ${n(x + tl)},${n(y)}` : `L${n(x)},${n(y)}`,
+ 'Z',
+ ].join(' ');
+}
+
+export function hexagonPath(cx: number, cy: number, r: number): string {
+ const pts: string[] = [];
+ for (let i = 0; i < 6; i++) {
+ const a = (Math.PI / 180) * (60 * i - 30);
+ pts.push(`${n(cx + r * Math.cos(a))},${n(cy + r * Math.sin(a))}`);
+ }
+ return `M${pts.join(' L')} Z`;
+}
+
+export function starPath(
+ cx: number,
+ cy: number,
+ outerR: number,
+ innerR: number,
+ spikes = 5
+): string {
+ let rot = (Math.PI / 2) * 3;
+ const step = Math.PI / spikes;
+ const pts: string[] = [`${n(cx)},${n(cy - outerR)}`];
+ for (let i = 0; i < spikes; i++) {
+ pts.push(`${n(cx + Math.cos(rot) * outerR)},${n(cy + Math.sin(rot) * outerR)}`);
+ rot += step;
+ pts.push(`${n(cx + Math.cos(rot) * innerR)},${n(cy + Math.sin(rot) * innerR)}`);
+ rot += step;
+ }
+ return `M${pts.join(' L')} Z`;
+}
+
+export function circlePath(cx: number, cy: number, r: number): string {
+ return [
+ `M${n(cx - r)},${n(cy)}`,
+ `A${n(r)},${n(r)} 0 1 0 ${n(cx + r)},${n(cy)}`,
+ `A${n(r)},${n(r)} 0 1 0 ${n(cx - r)},${n(cy)}`,
+ 'Z',
+ ].join(' ');
+}
+
+/** Deterministic per-cell hash, so mosaic looks random but renders identically every time. */
+function cellHash(r: number, c: number): number {
+ const v = Math.sin(r * 127.1 + c * 311.7) * 43758.5453;
+ return v - Math.floor(v);
+}
+
+export interface Neighbors {
+ top: boolean;
+ bottom: boolean;
+ left: boolean;
+ right: boolean;
+}
+
+export function moduleShapePath(
+ shape: ModuleShape,
+ x: number,
+ y: number,
+ size: number,
+ nb: Neighbors,
+ row: number,
+ col: number
+): string {
+ const { top, bottom, left, right } = nb;
+
+ switch (shape) {
+ case 'square':
+ return roundedRectPath(x, y, size, size, 0, 0, 0, 0);
+
+ case 'dots':
+ return circlePath(x + size / 2, y + size / 2, size * 0.42);
+
+ case 'diamond':
+ return `M${n(x + size / 2)},${n(y)} L${n(x + size)},${n(y + size / 2)} L${n(
+ x + size / 2
+ )},${n(y + size)} L${n(x)},${n(y + size / 2)} Z`;
+
+ case 'star':
+ return starPath(x + size / 2, y + size / 2, size * 0.5, size * 0.22, 5);
+
+ case 'hexagon':
+ return hexagonPath(x + size / 2, y + size / 2, size * 0.52);
+
+ case 'plus': {
+ const armW = size * 0.42;
+ const off = (size - armW) / 2;
+ return (
+ roundedRectPath(x + off, y, armW, size, 0, 0, 0, 0) +
+ ' ' +
+ roundedRectPath(x, y + off, size, armW, 0, 0, 0, 0)
+ );
+ }
+
+ case 'mosaic': {
+ const h = cellHash(row, col);
+ if (h < 0.34) return circlePath(x + size / 2, y + size / 2, size * 0.42);
+ if (h < 0.67) {
+ const r = size * 0.28;
+ return roundedRectPath(x, y, size, size, r, r, r, r);
+ }
+ return roundedRectPath(x, y, size, size, 0, 0, 0, 0);
+ }
+
+ case 'liquid':
+ case 'classy': {
+ const r = size * (shape === 'liquid' ? 0.55 : 0.5);
+ return roundedRectPath(
+ x,
+ y,
+ size,
+ size,
+ !top && !left ? r : 0,
+ !top && !right ? r : 0,
+ !bottom && !right ? r : 0,
+ !bottom && !left ? r : 0
+ );
+ }
+
+ case 'classy-rounded': {
+ const r = size * 0.5;
+ const soft = r * 0.35;
+ return roundedRectPath(
+ x,
+ y,
+ size,
+ size,
+ !top && !left ? r : top && left ? 0 : soft,
+ !top && !right ? r : top && right ? 0 : soft,
+ !bottom && !right ? r : bottom && right ? 0 : soft,
+ !bottom && !left ? r : bottom && left ? 0 : soft
+ );
+ }
+
+ case 'rounded':
+ default: {
+ const r = size * 0.32;
+ return roundedRectPath(
+ x,
+ y,
+ size,
+ size,
+ !top && !left ? r : 0,
+ !top && !right ? r : 0,
+ !bottom && !right ? r : 0,
+ !bottom && !left ? r : 0
+ );
+ }
+ }
+}
+
+/**
+ * Eye frame is a ring: outer shape minus inner shape, rendered with
+ * fill-rule="evenodd" so the hole appears without a second fill colour.
+ */
+export function eyeFramePath(
+ shape: EyeFrameShape,
+ x: number,
+ y: number,
+ s: number
+): string {
+ const inner = s * (5 / 7);
+ const io = s * (1 / 7);
+
+ switch (shape) {
+ case 'rounded': {
+ // 0.10, not 0.28. Measured: at 0.28 the code stopped decoding in all 15
+ // test renders, at 0.15 in a third of them, at 0.10 in none.
+ const r1 = s * 0.1;
+ const r2 = s * 0.1 * (5 / 7);
+ return `${roundedRectPath(x, y, s, s, r1, r1, r1, r1)} ${roundedRectPath(
+ x + io,
+ y + io,
+ inner,
+ inner,
+ r2,
+ r2,
+ r2,
+ r2
+ )}`;
+ }
+
+ case 'square':
+ default:
+ return `${roundedRectPath(x, y, s, s, 0, 0, 0, 0)} ${roundedRectPath(
+ x + io,
+ y + io,
+ inner,
+ inner,
+ 0,
+ 0,
+ 0,
+ 0
+ )}`;
+ }
+}
+
+export function eyeBallPath(
+ shape: EyeBallShape,
+ x: number,
+ y: number,
+ size: number
+): string {
+ switch (shape) {
+ case 'circle':
+ return circlePath(x + size / 2, y + size / 2, size / 2);
+ case 'diamond':
+ return `M${n(x + size / 2)},${n(y)} L${n(x + size)},${n(y + size / 2)} L${n(
+ x + size / 2
+ )},${n(y + size)} L${n(x)},${n(y + size / 2)} Z`;
+ case 'hexagon':
+ return hexagonPath(x + size / 2, y + size / 2, size * 0.55);
+ case 'rounded': {
+ const r = size * 0.3;
+ return roundedRectPath(x, y, size, size, r, r, r, r);
+ }
+ case 'square':
+ default:
+ return roundedRectPath(x, y, size, size, 0, 0, 0, 0);
+ }
+}
+
+/** True when the module at (r,c) belongs to one of the three finder patterns. */
+export function isInEye(r: number, c: number, size: number): boolean {
+ const eyes: [number, number][] = [
+ [0, 0],
+ [0, size - 7],
+ [size - 7, 0],
+ ];
+ return eyes.some(([er, ec]) => r >= er && r < er + 7 && c >= ec && c < ec + 7);
+}
diff --git a/src/lib/render-qr-svg.ts b/src/lib/render-qr-svg.ts
new file mode 100644
index 0000000..1f917a4
--- /dev/null
+++ b/src/lib/render-qr-svg.ts
@@ -0,0 +1,148 @@
+import QRCodeLib from 'qrcode';
+import {
+ ModuleShape,
+ EyeFrameShape,
+ EyeBallShape,
+ moduleShapePath,
+ eyeFramePath,
+ eyeBallPath,
+ isInEye,
+} from '@/lib/qr-shapes';
+
+/**
+ * Renders a styled QR code to an SVG string.
+ *
+ * The React component in components/generator/StyledQRCode.tsx produces the same
+ * output as JSX for the live preview; this is the string version, used where
+ * there is no React tree to render into - the bulk generator writing 500 files
+ * into a ZIP being the case that matters.
+ *
+ * Both paths call the same shape functions, so a preset previewed in /create and
+ * the same preset applied to a batch cannot drift apart.
+ */
+
+export interface QRDesignStyle {
+ foregroundColor?: string;
+ backgroundColor?: string;
+ moduleShape?: ModuleShape;
+ eyeFrameShape?: EyeFrameShape;
+ eyeBallShape?: EyeBallShape;
+ gradientMode?: 'none' | 'linear' | 'radial';
+ gradientTo?: string;
+ /** Data URL. Carried in presets so a batch keeps the client's mark. */
+ logoUrl?: string;
+ /** Logo edge length in the 200px editor scale. */
+ logoSize?: number;
+}
+
+export function renderStyledQRSvg(
+ value: string,
+ style: QRDesignStyle | null | undefined,
+ size = 300,
+ margin = 4
+): string {
+ const fg = style?.foregroundColor || '#000000';
+ const bg = style?.backgroundColor || '#FFFFFF';
+ const moduleShape: ModuleShape = style?.moduleShape || 'square';
+ const eyeFrameShape: EyeFrameShape = style?.eyeFrameShape || 'square';
+ const eyeBallShape: EyeBallShape = style?.eyeBallShape || 'square';
+ const gradientMode = style?.gradientMode ?? 'none';
+ const gradientTo = style?.gradientTo || fg;
+
+ const logoUrl = style?.logoUrl || '';
+ // Logo edge length as a share of the code, capped like the editor caps it.
+ const logoSpan = logoUrl ? Math.min((style?.logoSize ?? 24) / 200, 0.28) : 0;
+
+ // Always H, matching the interactive editor.
+ //
+ // This started at 'M' for plain codes, and one payload out of three then
+ // failed to decode at three of four render sizes - reproducibly, not as
+ // noise. A batch of 500 headed for print is the worst possible place to save
+ // a few modules, and preview and batch drifting apart is exactly what sharing
+ // the shape functions was supposed to prevent.
+ const ec = 'H';
+
+ const qr = QRCodeLib.create(value || ' ', { errorCorrectionLevel: ec });
+ const count = qr.modules.size;
+ const data = qr.modules.data;
+ const total = count + margin * 2;
+ const cell = size / total;
+ const offset = margin * cell;
+
+ const at = (r: number, c: number): boolean =>
+ r >= 0 && c >= 0 && r < count && c < count && data[r * count + c] === 1;
+
+ // Modules behind the logo are dropped, not painted over. Half-covered modules
+ // confuse some decoders; removing them lets error correction do its job.
+ const logoFrom = logoUrl ? Math.floor((count * (1 - logoSpan)) / 2) : -1;
+ const logoTo = logoUrl ? Math.ceil((count * (1 + logoSpan)) / 2) : -1;
+ const underLogo = (r: number, c: number) =>
+ Boolean(logoUrl) && r >= logoFrom && r < logoTo && c >= logoFrom && c < logoTo;
+
+ const modulePaths: string[] = [];
+ for (let r = 0; r < count; r++) {
+ for (let c = 0; c < count; c++) {
+ if (!at(r, c) || isInEye(r, c, count)) continue;
+ if (underLogo(r, c)) continue;
+ modulePaths.push(
+ moduleShapePath(
+ moduleShape,
+ offset + c * cell,
+ offset + r * cell,
+ cell,
+ {
+ top: at(r - 1, c),
+ bottom: at(r + 1, c),
+ left: at(r, c - 1),
+ right: at(r, c + 1),
+ },
+ r,
+ c
+ )
+ );
+ }
+ }
+
+ const eyes: [number, number][] = [
+ [0, 0],
+ [0, count - 7],
+ [count - 7, 0],
+ ];
+
+ const frames = eyes
+ .map(([r, c]) =>
+ eyeFramePath(eyeFrameShape, offset + c * cell, offset + r * cell, cell * 7)
+ )
+ .map((d) => ` `)
+ .join('');
+
+ const balls = eyes
+ .map(([r, c]) =>
+ eyeBallPath(eyeBallShape, offset + (c + 2) * cell, offset + (r + 2) * cell, cell * 3)
+ )
+ .map((d) => ` `)
+ .join('');
+
+ const gradId = 'g';
+ const fill = gradientMode === 'none' ? fg : `url(#${gradId})`;
+
+ const defs =
+ gradientMode === 'none'
+ ? ''
+ : gradientMode === 'linear'
+ ? ` `
+ : ` `;
+
+ return [
+ ``,
+ defs,
+ ` `,
+ ` `,
+ frames.split('FILL').join(fill),
+ balls.split('FILL').join(fill),
+ logoUrl
+ ? ` `
+ : '',
+ ' ',
+ ].join('');
+}
diff --git a/src/lib/revops.ts b/src/lib/revops.ts
index d1dc6cf..ae85e3d 100644
--- a/src/lib/revops.ts
+++ b/src/lib/revops.ts
@@ -96,9 +96,9 @@ export const JOB_ROLE_OPTIONS: OnboardingOption[] = [
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: '2_5', label: '2-5' },
+ { value: '6_20', label: '6-20' },
+ { value: '21_100', label: '21-100' },
{ value: '100_plus', label: '100+' },
];
diff --git a/src/lib/session.ts b/src/lib/session.ts
index 74abdeb..a6e68af 100644
--- a/src/lib/session.ts
+++ b/src/lib/session.ts
@@ -16,7 +16,7 @@ export const AUTH_COOKIE_NAME = 'userId';
function getSecret(): string {
const secret = process.env.NEXTAUTH_SECRET;
if (!secret) {
- throw new Error('NEXTAUTH_SECRET is not set — cannot sign or verify session cookies');
+ throw new Error('NEXTAUTH_SECRET is not set - cannot sign or verify session cookies');
}
return secret;
}
diff --git a/src/lib/temp-industries-1.ts b/src/lib/temp-industries-1.ts
index 0bd862b..e2af7cc 100644
--- a/src/lib/temp-industries-1.ts
+++ b/src/lib/temp-industries-1.ts
@@ -86,7 +86,7 @@ export const missingIndustries: IndustryPage[] = [
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",
+ 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" },
diff --git a/src/lib/temp-industries-2.ts b/src/lib/temp-industries-2.ts
index 8d17df5..17ea079 100644
--- a/src/lib/temp-industries-2.ts
+++ b/src/lib/temp-industries-2.ts
@@ -68,7 +68,7 @@ export const missingIndustries2: IndustryPage[] = [
],
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: "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." }
],
@@ -94,7 +94,7 @@ export const missingIndustries2: IndustryPage[] = [
{ 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: "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." }
],
@@ -133,7 +133,7 @@ export const missingIndustries2: IndustryPage[] = [
{ 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: "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." }
],
@@ -173,7 +173,7 @@ export const missingIndustries2: IndustryPage[] = [
],
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: "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: [
diff --git a/src/lib/temp-industries-3.ts b/src/lib/temp-industries-3.ts
index 94dd3ef..1d566d5 100644
--- a/src/lib/temp-industries-3.ts
+++ b/src/lib/temp-industries-3.ts
@@ -29,7 +29,7 @@ export const missingIndustries3: IndustryPage[] = [
],
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: "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." }
],
@@ -55,7 +55,7 @@ export const missingIndustries3: IndustryPage[] = [
{ 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: "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." }
],
@@ -69,7 +69,7 @@ export const missingIndustries3: IndustryPage[] = [
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: "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: [
@@ -94,9 +94,9 @@ export const missingIndustries3: IndustryPage[] = [
{ 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: "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." }
+ { 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",
@@ -133,7 +133,7 @@ export const missingIndustries3: IndustryPage[] = [
{ 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: "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." }
],
@@ -172,9 +172,9 @@ export const missingIndustries3: IndustryPage[] = [
{ 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: "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." }
+ { 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",
@@ -185,7 +185,7 @@ export const missingIndustries3: IndustryPage[] = [
],
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: "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." }
],
diff --git a/src/lib/temp-industries-4.ts b/src/lib/temp-industries-4.ts
index a1a75ff..b7cdea7 100644
--- a/src/lib/temp-industries-4.ts
+++ b/src/lib/temp-industries-4.ts
@@ -17,7 +17,7 @@ export const missingIndustries4: IndustryPage[] = [
],
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: "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: [
@@ -55,7 +55,7 @@ export const missingIndustries4: IndustryPage[] = [
{ 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: "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." }
],
@@ -108,7 +108,7 @@ export const missingIndustries4: IndustryPage[] = [
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: "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: [
@@ -172,7 +172,7 @@ export const missingIndustries4: IndustryPage[] = [
{ 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: "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." }
],
diff --git a/src/lib/temp-industries-5.ts b/src/lib/temp-industries-5.ts
index 1431783..fbd6371 100644
--- a/src/lib/temp-industries-5.ts
+++ b/src/lib/temp-industries-5.ts
@@ -68,7 +68,7 @@ export const missingIndustries5: IndustryPage[] = [
],
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: "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." }
],
@@ -94,7 +94,7 @@ export const missingIndustries5: IndustryPage[] = [
{ 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: "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." }
],
diff --git a/src/lib/testimonial-data.ts b/src/lib/testimonial-data.ts
index 9a9ec00..ea578f2 100644
--- a/src/lib/testimonial-data.ts
+++ b/src/lib/testimonial-data.ts
@@ -6,7 +6,7 @@ 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!",
+ 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",
@@ -22,7 +22,7 @@ export const testimonials: Testimonial[] = [
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.",
+ 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",
@@ -54,7 +54,7 @@ export const testimonials: Testimonial[] = [
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.",
+ 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",
@@ -86,7 +86,7 @@ export const testimonials: Testimonial[] = [
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.",
+ 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",
@@ -102,7 +102,7 @@ export const testimonials: Testimonial[] = [
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.",
+ 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",
@@ -118,7 +118,7 @@ export const testimonials: Testimonial[] = [
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.",
+ 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",
@@ -134,7 +134,7 @@ export const testimonials: Testimonial[] = [
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.",
+ 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",
@@ -150,7 +150,7 @@ export const testimonials: Testimonial[] = [
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.",
+ 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",
@@ -166,7 +166,7 @@ export const testimonials: Testimonial[] = [
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.",
+ 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",
@@ -182,7 +182,7 @@ export const testimonials: Testimonial[] = [
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.",
+ 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",
diff --git a/src/lib/validationSchemas.ts b/src/lib/validationSchemas.ts
index c854a8a..166ffbf 100644
--- a/src/lib/validationSchemas.ts
+++ b/src/lib/validationSchemas.ts
@@ -9,11 +9,38 @@ import { z } from 'zod';
// QR Code Schemas
// ==========================================
+/**
+ * Zod strips unknown keys silently. This schema therefore has to list every
+ * design field that is actually stored - otherwise the moment anyone switches
+ * the route from saving `body.style` to saving the parsed output, every shape,
+ * gradient and logo disappears without an error anywhere.
+ */
export const qrStyleSchema = z.object({
fgColor: z.string().regex(/^#[0-9A-F]{6}$/i, 'Invalid foreground color format').optional(),
bgColor: z.string().regex(/^#[0-9A-F]{6}$/i, 'Invalid background color format').optional(),
+ foregroundColor: z.string().optional(),
+ backgroundColor: z.string().optional(),
cornerStyle: z.enum(['square', 'rounded']).optional(),
size: z.number().min(100).max(1000).optional(),
+ moduleShape: z
+ .enum([
+ 'square', 'rounded', 'dots', 'classy', 'classy-rounded',
+ 'diamond', 'star', 'hexagon', 'plus', 'mosaic', 'liquid',
+ ])
+ .optional(),
+ eyeFrameShape: z.enum(['square', 'rounded']).optional(),
+ eyeBallShape: z.enum(['square', 'rounded', 'circle', 'diamond', 'hexagon']).optional(),
+ gradientMode: z.enum(['none', 'linear', 'radial']).optional(),
+ gradientTo: z.string().optional(),
+ frameType: z.string().optional(),
+ imageSettings: z
+ .object({
+ src: z.string(),
+ height: z.number().optional(),
+ width: z.number().optional(),
+ excavate: z.boolean().optional(),
+ })
+ .optional(),
});
export const createQRSchema = z.object({
@@ -31,12 +58,33 @@ export const createQRSchema = z.object({
tags: z.array(z.string()).optional(),
- style: z.object({
- foregroundColor: z.string().optional(),
- backgroundColor: z.string().optional(),
- cornerStyle: z.enum(['square', 'rounded']).optional(),
- size: z.number().optional(),
- }).optional(),
+ style: z
+ .object({
+ foregroundColor: z.string().optional(),
+ backgroundColor: z.string().optional(),
+ cornerStyle: z.enum(['square', 'rounded']).optional(),
+ size: z.number().optional(),
+ moduleShape: z
+ .enum([
+ 'square', 'rounded', 'dots', 'classy', 'classy-rounded',
+ 'diamond', 'star', 'hexagon', 'plus', 'mosaic', 'liquid',
+ ])
+ .optional(),
+ eyeFrameShape: z.enum(['square', 'rounded']).optional(),
+ eyeBallShape: z.enum(['square', 'rounded', 'circle', 'diamond', 'hexagon']).optional(),
+ gradientMode: z.enum(['none', 'linear', 'radial']).optional(),
+ gradientTo: z.string().optional(),
+ frameType: z.string().optional(),
+ imageSettings: z
+ .object({
+ src: z.string(),
+ height: z.number().optional(),
+ width: z.number().optional(),
+ excavate: z.boolean().optional(),
+ })
+ .optional(),
+ })
+ .optional(),
});
export const updateQRSchema = z.object({
@@ -53,6 +101,11 @@ export const updateQRSchema = z.object({
style: qrStyleSchema.optional(),
isActive: z.boolean().optional(),
+
+ // Pausing frees a dynamic code slot without deleting the code or its scan
+ // history. Used by the upgrade modal so hitting the limit has an exit that
+ // does not cost money and does not destroy anything.
+ status: z.enum(['ACTIVE', 'PAUSED']).optional(),
});
export const bulkQRSchema = z.object({
@@ -115,23 +168,23 @@ export const resetPasswordSchema = z.object({
// Settings Schemas
// ==========================================
-export const updateProfileSchema = z.object({
- name: z.string()
- .min(2, 'Name must be at least 2 characters')
- .max(100, 'Name must be less than 100 characters')
- .trim(),
-});
-
-export const onboardingUpdateSchema = z.object({
- signupSourceSelfReported: z.string().max(100).optional(),
- primaryUseCase: z.string().max(100).optional(),
- primaryGoal: z.string().max(100).optional(),
- jobRole: z.string().max(100).optional(),
- companyName: z.string().max(200).optional(),
- companyWebsite: z.string().max(200).optional(),
- teamSizeBucket: z.string().max(100).optional(),
- markProfileComplete: z.boolean().optional(),
-});
+export const updateProfileSchema = z.object({
+ name: z.string()
+ .min(2, 'Name must be at least 2 characters')
+ .max(100, 'Name must be less than 100 characters')
+ .trim(),
+});
+
+export const onboardingUpdateSchema = z.object({
+ signupSourceSelfReported: z.string().max(100).optional(),
+ primaryUseCase: z.string().max(100).optional(),
+ primaryGoal: z.string().max(100).optional(),
+ jobRole: z.string().max(100).optional(),
+ companyName: z.string().max(200).optional(),
+ companyWebsite: z.string().max(200).optional(),
+ teamSizeBucket: z.string().max(100).optional(),
+ markProfileComplete: z.boolean().optional(),
+});
export const changePasswordSchema = z.object({
currentPassword: z.string()