diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..7f455d9 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,39 @@ +## Summary + +- + +## Change Type + +- [ ] QRMaster SEO page +- [ ] QRMaster landing/tool page +- [ ] QRMaster conversion/pricing change +- [ ] GreenLens content/ASO workflow +- [ ] Bug fix +- [ ] Other + +## SEO / Content Review + +- [ ] Primary search intent is clear. +- [ ] Metadata is present and specific. +- [ ] Exactly one H1 is rendered for each new or changed page. +- [ ] Internal links are added to relevant money pages. +- [ ] CTA is specific to the page/use case. +- [ ] Duplicate or thin content risk was checked. +- [ ] Schema/structured data was added or intentionally skipped. + +## Verification + +- [ ] Build passes. +- [ ] Lint passes. +- [ ] Links/CTAs checked. +- [ ] Screenshots or notes included for UI changes. + +## Codex Review Prompt + +For QRMaster SEO/page changes, run: + +```text +Use docs/automations/qrmaster-pr-seo-review.md and review this PR for SEO, +conversion, internal linking, duplicate content, schema, and build/lint risk. +``` + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2446693..38d5ac8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI -on: [push] +on: [push, pull_request] jobs: build: @@ -23,4 +23,4 @@ jobs: run: npm run build - name: Run linter - run: npm run lint \ No newline at end of file + run: npm run lint diff --git a/.impeccable-live.json b/.impeccable-live.json new file mode 100644 index 0000000..4b86c3e --- /dev/null +++ b/.impeccable-live.json @@ -0,0 +1 @@ +{"pid":23720,"port":8400,"token":"99ca8ad6-3aa6-44f6-9b64-25921f55724b"} \ No newline at end of file diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 0000000..8a4f4f1 --- /dev/null +++ b/PRODUCT.md @@ -0,0 +1,51 @@ +# Product + +## Register + +brand + +## Users + +Two primary personas, both aesthetically-driven professionals who have outgrown generic tools: + +**Marketing / Agency Lead** — manages multiple client campaigns simultaneously. Needs organized folders, detailed analytics, and design output that doesn't betray itself as "internet freeware." They evaluate tools by how their outputs look to clients, not just how the tool works internally. + +**Modern Restaurateur** — owner of a boutique restaurant, cafe, or hotel. Their physical space is carefully designed; their digital touchpoints must match. They rely on Dynamic QR codes to swap menu PDFs and URLs seasonally without reprinting expensive acrylic table stands. Brand consistency between print and digital is non-negotiable for them. + +**Arrival context:** Both land on QR Master after being burned by "free" tools that expired, injected third-party ads, or looked visually cheap. They are actively comparing options and arrive skeptical. They're not discovering QR tools for the first time — they're looking for a permanent, professional home. + +## Product Purpose + +QR Master is a precision QR code platform — creation, dynamic editing, and analytics — for professionals who refuse to compromise on aesthetics. Success means users choose QR Master not because they have to, but because they want to: the tool itself feels like an extension of their creative workflow, not a clunky utility they tolerate. + +## Brand Personality + +**Confident, Minimal, Crafted.** + +"The Leica of QR Generators." A precision instrument that earns trust through intentionality, not decoration. Every pixel deliberate. Think Linear's pro-tool clarity, Raycast's utilitarian beauty, Framer's implied creative freedom — combined into something that feels high-performance without performing. + +Tone of voice: direct and assured. No hedging, no exclamation points for emphasis, no "Amazing!" copy. Let the product speak. Copy is short, specific, and treats the user as a professional. + +## Anti-references + +- **SEO ad farms** (QR-Code-Generator.com style): cluttered sidebars, aggressive upsell banners, walls of keyword-stuffed text. The opposite of QR Master. +- **Bubbly link-in-bio tools**: neon gradients, Gen-Z playfulness, the Linktree aesthetic. QR Master is for businesses, not social profiles. +- **Legacy enterprise software**: cold gray, mechanical, joyless. High-end is not the same as corporate. QR Master should feel premium, not bureaucratic. + +## Design Principles + +1. **Precision over decoration.** Every element earns its place. No UX furniture — no gratuitous dividers, decorative gradients, or filler icons. If removing it doesn't hurt, remove it. + +2. **Show, don't explain.** The product's quality is demonstrated by how the interface looks and behaves, not described in marketing copy. A beautifully rendered QR preview communicates more than three bullet points about "custom branding." + +3. **Premium through restraint.** Sophistication comes from what's removed. More whitespace, fewer words, tighter hierarchy. The instinct to add is the enemy of craft. + +4. **Trustworthy at a glance.** Clarity and professionalism must be instantaneous — users arriving skeptical decide in 5 seconds. No clever puzzles, no mystery-meat navigation. Confidence is expressed through legibility. + +5. **Tool-like beauty.** Functional elegance, like a well-made physical instrument. Interactions should feel responsive and precise. Animations are subtle cues, not performances. The UI should feel fast even when it isn't. + +## Accessibility & Inclusion + +- **Standard**: WCAG 2.1 AA +- **Reduced motion**: Full `prefers-reduced-motion` support. Animations (where used) default to subtle fades or scale shifts; no motion for motion's sake. +- **Color contrast**: Critical for a QR creation tool. The dashboard must warn users when foreground/background color combinations produce insufficient contrast — both for readability and QR scannability. This is a functional requirement, not a nice-to-have. diff --git a/docs/automations/README.md b/docs/automations/README.md new file mode 100644 index 0000000..0a9764d --- /dev/null +++ b/docs/automations/README.md @@ -0,0 +1,45 @@ +# Codex Automation System + +This folder defines reusable Codex workflows for QRMaster and GreenLens Pro. +Use these as operating playbooks when asking Codex to run growth, SEO, content, +or app-store work. + +## Active Automations + +### QRMaster + +1. `qrmaster-pr-seo-review.md` + - Purpose: review every SEO, landing page, and conversion change before it is merged. + - Primary plugins/tools: GitHub, Codex. + - Primary skills: `ai-seo`, `content-strategy`, `careful`, `qa`. + +2. `qrmaster-seo-sprint-machine.md` + - Purpose: plan and produce a weekly SEO sprint from keyword backlog to PR-ready work. + - Primary plugins/tools: GitHub, Coupler or CSV exports, Codex. + - Primary skills: `content-strategy`, `ai-seo`, `copywriting`, `qa`. + +3. `qrmaster-broken-link-cta-checker.md` + - Purpose: catch broken internal links and broken CTAs after direct `main` + branch changes. + - Primary plugins/tools: Codex, GitHub Actions or local npm script. + - Primary skills: `qa`, `ai-seo`. + +### GreenLens Pro + +1. `greenlens-pain-mining-machine.md` + - Purpose: turn reviews, comments, competitor messaging, and search questions into + product, ASO, and content opportunities. + - Primary plugins/tools: Codex, Gmail, Coupler or CSV exports. + - Primary skills: `app-store-aso`, `content-strategy`, `copywriting`. + +2. `greenlens-viral-slideshow-machine.md` + - Purpose: turn validated plant pains into TikTok, Instagram, and Canva-ready + slideshow assets. + - Primary plugins/tools: Canva, Codex, Gmail/Fyxer for creator briefs. + - Primary skills: `content-strategy`, `copywriting`, `ad-creative`, `app-store-aso`. + +## Operating Rule + +Do not automate publishing directly. Automate drafts, PRs, reviews, and packaged +outputs first. A human should approve live SEO pages, store metadata, influencer +messages, and paid/conversion changes. diff --git a/docs/automations/greenlens-pain-mining-machine.md b/docs/automations/greenlens-pain-mining-machine.md new file mode 100644 index 0000000..113c257 --- /dev/null +++ b/docs/automations/greenlens-pain-mining-machine.md @@ -0,0 +1,160 @@ +# GreenLens Pro Pain Mining Machine + +## Goal + +Turn real plant-owner pains into content, ASO, influencer, landing page, and +product opportunities. + +## Why This Exists + +GreenLens Pro should be driven by what users actually worry about: +yellow leaves, brown spots, root rot, overwatering, underwatering, pests, +curling leaves, and not knowing what to do next. + +## Plugins And Skills + +| Need | Use | +|---|---| +| Review/comment exports | Coupler, CSV exports, Codex | +| App Store optimization | `app-store-aso` skill | +| Content clustering | `content-strategy` skill | +| Copy and hooks | `copywriting` skill | +| Support/outreach drafting | Gmail/Fyxer plugin | +| Product issue creation | GitHub plugin | + +## Data Sources + +Use any available source, but label the source for every pain: + +- App Store competitor reviews +- Google Play competitor reviews +- Reddit plant-care threads +- TikTok or Instagram comments +- Google autocomplete or People Also Ask exports +- Support emails or user feedback +- Existing GreenLens analytics or onboarding responses + +## Pain Taxonomy + +Cluster each item into one primary category: + +- Yellow leaves +- Brown spots +- Root rot +- Overwatering +- Underwatering +- Curling leaves +- Drooping leaves +- Pests +- Light problems +- Soil and repotting +- Beginner confusion +- Diagnosis trust +- Price/paywall objection +- App usability issue + +## Scoring Model + +Score each pain from 0-100: + +| Factor | Weight | +|---|---:| +| User urgency | 30 | +| App fit | 25 | +| Content virality | 20 | +| ASO/search value | 15 | +| Product learning value | 10 | + +Prioritize urgent, visual, diagnosis-driven pains where GreenLens can credibly +help the user decide what to check next. + +## Weekly Output + +Produce: + +1. Top 20 pains. +2. Top 10 social hooks. +3. Top 5 ASO keyword opportunities. +4. Top 5 blog or landing page ideas. +5. Top 5 product issues or feature hypotheses. +6. Top 10 influencer angles. + +## Codex Pain Mining Prompt + +```text +Run the GreenLens Pro Pain Mining Machine. + +Use: +- docs/automations/greenlens-pain-mining-machine.md +- app-store-aso skill +- content-strategy skill + +Input source: [reviews/comments/export/pasted text] +Market: [US / DE / global] +Platform focus: [iOS / Android / both] + +Tasks: +1. Extract raw plant-owner pains. +2. Cluster them into the GreenLens pain taxonomy. +3. Score each pain by urgency, app fit, virality, ASO value, and product learning. +4. Convert winners into: + - social hooks + - ASO keyword ideas + - blog/landing page ideas + - product issues + - influencer outreach angles + +Do not invent source quotes. If evidence is weak, label it as hypothesis. +``` + +## Output Template + +```markdown +# GreenLens Pain Mining Report + +## Source Summary + +## Top Pains +| Rank | Pain | Source | Score | Why it matters | +|---|---|---|---:|---| + +## Hook Backlog + +## ASO Opportunities + +## Product Issues + +## Influencer Angles + +## Next Actions +``` + +## Product Issue Template + +```markdown +Title: [Feature or improvement] + +User pain: +[What the user is struggling with] + +Hypothesis: +If GreenLens [change], users will [outcome]. + +Acceptance criteria: +- [criterion] +- [criterion] +- [criterion] + +Measurement: +- activation +- scan completion +- paywall conversion +- retention +``` + +## Success Criteria + +- Every recommendation traces back to a real pain or explicitly marked hypothesis. +- Top pains can feed both ASO and social content. +- Product issues are concrete enough for GitHub. + diff --git a/docs/automations/greenlens-viral-slideshow-machine.md b/docs/automations/greenlens-viral-slideshow-machine.md new file mode 100644 index 0000000..0c079a4 --- /dev/null +++ b/docs/automations/greenlens-viral-slideshow-machine.md @@ -0,0 +1,139 @@ +# GreenLens Pro Viral Slideshow Machine + +## Goal + +Convert validated plant pains into TikTok, Instagram, and Canva-ready slideshow +assets that drive awareness and app downloads. + +## Plugins And Skills + +| Need | Use | +|---|---| +| Creative generation | Canva plugin | +| Hook and caption writing | `copywriting`, `ad-creative` skills | +| Content planning | `content-strategy` skill | +| ASO alignment | `app-store-aso` skill | +| Creator briefs and outreach | Gmail/Fyxer plugin | + +## Required Input + +Use outputs from `greenlens-pain-mining-machine.md`: + +- pain cluster +- urgency score +- source evidence +- target audience +- desired CTA +- app positioning angle + +## Content Pillars + +- Diagnosis before guessing +- Overwatering mistakes +- Yellow leaves +- Brown spots +- Root rot warnings +- Beginner plant rescue +- Plant symptoms explained +- "Do not water yet" warnings +- App scan/use-case demos + +## Slideshow Formula + +1. Hook: direct warning, contradiction, or curiosity. +2. Problem: show the common wrong assumption. +3. Explanation: simple plant-care reason. +4. Check: what the user should inspect first. +5. Risk: what happens if they guess. +6. GreenLens bridge: scan or diagnose before acting. +7. CTA: download, scan, or save. + +## Hook Patterns + +- "Do not water your plant before checking this." +- "Yellow leaves do not always mean your plant is thirsty." +- "Brown spots can mean more than sunburn." +- "Your plant was warning you before it started dying." +- "Overwatering often looks like underwatering." +- "Scan before you guess." + +## Canva Direction + +Use GreenLens as a calm diagnosis-first plant app: + +- natural plant photography or close-up symptom imagery +- clear readable overlay text +- botanical but not decorative-only +- show symptoms clearly +- app screenshot or phone mockup only when it explains the action +- avoid vague wellness aesthetics that do not show the plant problem + +## Codex Slideshow Prompt + +```text +Run the GreenLens Pro Viral Slideshow Machine. + +Use: +- docs/automations/greenlens-pain-mining-machine.md +- docs/automations/greenlens-viral-slideshow-machine.md +- app-store-aso skill +- content-strategy skill +- copywriting/ad-creative skills + +Input pain cluster: [pain] +Audience: [beginner plant owners / plant rescue followers / houseplant collectors] +CTA: [Download GreenLens Pro / Scan your plant / Save this checklist] +Channel: [TikTok / Instagram / both] +Quantity: [number] + +Return for each concept: +1. hook +2. 5-7 slide script +3. visual direction per slide +4. Canva prompt +5. caption +6. hashtags +7. ASO keyword tie-in +8. creator brief version + +Rules: +- Keep claims educational, not medical/certain beyond evidence. +- Do not promise perfect diagnosis. +- Make the symptom visually inspectable. +- The app CTA should feel like the next practical step, not a hard sell. +``` + +## Output Template + +```markdown +# GreenLens Slideshow Pack: [Pain Cluster] + +## Concept 1: [Hook] + +### Slides +1. [text] -- [visual] +2. [text] -- [visual] +3. [text] -- [visual] +4. [text] -- [visual] +5. [text] -- [visual] +6. [text] -- [visual] +7. [text] -- [visual] + +### Canva Prompt + +### Caption + +### Hashtags + +### ASO Tie-In + +### Creator Brief +``` + +## Quality Bar + +- The first slide must be understandable in under 2 seconds. +- Every slide should be shorter than 12 words when possible. +- The visual must show the symptom or action, not just a plant mood shot. +- The final CTA should match the pain: scan, check, save, or download. + diff --git a/docs/automations/qrmaster-broken-link-cta-checker.md b/docs/automations/qrmaster-broken-link-cta-checker.md new file mode 100644 index 0000000..800a47a --- /dev/null +++ b/docs/automations/qrmaster-broken-link-cta-checker.md @@ -0,0 +1,57 @@ +# QRMaster Broken Link + CTA Checker + +## Goal + +Catch broken internal links and broken conversion CTAs on `main`, especially +after direct edits without a pull request. + +## What It Checks + +- Static internal `href` values in source files. +- Static `router.push("/...")` destinations. +- Internal links against known Next.js app routes and files in `public/`. +- CTA-like links such as "Get started", "Create QR", "Start free", + "Generate QR", "Pricing", and "Upgrade". +- Pages that appear to have no obvious CTA link. + +## Command + +```bash +npm run check:links +``` + +The command prints a JSON report. It exits with a non-zero status if broken +internal links or broken CTA links are found. + +## Known Limits + +- Dynamic CMS/blog slugs are allowed by prefix and not fully validated. +- Runtime-only links built from variables are skipped. +- External links are not checked by this local script. +- This is a fast safety check, not a full crawl of the deployed website. + +## Codex Automation Prompt + +```text +Run the QRMaster Broken Link + CTA Checker. + +Use: +- docs/automations/qrmaster-broken-link-cta-checker.md +- scripts/check-links-and-ctas.js + +Run npm run check:links. Review the JSON report and summarize: +1. broken internal links +2. broken CTA links +3. important pages without obvious CTAs +4. concrete fixes with file paths + +If the script fails, inspect the listed files and propose the smallest safe fix. +Do not modify production configuration automatically. +``` + +## Success Criteria + +- No broken internal links. +- No broken CTA links. +- Important marketing, tool, and pricing pages have a clear CTA. + diff --git a/docs/automations/qrmaster-pr-seo-review.md b/docs/automations/qrmaster-pr-seo-review.md new file mode 100644 index 0000000..8336535 --- /dev/null +++ b/docs/automations/qrmaster-pr-seo-review.md @@ -0,0 +1,163 @@ +# QRMaster PR SEO Review + +## Goal + +Catch SEO, conversion, content-quality, and technical issues before a QRMaster +page change is merged. + +## Use When + +- A PR changes landing pages, tool pages, comparison pages, blog posts, metadata, + schema, sitemap behavior, internal links, pricing copy, or CTAs. +- Codex generated new SEO pages from `marketing/programmatic-seo-top-50.md`. +- Existing pages were refreshed from Google Search Console or keyword data. + +## Plugins And Skills + +| Need | Use | +|---|---| +| Diff and PR review | GitHub plugin | +| Code/content inspection | Codex | +| SEO/AEO review | `ai-seo` skill | +| Content intent and cluster fit | `content-strategy` skill | +| Build/lint verification | GitHub Actions, `qa` skill | +| Careful merge decision | `careful` skill | + +## Review Checklist + +### 1. Technical SEO + +- Exactly one H1 on the rendered page. +- Meta title exists and is specific to the page intent. +- Meta title places the primary keyword near the start where natural. +- Meta title stays under roughly 60 characters unless there is a clear reason. +- Meta description exists and promises the right outcome. +- Meta description includes the target keyword naturally, states the user benefit, + and stays concise enough to avoid likely truncation. +- Canonical URL is correct. +- Page is not accidentally noindexed. +- FAQ, Article, Product, Breadcrumb, or HowTo schema is valid where used. +- Sitemap and internal routing include the page where required. +- No broken internal links or CTA links. +- Language and locale are consistent. +- URL slug is short, descriptive, hyphenated, and does not include stale dates. +- Meaningful images have descriptive alt text and useful filenames where local + image handling allows it. +- Large visual assets are compressed or already optimized. +- Mobile layout is readable and CTAs are tappable. +- The page does not introduce obvious Core Web Vitals risks. +- Robots rules do not block important pages or desired AI/search crawlers. + +### 2. Search Intent + +- The first screen makes it obvious the page answers the target query. +- The opening paragraph states the problem directly. +- The page matches one primary intent only. +- The content is not a generic rewrite of another QRMaster page. +- The page includes concrete examples for its audience or use case. +- The target keyword intent is labeled as informational, commercial, + transactional, or navigational. +- The page covers the related subtopics a search or AI system would fan out to + for the main query. +- Each H2/H3 section answers the heading directly in the first sentence before + adding background or nuance. + +### 3. QRMaster Conversion Fit + +- Primary CTA is visible early. +- CTA copy matches the use case, not just generic "Get started". +- The page explains why dynamic QR codes matter when links change after printing. +- Scan analytics or tracking is mentioned when relevant. +- Privacy/GDPR positioning is included where tracking is discussed. +- The copy avoids unsupported claims. + +### 4. Internal Linking + +- New page links to the relevant money page: + - `/dynamic-qr-code-generator` + - `/qr-code-tracking` + - `/bulk-qr-code-generator` + - `/pricing` + - relevant `/tools/...` page +- Existing related pages should link back to the new page. +- Anchor text is natural and varied. + +### 5. AI Search Extractability + +- Important answer blocks are self-contained. +- Comparison content uses tables where useful. +- FAQ questions are written in natural user language. +- Definitions answer the query in 40-60 words when possible. +- Claims that need evidence include a source or are framed as product positioning. +- Sections are focused on one question or subtopic at a time. +- Bullet lists, tables, and short paragraphs are used where they improve + extraction and scanning. +- The page can be cited by AI systems without relying on surrounding context. + +### 6. E-E-A-T And Quality + +- Content is accurate, current, and not copied from competitors. +- Any competitor, pricing, legal, privacy, or compliance claim is verified or + clearly avoided. +- The page adds QRMaster-specific value, examples, workflows, or product context. +- The tone stays direct, useful, and trustworthy. + +## Codex Review Prompt + +```text +Review this QRMaster PR as an SEO, conversion, and technical quality gate. + +Use: +- docs/automations/qrmaster-pr-seo-review.md +- .agents/product-marketing-context.md +- marketing/programmatic-seo-top-50.md + +Check the diff for: +1. technical SEO issues +2. search intent mismatch +3. weak or missing CTA +4. duplicate/thin content +5. missing internal links +6. invalid or missing schema +7. weak AI/agentic-search extractability +8. missing visual/mobile/performance considerations +9. build/lint risks + +Return findings ordered by severity. For each finding include: +- file path +- exact line if possible +- why it matters +- concrete fix + +Also include: +- merge recommendation: approve / request changes +- required follow-up tasks +``` + +## Review Output Format + +```markdown +## QRMaster PR SEO Review + +Decision: Request changes + +### Findings +1. [High] Missing internal link to `/dynamic-qr-code-generator` +2. [Medium] CTA is too generic for the target intent +3. [Low] FAQ question overlaps with another page + +### Required Fixes +- Add contextual link from the "after printing" section to `/dynamic-qr-code-generator`. +- Change CTA from "Get started" to "Create an editable QR code for your flyer". + +### Verification +- Build: +- Lint: +- Link/schema check: +``` + +## Success Criteria + +- No high-severity SEO or conversion findings remain. +- Build and lint pass. +- The PR has a clear human approval before merge. diff --git a/docs/automations/qrmaster-seo-sprint-machine.md b/docs/automations/qrmaster-seo-sprint-machine.md new file mode 100644 index 0000000..395af6a --- /dev/null +++ b/docs/automations/qrmaster-seo-sprint-machine.md @@ -0,0 +1,211 @@ +# QRMaster SEO Sprint Machine + +## Goal + +Run a weekly controlled SEO sprint that chooses the right pages, creates or +updates them, adds internal links, and ships through a reviewed PR. + +## Why This Exists + +QRMaster should not publish random daily content. The goal is to build +commercially useful SEO clusters around dynamic QR codes, tracking, tool pages, +comparison pages, and industry workflows. + +## Plugins And Skills + +| Need | Use | +|---|---| +| Repository changes and PRs | GitHub plugin | +| Keyword and performance imports | Coupler, CSV exports, Google Search Console export | +| Page creation and refactors | Codex | +| SEO/content planning | `content-strategy`, `ai-seo` skills | +| Copy generation | `copywriting` skill | +| Verification | GitHub Actions, `qa` skill | + +## Weekly Inputs + +- Current keyword backlog: + - `marketing/programmatic-seo-top-50.md` + - `marketing/keyword-strategy-seo-plan.md` + - `seo-keywords.csv` +- Existing product positioning: + - `.agents/product-marketing-context.md` +- Performance data when available: + - Google Search Console export + - signup/conversion report + - top landing pages by traffic +- Sprint focus: + - Dynamic QR + - Tracking/analytics + - Restaurant/menu QR + - Print marketing + - Bulk QR + - Comparison/alternatives + +## Scoring Model + +Score each candidate from 0-100: + +| Factor | Weight | +|---|---:| +| Product fit | 30 | +| Commercial intent | 25 | +| Differentiation potential | 15 | +| Cluster leverage | 10 | +| Search winability | 10 | +| Production effort | 10 | + +Do not select pages only because they have volume. Prefer pages where QRMaster +can naturally sell dynamic QR, scan tracking, bulk creation, or privacy-first +analytics. + +## On-Page And Agentic Search Rules + +Every new or refreshed page must follow these checks before review: + +- Identify the primary keyword and intent type: informational, commercial, + transactional, or navigational. +- Cover the query fan-out: list the related subtopics an AI/search system would + need to answer the query well. +- Put the primary keyword naturally near the start of the title tag and H1. +- Keep title tags under roughly 60 characters when possible. +- Keep meta descriptions concise, benefit-led, and naturally keyword-aligned. +- Use one clear H1 and a logical H2/H3 hierarchy. +- Start each section with a direct answer to the heading. +- Use short paragraphs, bullets, and comparison tables where they improve + scanning and AI extraction. +- Add descriptive internal links with natural anchor text. +- Add useful visuals, screenshots, or examples where the page needs them. +- Add schema when the page type supports it. +- Check mobile readability, CTA tap targets, and obvious speed risks. +- Verify robots/indexing assumptions for important SEO pages. + +## Default Weekly Sprint + +1. Select one cluster. +2. Create 3 new pages. +3. Refresh 2 existing pages with impressions, weak CTR, or position 8-20. +4. Add internal links in both directions. +5. Create one GitHub PR. +6. Run `qrmaster-pr-seo-review.md`. +7. Produce social and outreach drafts after the PR is ready. + +## Page Types + +### Tool Pages + +Examples: + +- `/tools/pdf-qr-code` +- `/tools/vcard-qr-code` +- `/tools/wifi-qr-code` +- `/tools/menu-qr-code` +- `/tools/google-review-qr-code` + +Must include: + +- direct tool-oriented hero +- use cases +- dynamic vs static guidance +- FAQ +- CTA into the app +- internal links to related use cases + +### Industry Workflow Pages + +Examples: + +- `/qr-code-for/restaurants/menu-updates` +- `/qr-code-for/events/check-in` +- `/qr-code-for/real-estate/open-house-flyers` + +Must include: + +- specific audience pain +- example workflow +- print-risk or tracking angle +- CTA matching the industry + +### Comparison Pages + +Examples: + +- `/compare/dynamic-vs-static-qr-codes` +- `/compare/free-vs-paid-qr-code-generator` +- `/compare/flowcode-alternative` + +Must include: + +- comparison table +- fair positioning +- current facts verified before publishing +- "who this is best for" section +- CTA to the best-fit QRMaster feature + +## Codex Sprint Prompt + +```text +Run the QRMaster SEO Sprint Machine. + +Use: +- docs/automations/qrmaster-seo-sprint-machine.md +- docs/automations/qrmaster-pr-seo-review.md +- .agents/product-marketing-context.md +- marketing/programmatic-seo-top-50.md +- marketing/keyword-strategy-seo-plan.md + +Sprint focus: [cluster] +Target output: 3 new SEO/tool pages, 2 page refreshes, internal links, and one PR-ready diff. + +Rules: +- Follow existing code and page patterns. +- Do not invent competitor pricing or claims. +- Prioritize dynamic QR, edit-after-print, analytics, bulk, and privacy-first messaging. +- Add metadata, FAQ/schema where local patterns support it. +- Apply the on-page and agentic search rules from the automation doc. +- Keep pages specific enough to avoid thin programmatic content. +- Run build/lint or explain why not. + +Return: +1. selected pages and scoring +2. target keyword, intent, and fan-out subtopics per page +3. files changed +4. internal links added +5. PR summary +6. SEO review status +7. follow-up social/outreach package +``` + +## Sprint Output Template + +```markdown +# QRMaster SEO Sprint: [Cluster] + +## Selected Work +| Type | URL | Score | Reason | +|---|---|---:|---| + +## Keyword Intent And Fan-Out + +| URL | Primary keyword | Intent | Fan-out subtopics | +|---|---|---|---| + +## New Pages + +## Updated Pages + +## Internal Links + +## PR Summary + +## Verification + +## Social/Outreach Follow-Up +``` + +## Success Criteria + +- Each new page has clear commercial intent or cluster leverage. +- Refreshed pages have a measurable reason for the update. +- Internal links support money pages. +- PR SEO Review passes before merge. diff --git a/meta-fix.js b/meta-fix.js new file mode 100644 index 0000000..71c69da --- /dev/null +++ b/meta-fix.js @@ -0,0 +1,23 @@ +import { config } from 'dotenv'; +config(); + +const TOKEN = process.env.META_ACCESS_TOKEN; +const BASE = 'https://graph.facebook.com/v21.0'; + +async function api(path, method = 'GET', body) { + const url = new URL(`${BASE}/${path}`); + url.searchParams.set('access_token', TOKEN); + const res = await fetch(url.toString(), { + method, + headers: body ? { 'Content-Type': 'application/json' } : undefined, + body: body ? JSON.stringify(body) : undefined, + }); + const data = await res.json(); + if (!res.ok) throw new Error(JSON.stringify(data)); + return data; +} + +console.log('Pausing orphaned ad sets...'); +await api('6968509692127', 'POST', { status: 'PAUSED' }); +await api('6958800756527', 'POST', { status: 'PAUSED' }); +console.log('Done: Paused 2 orphaned ad sets (New Sales Ad Set, New Sales ad set)'); \ No newline at end of file diff --git a/quora_suche.txt b/quora_suche.txt new file mode 100644 index 0000000..84e6a21 Binary files /dev/null and b/quora_suche.txt differ diff --git a/read-inbox.mjs b/read-inbox.mjs new file mode 100644 index 0000000..71262b8 --- /dev/null +++ b/read-inbox.mjs @@ -0,0 +1,49 @@ +import tls from 'node:tls'; + +function readMessages(seqs) { + return new Promise((resolve, reject) => { + const socket = tls.connect({ host: 'imap.qrmaster.net', port: 993 }, () => { + let buf = ''; + let step = 0; + const results = {}; + + socket.on('data', (chunk) => { + buf += chunk.toString(); + const lines = buf.split('\r\n'); + buf = lines.pop(); + + for (const line of lines) { + if (step === 0 && line.includes('* OK')) { + socket.write(`A1 LOGIN timo@qrmaster.net fiesta\r\n`); + step = 1; + } else if (step === 1 && line.startsWith('A1 OK')) { + socket.write(`A2 SELECT INBOX\r\n`); + step = 2; + } else if (step === 2 && line.startsWith('A2 OK')) { + socket.write(`A3 FETCH ${seqs.join(',')} (BODY.PEEK[1])\r\n`); + step = 3; + } else if (step === 3) { + if (line.startsWith('A3 OK')) { + socket.write(`A4 LOGOUT\r\n`); + resolve(results); + } + const m = line.match(/^\* (\d+) FETCH/); + if (m) results[m[1]] = { body: '' }; + const curr = Object.keys(results).at(-1); + if (curr && line && !line.match(/^\* \d+ FETCH/) && !line.startsWith('A3') && line !== ')') { + results[curr].body += line + '\n'; + } + } + } + }); + + socket.on('error', reject); + }); + }); +} + +const r = await readMessages([954, 990, 997]); +for (const [seq, msg] of Object.entries(r)) { + console.log(`\n=== SEQ ${seq} ===`); + console.log(msg.body.slice(0, 1500)); +} diff --git a/scripts/check-links-and-ctas.js b/scripts/check-links-and-ctas.js new file mode 100644 index 0000000..8b454ef --- /dev/null +++ b/scripts/check-links-and-ctas.js @@ -0,0 +1,249 @@ +const fs = require("fs"); +const path = require("path"); + +const root = process.cwd(); +const scanDirs = ["src", "marketing", "articles", "blog-posts-improved"]; +const sourceExtensions = new Set([".ts", ".tsx", ".js", ".jsx", ".md", ".mdx"]); +const publicDir = path.join(root, "public"); +const appDir = path.join(root, "src", "app"); + +const ignoredPrefixes = [ + "/api/", + "/_next/", + "/auth/", + "/r/", + "/qr/", + "/scan/", +]; + +const knownDynamicPrefixes = [ + "/blog/", + "/learn/", + "/authors/", + "/qr-code-for/", + "/use-cases/", +]; + +const ctaPatterns = [ + /get started/i, + /start free/i, + /try free/i, + /create.*qr/i, + /generate.*qr/i, + /sign up/i, + /pricing/i, + /upgrade/i, + /create.*free/i, + /start tracking/i, + /create.*editable/i, +]; + +const nonConversionPageParts = [ + "/contact/", + "/cookie-policy/", + "/privacy/", + "/press/", + "/authors/", + "/blog/", + "/newsletter/", +]; + +const findings = []; +const ctas = []; + +function walk(dir, files = []) { + if (!fs.existsSync(dir)) return files; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if (entry.name === "node_modules" || entry.name === ".next" || entry.name === ".git") continue; + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + walk(fullPath, files); + } else { + files.push(fullPath); + } + } + return files; +} + +function toPosix(value) { + return value.split(path.sep).join("/"); +} + +function routeFromPageFile(file) { + const rel = toPosix(path.relative(appDir, file)); + if (!rel.endsWith("/page.tsx") && !rel.endsWith("/page.ts") && !rel.endsWith("/route.ts")) { + return null; + } + + const parts = rel.split("/"); + parts.pop(); + const routeParts = parts.filter((part) => { + if (!part) return false; + if (part.startsWith("(") && part.endsWith(")")) return false; + return true; + }); + + if (routeParts.some((part) => part.startsWith("[") && part.endsWith("]"))) return null; + return "/" + routeParts.join("/"); +} + +function collectRoutes() { + const routes = new Set(["/"]); + for (const file of walk(appDir)) { + const route = routeFromPageFile(file); + if (route) routes.add(route === "/" ? "/" : route.replace(/\/$/, "")); + } + + for (const file of walk(publicDir)) { + const rel = "/" + toPosix(path.relative(publicDir, file)); + routes.add(rel); + } + + return routes; +} + +function normalizeHref(rawHref) { + if (!rawHref) return null; + let href = rawHref.trim(); + if (!href || href.startsWith("#")) return null; + if (/^(https?:|mailto:|tel:|sms:|javascript:|data:)/i.test(href)) return null; + + if (!href.startsWith("/")) return null; + href = href.split("#")[0].split("?")[0]; + if (href.length > 1) href = href.replace(/\/$/, ""); + return href || "/"; +} + +function isAllowedDynamicHref(href) { + if (ignoredPrefixes.some((prefix) => href.startsWith(prefix))) return true; + if (href.includes("[") || href.includes("${") || href.includes("`")) return true; + return knownDynamicPrefixes.some((prefix) => href.startsWith(prefix) && href !== prefix.replace(/\/$/, "")); +} + +function lineNumber(content, index) { + return content.slice(0, index).split(/\r?\n/).length; +} + +function extractHrefMatches(content) { + const matches = []; + const patterns = [ + /href\s*=\s*["']([^"']+)["']/g, + /href\s*=\s*{\s*["']([^"']+)["']\s*}/g, + /router\.push\(\s*["']([^"']+)["']\s*\)/g, + ]; + + for (const pattern of patterns) { + let match; + while ((match = pattern.exec(content)) !== null) { + matches.push({ href: match[1], index: match.index }); + } + } + return matches; +} + +function extractAnchors(content) { + const anchors = []; + const linkPattern = /([\s\S]*?)<\/Link>/g; + const anchorPattern = /([\s\S]*?)<\/a>/g; + + for (const pattern of [linkPattern, anchorPattern]) { + let match; + while ((match = pattern.exec(content)) !== null) { + const href = match[1] || match[2]; + const text = match[3] + .replace(/<[^>]*>/g, " ") + .replace(/\{[^}]*\}/g, " ") + .replace(/\s+/g, " ") + .trim(); + anchors.push({ href, text, index: match.index }); + } + } + return anchors; +} + +function sourceFiles() { + const files = []; + for (const dir of scanDirs) { + for (const file of walk(path.join(root, dir))) { + if (sourceExtensions.has(path.extname(file))) files.push(file); + } + } + return files; +} + +function check() { + const routes = collectRoutes(); + + for (const file of sourceFiles()) { + const content = fs.readFileSync(file, "utf8"); + const rel = toPosix(path.relative(root, file)); + + for (const item of extractHrefMatches(content)) { + const href = normalizeHref(item.href); + if (!href) continue; + if (routes.has(href) || isAllowedDynamicHref(href)) continue; + + findings.push({ + type: "broken-internal-link", + file: rel, + line: lineNumber(content, item.index), + href, + }); + } + + for (const anchor of extractAnchors(content)) { + const text = anchor.text || ""; + if (!ctaPatterns.some((pattern) => pattern.test(text))) continue; + + const href = normalizeHref(anchor.href); + const status = !href + ? "external-or-non-http" + : routes.has(href) || isAllowedDynamicHref(href) + ? "ok" + : "broken"; + + ctas.push({ + file: rel, + line: lineNumber(content, anchor.index), + text: text.slice(0, 100), + href: anchor.href, + status, + }); + } + } + + const brokenCtas = ctas.filter((cta) => cta.status === "broken"); + const weakFiles = sourceFiles().filter((file) => { + const rel = toPosix(path.relative(root, file)); + if (!rel.includes("src/app/") || !rel.endsWith("/page.tsx")) return false; + if (!rel.includes("(marketing)")) return false; + if (rel.includes("[")) return false; + if (nonConversionPageParts.some((part) => rel.includes(part))) return false; + + const content = fs.readFileSync(file, "utf8"); + return !extractAnchors(content).some((anchor) => + ctaPatterns.some((pattern) => pattern.test(anchor.text || "")), + ); + }); + + const report = { + checkedAt: new Date().toISOString(), + routeCount: routes.size, + filesChecked: sourceFiles().length, + brokenInternalLinks: findings, + ctaSummary: { + total: ctas.length, + broken: brokenCtas.length, + sample: ctas.slice(0, 50), + }, + pagesWithoutObviousCta: weakFiles.map((file) => toPosix(path.relative(root, file))).slice(0, 100), + }; + + console.log(JSON.stringify(report, null, 2)); + + if (findings.length > 0 || brokenCtas.length > 0) { + process.exitCode = 1; + } +} + +check(); diff --git a/src/app/(main)/(app)/create/page.tsx b/src/app/(main)/(app)/create/page.tsx index 91ce483..c9f1046 100644 --- a/src/app/(main)/(app)/create/page.tsx +++ b/src/app/(main)/(app)/create/page.tsx @@ -1004,49 +1004,51 @@ export default function CreatePage() { +
-
- -
- setForegroundColor(e.target.value)} - className="w-12 h-10 rounded border border-gray-300" - disabled={!canCustomizeColors} - /> - setForegroundColor(e.target.value)} - className="flex-1" - disabled={!canCustomizeColors} - /> -
-
- -
- -
- 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} - /> -
-
+
+ +
+ setForegroundColor(e.target.value)} + className="w-12 h-10 rounded border border-gray-300" + disabled={!canCustomizeColors} + /> + setForegroundColor(e.target.value)} + className="flex-1" + disabled={!canCustomizeColors} + />
+
+ +
+ +
+ 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} + /> +
+
+
+