Purpose: Standalone public/marketing site (Astro 7, static output, React islands) that will own https://prepaidqr.com after the apex cutover. Born 2026-08-04 from the public-site split (project/public-site-split-plan.md): every public page was ported from apps/dashboard’s (marketing)/(public) groups with copy, meta, and JSON-LD parity. Deployed to Cloudflare Workers static assets; live on https://public-dev.prepaidqr.com, production env reserved for prepaidqr.com at cutover.
Shows: 23 pages — / (full campaign landing), /learn + 8 articles, /about, /abuse, /privacy, /terms, /sub-processors, three free generators (/free-qr-code-generator, /free-vcard-qr-code-generator, /free-wifi-qr-code-generator), four workbench pages (/facebook-qr-code, /linkedin-qr-code, /youtube-qr-code, /google-review-qr-code), plus sitemap-index.xml, robots.txt, llms.txt, og.png, favicons/manifest, 404.
User can: read every public marketing/legal/learn surface · generate and download static QR codes entirely client-side on the seven tool pages · follow sign-in/sign-up/dashboard CTAs to https://app.prepaidqr.com.
Boundary: Owns page rendering, site chrome, SEO artifacts, the deploy config, and the staged app-path redirects. Does NOT own copy decisions (prose is ported verbatim from the dashboard sources, which stay canonical until the apex cutover — see the dual-maintenance bullet in (marketing)/KNOWLEDGE.md), pricing math (standalonePackPrice is a verbatim copy of the dashboard formula), auth (no Clerk here, ever), or the QR-code data model.
application/ld+json blocks and the (key-gated) PostHog snippet appear. React hydrates exclusively via client:load entries under src/islands/ on the seven tool pages. FAQ accordion and mobile nav are native <details>/<summary>; the article TOC is a static anchor list (dashboard’s IntersectionObserver scroll-spy deliberately dropped).prepaidqr.com: visible text byte-identical, titles/descriptions/canonicals/keywords identical, JSON-LD deep-equal. Deliberate divergences only: CTA hosts → app.prepaidqr.com; og:image/twitter:image added (production emitted none); learn articles emit og:type="article" via a Base/Site ogType prop.build: { format: 'file' } + Workers auto-trailing-slash: /learn/foo is the single 200 form; /learn/foo/ and .html variants 307 onto it, matching production Next exactly. canonicalUrl strips /index.html, .html, and trailing slashes; root canonical is bare https://prepaidqr.com.public/_redirects 301s /dashboard*, /sign-in*, /sign-up*, /qr-codes*, /billing*, /settings*, /api/* to https://app.prepaidqr.com (inert until the apex cutover; verified via wrangler dev). /dashboard targets /qr-codes because /dashboard is a Next route group, not a servable route.@astrojs/sitemap (inventory-agnostic build test asserts every dist page except 404 appears slash-less), robots.txt pointing at sitemap-index.xml, llms.txt byte-identical static copy of the dashboard route’s output, og.png exported from production’s dynamic /opengraph-image, favicons/manifest ported (the icon production actually serves — src/app/favicon.ico, not the dashboard’s unused public/ one).TrackedLink is replaced by data-track-event/data-track-* attributes plus ONE delegated click listener in Site.astro, emitted only when PUBLIC_POSTHOG_KEY is set; event names/payloads preserved 1:1 (nav, login/signup, footer, pricing CTA, faq-expand fires only on expand, mobile-nav-toggle reports next state). PostHog init mirrors the dashboard’s cookieless posture (EU hosts, cookieless_mode: 'always', autocapture/recording/surveys off).lib/qr/* (incl. the frame subsystem), hooks/, components/{qr,ui,brand}/, types/qr-code.ts under the @/* alias so ported imports resolve unchanged. Documented adaptations only: ObjectId → local type ObjectId = string; NEXT_PUBLIC_REDIRECT_DOMAIN → PUBLIC_REDIRECT_DOMAIN (needed only by QrPreview.staticRender, which no live page invokes)./abuse keeps its privacy posture — builds its own document head (not Base/Site) to emit noindex, nofollow, no canonical, and zero scripts, matching the dashboard’s KNOWLEDGE invariant for the abuse-report surface.<astro-static-slot>, breaking markup parity; the workbench pages therefore keep their static sections in per-page React content modules (components/qr-platform/*-page.tsx, components/qr-review/review-page.tsx) rendered server-side with no client directive.public-dev.prepaidqr.com); [env.production] = prepaidqr.com, workers_dev = false, deployed ONLY on explicit release (mirrors the repo’s dev→main discipline). Custom domains auto-provision DNS.</strong> on /privacy and /learn/what-is-a-dynamic-qr-code — deployed-build artifacts, sources correct; this app renders the source-intended text).