Landing Page Redesign Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
Goal: Rebuild the landing page around the payback calculator as the conversion north star, with prerendered static HTML so AI crawlers (GPTBot/ClaudeBot/PerplexityBot) read the full keyword-bearing content.
Architecture: 7 sections (hero → calculator → WhatsApp ops → Ruby → clinical depth → GEO FAQ → final CTA), all pure HTML/CSS/Tailwind — no three.js, no framer-motion, no HeroWave canvas in new sections. A Playwright postbuild script captures the rendered landing into dist/index.html; a clean shell (dist/app.html) becomes the SPA fallback for every other route. FAQ content lives in one data module that feeds both the UI and FAQPage JSON-LD.
Tech Stack: React 18 + Vite 6 + Tailwind, vitest for unit tests, @playwright/test (already a devDep) for prerendering, Cloudflare Pages (_redirects SPA fallback).
Spec: docs/superpowers/specs/2026-06-11-landing-redesign-design.md
Hard constraints (from spec + repo memory):
- Keep
ui/index.html<title>/meta/OG/SoftwareApplication JSON-LD verbatim (additive changes only). - CSP is
script-src 'self'— NO inline<script>with JS code (JSON-LDapplication/ld+jsonis fine, it’s data). The prerender guard must be an external file. ui/public/_headersis FROZEN — do not touch it._redirectsmay change.- First-person brand voice (“we”, “our platform”); never stuffed keywords; no invented stats/testimonials/logos; no prices in copy; Banknote icon only (never DollarSign); no superadmin mentions.
- Existing anchors must keep working: navbar links
#how-it-works,#features,#savings; other surfaces link to/onboardingand/legal?tab=book. - All work happens in
ui/. Run all commands fromui/unless stated otherwise.
Task 1: Payback math module (extracted, tested)
The calculator math currently lives inline inRoiCalculator.tsx. Extract it so the calculator component, hero copy, and tests share one source.
Files:
-
Create:
ui/src/lib/payback-math.ts -
Test:
ui/src/lib/__tests__/payback-math.test.ts - Step 1: Write the failing test
- Step 2: Run test to verify it fails
npx vitest run src/lib/__tests__/payback-math.test.ts
Expected: FAIL — “Cannot find module ’../payback-math’”
Note: if thepkrgrouping assertion fails because Node’s ICU formats en-PK as179,712(western grouping), update the assertion to match the actualtoLocaleString('en-PK')output of the environment — the format must simply match whatRoiCalculator.tsx:27produces today.
- Step 3: Implement the module
- Step 4: Run test to verify it passes
npx vitest run src/lib/__tests__/payback-math.test.ts
Expected: PASS (2 test files may not exist yet in repo — a single passing file is the success state)
- Step 5: Commit
Task 2: FAQ data module + JSON-LD generator (the GEO source of truth)
One data module feeds the FAQ UI and the FAQPage JSON-LD so page text and schema never drift. Files:-
Create:
ui/src/components/landing/faq-data.ts -
Test:
ui/src/components/landing/__tests__/faq-data.test.ts - Step 1: Write the failing test
- Step 2: Run test to verify it fails
npx vitest run src/components/landing/__tests__/faq-data.test.ts
Expected: FAIL — “Cannot find module ’../faq-data’”
- Step 3: Implement the module
- Step 4: Run test to verify it passes
npx vitest run src/components/landing/__tests__/faq-data.test.ts
Expected: PASS
- Step 5: Commit
Task 3: Hero — extract product mock, rebuild headline/CTAs
Files:-
Create:
ui/src/components/landing/HeroProductMock.tsx(mechanical extraction) -
Create:
ui/src/components/landing/Hero.tsx -
Reference (do not delete yet):
ui/src/components/landing/PremiumHero.tsx - Step 1: Extract the dashboard mock
ui/src/components/landing/PremiumHero.tsx. The dashboard preview mock is the JSX block at approximately lines 115–375 (the browser-frame <div> containing the sidebar, KPI cards, Ruby briefs, and appointments table — it starts right after the trust-signals block). Create HeroProductMock.tsx that default-exports exactly that JSX wrapped in a component, moving with it ONLY the imports that block uses (check the top of PremiumHero for which lucide icons / Logo the mock references):
- Step 2: Build the new Hero
CheckLandingNavbar’s prop signature inLandingNavbar.tsxbefore wiringuser— pass whatever it currently receives fromPremiumHero.tsx(same prop name and shape).
- Step 3: Typecheck
npx tsc --noEmit -p tsconfig.json
Expected: clean exit
- Step 4: Commit
Task 4: Payback calculator becomes section 2
Files:-
Create:
ui/src/components/landing/PaybackCalculator.tsx(adapted fromRoiCalculator.tsx) -
Reference:
ui/src/components/landing/RoiCalculator.tsx - Step 1: Create PaybackCalculator
RoiCalculator.tsx to PaybackCalculator.tsx, then apply exactly these changes (everything else — the Slider subcomponent, the inputs card, the violet result card — stays as-is):
- Replace the inline math +
pkrwith the Task 1 module:
- Anchor + heading for the north-star position (replace the
<section>open tag and header block):
<h2> text to: How much are no-shows costing your clinic? (keep the existing h2 classes). Change the eyebrow <span> text to Your payback number and the paragraph below the h2 to:
- CTA under the result (replace the bottom CTA
<a>text and href):
- Rename the default export to
PaybackCalculator.
- Step 2: Typecheck
npx tsc --noEmit -p tsconfig.json
Expected: clean
- Step 3: Run the math tests again (regression)
npx vitest run src/lib/__tests__/payback-math.test.ts
Expected: PASS
- Step 4: Commit
Task 5: WhatsApp ops section (“Here’s how we recover it”)
Files:-
Create:
ui/src/components/landing/WhatsAppOps.tsx - Step 1: Implement the section
- Step 2: Typecheck
npx tsc --noEmit -p tsconfig.json
Expected: clean. If React.ReactNode errors, add import type { ReactNode } from 'react'; and use ReactNode.
- Step 3: Commit
Task 6: Ruby section rewrite (“Your front desk never sleeps”)
Files:-
Modify:
ui/src/components/landing/RubySection.tsx(full rewrite, keep the filename so lazy imports stay stable) - Step 1: Rewrite RubySection.tsx
VerifygetStaticAssetUrlexists in@/lib/subdomain-utils(PremiumFooter imports it today). If its signature differs, match how PremiumFooter calls it; if/ruby-icon.webp404s locally, use the plain<img src="/ruby-icon.webp">fromui/public/.
- Step 2: Typecheck
npx tsc --noEmit -p tsconfig.json
Expected: clean
- Step 3: Commit
Task 7: Clinical depth showcase (merges DICOM + BeforeAfter + DetailedFeatures)
Files:-
Create:
ui/src/components/landing/ClinicalShowcase.tsx - Step 1: Implement the tabbed showcase
- Step 2: Typecheck
npx tsc --noEmit -p tsconfig.json
Expected: clean
- Step 3: Commit
Task 8: GEO FAQ section (native <details> — works without JS)
Files:
-
Create:
ui/src/components/landing/GeoFaq.tsx - Step 1: Implement
<details>/<summary> means the FAQ opens even in the prerendered, JS-free HTML — exactly what crawlers and no-JS readers get.
- Step 2: Typecheck
npx tsc --noEmit -p tsconfig.json
Expected: clean
- Step 3: Commit
Task 9: Final CTA + footer keyword line
Files:-
Create:
ui/src/components/landing/FinalCta.tsx -
Modify:
ui/src/components/landing/PremiumFooter.tsx - Step 1: Implement FinalCta (no framer-motion, no HeroWave)
CheckgetAuthUrlexists in@/lib/subdomain-utils—CTA.tsxcurrently links to the auth URL; reuse whatever helper it uses (e.g.,getAuthUrl()orgetIdUrl()), matching its exact name.
- Step 2: Add the keyword line to PremiumFooter
PremiumFooter.tsx, directly under the logo/copyright block (before the links row), add:
HeroWave import and its JSX usage from PremiumFooter (replace with nothing — the dark background stays). If CTA.tsx was the only other HeroWave consumer on the landing and it’s deleted in Task 12, the landing route ships zero canvas animation.
- Step 3: Typecheck + commit
npx tsc --noEmit -p tsconfig.json — expected clean.
Task 10: Assemble Landing.tsx + navbar anchors
Files:-
Modify:
ui/src/pages/Landing.tsx(full rewrite) -
Modify:
ui/src/components/landing/LandingNavbar.tsx(nav items) - Step 1: Rewrite Landing.tsx
CheckLazySection’s props — today’s Landing passesminHeightandid; keep the same API. IfminHeightdefaults are fine without explicit values, the explicit ones above are still preferred (CLS guard for prerender/live swap).
- Step 2: Update LandingNavbar items
LandingNavbar.tsx, update the nav arrays (desktop ~lines 52–71 AND the mobile menu ~lines 107–142 — both lists):
-
#how-it-works→ label “How It Works” (now resolves to WhatsAppOps) — keep -
#features→ label “Features” (now resolves to ClinicalShowcase) — keep -
#savings→ relabel to “Payback” pointing at#payback— change href, keep position -
REMOVE the
#testimonials“Reviews” item (section no longer exists) -
/refer“Refer & Earn” and/the-recall-effect“Blog” — keep - Step 3: Typecheck + dev-server smoke
npx tsc --noEmit -p tsconfig.json — expected clean.
Run: npm run dev in background, open http://localhost:5173/ with Playwright, screenshot full page, and verify: hero h1 renders, clicking the hero CTA scrolls to the calculator, all 7 sections mount while scrolling, navbar anchors land on the right sections. Stop the dev server after.
- Step 4: Commit
Task 11: llms.txt + sitemap touch
Files:-
Create:
ui/public/llms.txt -
Modify:
ui/public/sitemap.xml(bump<lastmod>for/to 2026-06-11) - Step 1: Write llms.txt
- Step 2: Bump sitemap lastmod
ui/public/sitemap.xml, change the <lastmod> for the https://odontox.io/ entry to 2026-06-11. Leave every other entry untouched.
- Step 3: Verify llms.txt is served
npm run dev background; curl -s http://localhost:5173/llms.txt | head -5
Expected: the # OdontoX header line. Stop the dev server.
- Step 4: Commit
Task 12: Delete dead landing components
Files:-
Delete:
PremiumHero.tsx,TrustedBy.tsx,BentoFeatures.tsx,BridgeSection.tsx,MobileAppCTA.tsx,Testimonials.tsx,DicomSection.tsx,BeforeAfter.tsx,DetailedFeatures.tsx,FAQ.tsx,CTA.tsx,RoiCalculator.tsx(all underui/src/components/landing/) - Step 1: Verify nothing else imports each file
/refer or blog pages reuse CTA or Testimonials), DO NOT delete that file — leave it and note it in the commit message. LandingNavbar, PremiumFooter, CookieBanner, LazySection are kept by design.
- Step 2: Delete and verify build
git checkout -- <path>) and re-run.
- Step 3: Commit
Task 13: Prerender pipeline
Files:-
Create:
ui/scripts/prerender-landing.mjs -
Create:
ui/public/prerender-guard.js -
Modify:
ui/index.html(guard script tag after the root div) -
Modify:
ui/public/_redirects(SPA fallback →app.html) -
Modify:
ui/package.json(build script) - Step 1: Write the guard (external file — CSP forbids inline JS)
- Step 2: Reference the guard in index.html
ui/index.html, find <div id="root"></div> and add the script tag on the next line (inside <body>, immediately after the div so it runs during parse, after the div exists):
- Step 3: Write the prerender script
- Step 4: Update _redirects
ui/public/_redirects, change ONLY the final SPA-fallback line:
/support, /assets/*, and /fonts/* rules untouched. (Cloudflare Pages serves / from the real dist/index.html — the prerendered one — because static files win before _redirects fallbacks; every other route falls back to the clean app.html shell.)
- Step 5: Wire into the build
ui/package.json, change:
postbuild.js so app.html inherits the data-cfasync injection too.)
- Step 6: Run the full build and verify
npx playwright install chromium once, then re-run.
- Step 7: Commit
Task 14: Full verification pass
Files: none (verification only)- Step 1: Unit tests + typecheck + build
- Step 2: Chunk-graph check — no heavy vendors on the landing route
http://localhost:4174/, scroll to the bottom, then list all loaded JS URLs (performance.getEntriesByType('resource')). Verify NO chunk named vendor-three, vendor-dicom, vendor-pdf, vendor-tiff, or vendor-docx was fetched. Kill the preview server after.
- Step 3: Crawler’s-eye check (no JS)
- Step 4: Visual pass (required — tsc/build never proves UI quality)
-
Screenshot every section on
/(scroll stepwise, capture each viewport). - Verify: hero h1 + gradient render, CTA scrolls to calculator, sliders move and the number updates, WhatsApp mock bubbles align, Ruby cards 3-across (desktop) / stacked (mobile), clinical tabs switch, FAQ opens/closes, final CTA gradient panel intact, footer keyword line present.
-
Verify dark mode (
document.documentElement.classList.add('dark')) for the hero + calculator at minimum. - Step 5: Lighthouse spot-check (optional but recommended)
npx lighthouse is available: run against the preview URL, mobile preset. Targets per spec: Performance ≥ 90, SEO = 100. Record the scores in the final report; do not block on this locally (CF edge changes numbers) — the deployed page is the real gate.
- Step 6: Commit any fixes, then final commit
Post-merge / deploy notes (not part of this plan’s tasks)
- Deploy via the odontox-commit-deploy skill as always; after deploy, verify fresh dist timestamp + matching live chunk hash, AND
curl -s https://odontox.io/ | grep -c "best dental software in Pakistan"≥ 1. - Validate FAQPage JSON-LD in Google’s Rich Results test against the live URL.
go.odontox.io/should show no landing flash (prerender-guard) — spot-check logged-out and logged-in.- The
#savingsanchor still resolves (navbar + any external links) via the alias span inside the calculator section. - Spec success criteria live in
docs/superpowers/specs/2026-06-11-landing-redesign-design.md§Success criteria.

