name: “landing-page-generator”
description: “Generates high-converting landing pages as complete Next.js/React (TSX) components with Tailwind CSS. Creates hero sections, feature grids, pricing tables, FAQ accordions, testimonial blocks, and CTA sections using proven copy frameworks (PAS, AIDA, BAB). Outputs SEO meta tags, structured data, and performance-optimised code targeting Core Web Vitals (LCP < 1s, CLS < 0.1). Use when the user asks to create a landing page, marketing page, homepage, single-page site, lead capture page, campaign page, promo page, or conversion-optimised web page — or when they want to A/B test landing page variants or replace a static page with one designed to convert.”
Landing Page Generator
Generate high-converting landing pages from a product description. Output complete Next.js/React components with multiple section variants, proven copy frameworks, SEO optimization, and performance-first patterns. Not lorem ipsum — actual copy that converts.
Target: LCP < 1s · CLS < 0.1 · FID < 100ms
Output: TSX components + Tailwind styles + SEO meta + copy variants
Core Capabilities
- 5 hero section variants (centered, split, gradient, video-bg, minimal)
- Feature sections (grid, alternating, cards with icons)
- Pricing tables (2–4 tiers with feature lists and toggle)
- FAQ accordion with schema markup
- Testimonials (grid, carousel, single-quote)
- CTA sections (banner, full-page, inline)
- Footer (simple, mega, minimal)
- 4 design styles with Tailwind class sets
Generation Workflow
Follow these steps in order for every landing page request:
- Gather inputs — collect product name, tagline, audience, pain point, key benefit, pricing tiers, design style, and copy framework using the trigger format below. Ask only for missing fields.
- Analyze brand voice (recommended) — if the user has existing brand content (website copy, blog posts, marketing materials), run it through
marketing-skill/content-production/scripts/brand_voice_analyzer.py to get a voice profile (formality, tone, perspective). Use the profile to inform design style and copy framework selection:
- formal + professional → enterprise style, AIDA framework
- casual + friendly → bold-startup style, BAB framework
- professional + authoritative → dark-saas style, PAS framework
- casual + conversational → clean-minimal style, BAB framework
- Select design style — map the user’s choice (or infer from brand voice analysis) to one of the four Tailwind class sets in the Design Style Reference.
- Apply copy framework — write all headline and body copy using the chosen framework (PAS / AIDA / BAB) before generating components. Match the voice profile’s formality and tone throughout.
- Generate sections in order — Hero → Features → Pricing → FAQ → Testimonials → CTA → Footer. Skip sections not relevant to the product.
- Validate against SEO checklist — run through every item in the SEO Checklist before outputting final code. Fix any gaps inline.
- Output final components — deliver complete, copy-paste-ready TSX files with all Tailwind classes, SEO meta, and structured data included.
Triggering This Skill
Product: [name]
Tagline: [one sentence value prop]
Target audience: [who they are]
Key pain point: [what problem you solve]
Key benefit: [primary outcome]
Pricing tiers: [free/pro/enterprise or describe]
Design style: dark-saas | clean-minimal | bold-startup | enterprise
Copy framework: PAS | AIDA | BAB
Design Style Reference
| Style | Background | Accent | Cards | CTA Button |
|---|
| Dark SaaS | bg-gray-950 text-white | violet-500/400 | bg-gray-900 border border-gray-800 | bg-violet-600 hover:bg-violet-500 |
| Clean Minimal | bg-white text-gray-900 | blue-600 | bg-gray-50 border border-gray-200 rounded-2xl | bg-blue-600 hover:bg-blue-700 |
| Bold Startup | bg-white text-gray-900 | orange-500 | shadow-xl rounded-3xl | bg-orange-500 hover:bg-orange-600 text-white |
| Enterprise | bg-slate-50 text-slate-900 | slate-700 | bg-white border border-slate-200 shadow-sm | bg-slate-900 hover:bg-slate-800 text-white |
Bold Startup headings: add font-black tracking-tight to all <h1>/<h2> elements.
Copy Frameworks
PAS (Problem → Agitate → Solution)
- H1: Painful state they’re in
- Sub: What happens if they don’t fix it
- CTA: What you offer
- Example — H1: “Your team wastes 3 hours a day on manual reporting” / Sub: “Every hour spent on spreadsheets is an hour not closing deals. Your competitors are already automated.” / CTA: “Automate your reports in 10 minutes →”
AIDA (Attention → Interest → Desire → Action)
- H1: Bold attention-grabbing statement → Sub: Interesting fact or benefit → Features: Desire-building proof points → CTA: Clear action
BAB (Before → After → Bridge)
- H1: “[Before state] → [After state]” → Sub: “Here’s how [product] bridges the gap” → Features: How it works (the bridge)
Representative Component: Hero (Centered Gradient — Dark SaaS)
Use this as the structural template for all hero variants. Swap layout classes, gradient direction, and image placement for split, video-bg, and minimal variants.
export function HeroCentered() {
return (
<section className="relative flex min-h-screen flex-col items-center justify-center overflow-hidden bg-gray-950 px-4 text-center">
<div className="absolute inset-0 bg-gradient-to-b from-violet-900/20 to-transparent" />
<div className="pointer-events-none absolute -top-40 left-1/2 h-[600px] w-[600px] -translate-x-1/2 rounded-full bg-violet-600/20 blur-3xl" />
<div className="relative z-10 max-w-4xl">
<div className="mb-6 inline-flex items-center gap-2 rounded-full border border-violet-500/30 bg-violet-500/10 px-4 py-1.5 text-sm text-violet-300">
<span className="h-1.5 w-1.5 rounded-full bg-violet-400" />
Now in public beta
</div>
<h1 className="mb-6 text-5xl font-bold tracking-tight text-white md:text-7xl">
Ship faster.<br />
<span className="bg-gradient-to-r from-violet-400 to-pink-400 bg-clip-text text-transparent">
Break less.
</span>
</h1>
<p className="mx-auto mb-10 max-w-2xl text-xl text-gray-400">
The deployment platform that catches errors before your users do.
Zero config. Instant rollbacks. Real-time monitoring.
</p>
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
<Button size="lg" className="bg-violet-600 text-white hover:bg-violet-500 px-8">
Start free trial
</Button>
<Button size="lg" variant="outline" className="border-gray-700 text-gray-300">
See how it works →
</Button>
</div>
<p className="mt-4 text-sm text-gray-500">No credit card required · 14-day free trial</p>
</div>
</section>
)
}
Other Section Patterns
Feature Section (Alternating)
Map over a features array with { title, description, image, badge }. Toggle layout direction with i % 2 === 1 ? "lg:flex-row-reverse" : "". Use <Image> with explicit width/height and rounded-2xl shadow-xl. Wrap in <section className="py-24"> with max-w-6xl container.
Pricing Table
Map over a plans array with { name, price, description, features[], cta, highlighted }. Highlighted plan gets border-2 border-violet-500 bg-violet-950/50 ring-4 ring-violet-500/20; others get border border-gray-800 bg-gray-900. Render null price as “Custom”. Use <Check> icon per feature row. Layout: grid gap-8 lg:grid-cols-3.
FAQ with Schema Markup
Inject FAQPage JSON-LD via <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }} /> inside the section. Map FAQs with { q, a } into shadcn <Accordion> with type="single" collapsible. Container: max-w-3xl.
- Testimonials: Grid (
grid-cols-1 md:grid-cols-3) or single-quote hero block with avatar, name, role, and quote text.
- CTA Banner: Full-width section with headline, subhead, and two buttons (primary + ghost). Add trust signals (money-back guarantee, logo strip) immediately below.
- Footer: Logo + nav columns + social links + legal. Use
border-t border-gray-800 separator.
SEO Checklist
Validation step: Before outputting final code, verify every checklist item above is satisfied. Fix any gaps inline — do not skip items.
| Metric | Target | Technique |
|---|
| LCP | < 1s | Preload hero image, use priority on Next/Image |
| CLS | < 0.1 | Set explicit width/height on all images |
| FID/INP | < 100ms | Defer non-critical JS, use loading="lazy" |
| TTFB | < 200ms | Use ISR or static generation for landing pages |
| Bundle | < 100KB JS | Audit with @next/bundle-analyzer |
Common Pitfalls
- Hero image not preloaded — add
priority prop to first <Image>
- Missing mobile breakpoints — always design mobile-first with
sm: prefixes
- CTA copy too vague — “Get started” beats “Learn more”; “Start free trial” beats “Sign up”
- Pricing page missing trust signals — add money-back guarantee and testimonials near CTA
- No above-the-fold CTA on mobile — ensure button is visible without scrolling on 375px viewport
- Brand Voice Analyzer (
marketing-skill/content-production/scripts/brand_voice_analyzer.py) — Run before generation to establish voice profile and ensure copy consistency
- UI Design System (
product-team/ui-design-system/) — Generate design tokens from brand color before building the page
- Competitive Teardown (
product-team/competitive-teardown/) — Competitive positioning informs landing page messaging and differentiation
Landing Page Copywriting Frameworks
Overview
Four copy frameworks with worked SaaS examples you can adapt. Each framework includes a complete before/after example plus specific guidelines for each section.
1. AIDA Framework (Attention - Interest - Desire - Action)
The classic direct response formula, ideal for product landing pages.
Example — Project management SaaS:
Attention: "Your Team Loses 12 Hours Every Sprint to Status Meetings"
Interest: "Engineering teams at Series A-C startups spend 23% of their week in sync meetings — not writing code. We tracked 847 teams over 6 months. The pattern was clear: the more people in a standup, the less code shipped that day."
Desire: "Teams using AsyncStand ship 31% more story points per sprint. No more 15-person standups where 13 people zone out. Replace your daily sync with a 2-minute async check-in that your engineers actually complete (94% response rate vs 67% attendance for live standups)."
Action: "Start Your Free 14-Day Trial — No Credit Card Required"
Attention
- Lead with a specific, quantified pain point (not vague claims)
- Weak: "Save time on meetings" → Strong: "Your Team Loses 12 Hours Every Sprint to Status Meetings"
- Keep headlines under 10 words for maximum impact
Interest
- Back up the headline with specific data or a relatable scenario
- Weak: "Meetings waste time" → Strong: "We tracked 847 teams — the more people in standup, the less code shipped that day"
- Use their language: mirror words from customer reviews, support tickets, and G2 feedback
Desire
- Stack measurable outcomes, not features
- Weak: "AI-powered async updates" → Strong: "31% more story points per sprint, 94% response rate"
- Compare directly to the status quo they already endure
Action
- Single, clear CTA with action-oriented verb
- Reduce friction: "No credit card required," "Set up in 2 minutes"
- Repeat CTA after each major content block
2. PAS Framework (Problem - Agitate - Solution)
Best for pain-point-driven products where the problem is well understood.
Example — Expense management tool:
Problem: "Your finance team is still chasing receipts in Slack DMs."
Agitate: "Last quarter, your team spent 46 hours manually reconciling expenses across email threads, shared drives, and 'I'll submit it later' promises. That's $4,200 in payroll — spent on data entry. And when audit season hits? Good luck finding that client dinner receipt from February."
Solution: "Snap a photo of the receipt. ExpenseFlow auto-extracts vendor, amount, and category in 3 seconds. Your monthly close drops from 5 days to 1. 2,400 finance teams already made the switch."
Problem
- Name the exact scenario (not the abstract category)
- Weak: "Expense tracking is hard" → Strong: "Your finance team is still chasing receipts in Slack DMs"
- Mirror language from reviews and support tickets
Agitate
- Quantify the cost in dollars, hours, or missed opportunities
- Weak: "This costs you money" → Strong: "46 hours last quarter, $4,200 in payroll — on data entry"
- Acknowledge the workarounds they've tried and why those fail too
Solution
- Lead with the user action, not the technology: "Snap a photo" not "AI-powered OCR"
- Include one proof point: number of customers, time saved, or before/after metric
- Make the mechanism clear in one sentence: what happens when they use it
3. BAB Framework (Before - After - Bridge)
Ideal for aspirational products and lifestyle-oriented landing pages.
Example — Sales enablement platform:
Before: "It's 9 PM. You're rebuilding a deck for tomorrow's demo because the prospect is in healthcare, not fintech. You copy-paste from three old decks, pray the logos are right, and rehearse the new talk track in the shower."
After: "It's 9 AM. You type 'healthcare, 200-bed hospital, HIPAA-concerned CTO.' DeckGen builds your slides in 40 seconds — case studies, compliance badges, ROI calculator pre-loaded. You walk into the call with the best deck your prospect has ever seen."
Bridge: "DeckGen connects to your CRM, learns your win patterns, and generates prospect-specific decks in under a minute. 340 AEs at companies like Stripe and Notion already use it. Start free — your first 5 decks are on us."
Before
- Describe a specific, lived moment — not an abstract pain category
- Weak: "Sales decks take too long" → Strong: "It's 9 PM. You're rebuilding a deck for tomorrow's demo..."
- Use second person and present tense to make it feel immediate
After
- Same level of specificity — show the transformed version of that exact moment
- Include a measurable outcome: "40 seconds," "best deck your prospect has ever seen"
- The after state should feel effortless compared to the before
Bridge
- Name the product explicitly and explain the mechanism in one sentence
- Include one social proof data point
- End with a low-friction CTA that connects to the after state
4. 4Ps Framework (Promise - Picture - Proof - Push)
Strong for SaaS and B2B landing pages with measurable outcomes.
Promise
- Make a clear, specific, believable promise
- Tie it to a measurable outcome
- Example: "Reduce customer churn by 25% in 90 days"
Picture
- Help the reader visualize success
- Use scenarios they can relate to
- Show the product in context (screenshots, demos)
Proof
- Back the promise with evidence
- Customer testimonials with specific results
- Case studies with before/after metrics
- Third-party validation (awards, analyst reports)
Push
- Give a compelling reason to act now
- Limited-time offer, bonus, or guarantee
- Risk reversal (money-back guarantee, free trial)
Headline Formulas
Benefit-Driven
- "Get [Desired Outcome] Without [Common Objection]"
- "[Specific Result] in [Timeframe]"
- "The [Adjective] Way to [Achieve Goal]"
Problem-Driven
- "Stop [Painful Activity]. Start [Better Alternative]."
- "Tired of [Problem]? There's a Better Way."
- "[Problem]? Not Anymore."
Social Proof-Driven
- "[Number] Teams Trust [Product] to [Outcome]"
- "Why [Notable Company] Switched to [Product]"
- "Rated #1 for [Category] by [Authority]"
Question-Driven
- "What If You Could [Desirable Outcome]?"
- "Ready to [Transformation]?"
- "Still [Painful Status Quo]?"
CTA Best Practices
Language
- Use first-person: "Start My Free Trial" > "Start Your Free Trial"
- Be specific: "Get My Report" > "Submit"
- Include benefit: "Start Saving Time" > "Sign Up"
- Add urgency naturally: "Start Free Today" > "Sign Up Now!!!"
Placement
- Primary CTA above the fold
- Repeat after each major content section
- Sticky CTA on scroll (mobile especially)
- Exit-intent as last chance
Design
- High contrast color (stands out from page palette)
- Sufficient whitespace around the button
- Large enough to tap on mobile (min 44x44px)
- Micro-copy below button to reduce anxiety ("No credit card required")
Above-the-Fold Principles
The first viewport must accomplish these goals within 5 seconds:
- Communicate what you do - Clear, jargon-free headline
- Show who it's for - Audience identification
- Demonstrate value - Primary benefit or outcome
- Provide next step - Visible CTA button
- Build credibility - One trust signal (logo bar, metric, badge)
Above-the-Fold Checklist
#!/usr/bin/env python3
"""Landing Page Scaffolder — Generate landing pages as HTML or Next.js TSX from config.
Creates production-ready landing pages with hero sections, features,
testimonials, pricing, CTAs, and responsive design.
Usage:
python landing_page_scaffolder.py config.json --format html --output page.html
python landing_page_scaffolder.py config.json --format tsx --output LandingPage.tsx
python landing_page_scaffolder.py config.json --format json
"""
import argparse
import json
import sys
from typing import Dict, List, Any, Optional
from datetime import datetime
import html as html_module
def escape(text: str) -> str:
"""HTML-escape text."""
return html_module.escape(str(text))
# ---------------------------------------------------------------------------
# Tailwind style mappings for TSX output
# ---------------------------------------------------------------------------
DESIGN_STYLES = {
"dark-saas": {
"bg": "bg-gray-950", "text": "text-white",
"accent": "violet", "card_bg": "bg-gray-900 border border-gray-800",
"btn": "bg-violet-600 hover:bg-violet-500 text-white",
"btn_secondary": "border border-gray-700 text-gray-300 hover:bg-gray-800",
"section_alt": "bg-gray-900/50", "muted": "text-gray-400",
"border": "border-gray-800",
},
"clean-minimal": {
"bg": "bg-white", "text": "text-gray-900",
"accent": "blue", "card_bg": "bg-gray-50 border border-gray-200 rounded-2xl",
"btn": "bg-blue-600 hover:bg-blue-700 text-white",
"btn_secondary": "border border-gray-300 text-gray-700 hover:bg-gray-50",
"section_alt": "bg-gray-50", "muted": "text-gray-500",
"border": "border-gray-200",
},
"bold-startup": {
"bg": "bg-white", "text": "text-gray-900",
"accent": "orange", "card_bg": "shadow-xl rounded-3xl bg-white",
"btn": "bg-orange-500 hover:bg-orange-600 text-white",
"btn_secondary": "border-2 border-orange-500 text-orange-600 hover:bg-orange-50",
"section_alt": "bg-orange-50/30", "muted": "text-gray-500",
"border": "border-gray-200",
},
"enterprise": {
"bg": "bg-slate-50", "text": "text-slate-900",
"accent": "slate", "card_bg": "bg-white border border-slate-200 shadow-sm",
"btn": "bg-slate-900 hover:bg-slate-800 text-white",
"btn_secondary": "border border-slate-300 text-slate-700 hover:bg-slate-100",
"section_alt": "bg-white", "muted": "text-slate-500",
"border": "border-slate-200",
},
}
# ---------------------------------------------------------------------------
# TSX generators
# ---------------------------------------------------------------------------
def tsx_nav(config: Dict[str, Any], style: Dict[str, str]) -> str:
brand = config.get("brand", "Brand")
nav_links = config.get("nav_links", [])
cta = config.get("nav_cta", {"text": "Get Started", "url": "#"})
links_jsx = "\n ".join(
f'<a href="{l.get("url", "#")}" className="{style["muted"]} hover:{style["text"]} font-medium transition-colors">{l.get("text", "")}</a>'
for l in nav_links
)
return f'''function Navbar() {{
return (
<nav className="sticky top-0 z-50 {style["bg"]} border-b {style["border"]} backdrop-blur-sm">
<div className="mx-auto flex max-w-7xl items-center justify-between px-6 py-4">
<a href="#" className="text-xl font-bold {style["text"]}">{brand}</a>
<div className="hidden items-center gap-8 md:flex">
{links_jsx}
<a href="{cta.get("url", "#")}" className="rounded-lg {style["btn"]} px-5 py-2.5 text-sm font-semibold transition-colors">
{cta.get("text", "Get Started")}
</a>
</div>
</div>
</nav>
);
}}'''
def tsx_hero(hero: Dict[str, Any], style: Dict[str, str]) -> str:
h1 = hero.get("headline", "Your Headline Here")
sub = hero.get("subheadline", "")
primary_cta = hero.get("primary_cta", {"text": "Get Started", "url": "#"})
secondary_cta = hero.get("secondary_cta", None)
secondary_jsx = ""
if secondary_cta:
secondary_jsx = f'''
<a href="{secondary_cta.get("url", "#")}" className="rounded-lg {style["btn_secondary"]} px-8 py-3 text-lg font-semibold transition-colors">
{secondary_cta.get("text", "Learn More")}
</a>'''
return f'''function Hero() {{
return (
<section className="flex min-h-[80vh] flex-col items-center justify-center px-6 py-24 text-center {style["bg"]}">
<div className="mx-auto max-w-4xl">
<h1 className="mb-6 text-5xl font-bold tracking-tight {style["text"]} md:text-7xl">
{h1}
</h1>
<p className="mx-auto mb-10 max-w-2xl text-xl {style["muted"]}">
{sub}
</p>
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
<a href="{primary_cta.get("url", "#")}" className="rounded-lg {style["btn"]} px-8 py-3 text-lg font-semibold transition-colors">
{primary_cta.get("text", "Get Started")}
</a>{secondary_jsx}
</div>
</div>
</section>
);
}}'''
def tsx_features(features: Dict[str, Any], style: Dict[str, str]) -> str:
title = features.get("title", "Features")
subtitle = features.get("subtitle", "")
items = features.get("items", [])
cards_jsx = "\n ".join(
f'''<div className="{style["card_bg"]} rounded-xl p-8">
<div className="mb-4 text-3xl">{f.get("icon", "")}</div>
<h3 className="mb-3 text-xl font-semibold {style["text"]}">{f.get("title", "")}</h3>
<p className="{style["muted"]}">{f.get("description", "")}</p>
</div>'''
for f in items
)
return f'''function Features() {{
return (
<section className="{style["section_alt"]} px-6 py-24">
<div className="mx-auto max-w-7xl">
<h2 className="mb-4 text-center text-4xl font-bold {style["text"]}">{title}</h2>
<p className="mx-auto mb-16 max-w-2xl text-center text-lg {style["muted"]}">{subtitle}</p>
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{cards_jsx}
</div>
</div>
</section>
);
}}'''
def tsx_testimonials(testimonials: Dict[str, Any], style: Dict[str, str]) -> str:
title = testimonials.get("title", "What Our Customers Say")
items = testimonials.get("items", [])
if not items:
return ""
cards_jsx = "\n ".join(
f'''<div className="rounded-xl border {style["border"]} p-8">
<p className="mb-6 text-lg italic {style["muted"]}">"{t.get("quote", "")}"</p>
<div>
<p className="font-semibold {style["text"]}">{t.get("name", "")}</p>
<p className="text-sm {style["muted"]}">{t.get("title", "")}, {t.get("company", "")}</p>
</div>
</div>'''
for t in items
)
return f'''function Testimonials() {{
return (
<section className="px-6 py-24 {style["bg"]}">
<div className="mx-auto max-w-7xl">
<h2 className="mb-16 text-center text-4xl font-bold {style["text"]}">{title}</h2>
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{cards_jsx}
</div>
</div>
</section>
);
}}'''
def tsx_pricing(pricing: Dict[str, Any], style: Dict[str, str]) -> str:
title = pricing.get("title", "Pricing")
plans = pricing.get("plans", [])
if not plans:
return ""
accent = style["accent"]
cards = []
for p in plans:
featured = p.get("featured", False)
border_cls = f"border-2 border-{accent}-500 ring-4 ring-{accent}-500/20" if featured else f"border {style['border']}"
badge = f'\n <div className="absolute -top-3 left-1/2 -translate-x-1/2 rounded-full bg-{accent}-600 px-4 py-1 text-xs font-semibold text-white">Most Popular</div>' if featured else ""
features_jsx = "\n ".join(
f'<li className="flex items-center gap-2 py-2"><span className="text-{accent}-500 font-bold">✓</span> {feat}</li>'
for feat in p.get("features", [])
)
cards.append(f'''<div className="relative rounded-2xl {border_cls} {style["card_bg"]} p-8 text-center">{badge}
<h3 className="mb-2 text-xl font-semibold {style["text"]}">{p.get("name", "")}</h3>
<div className="my-6 text-5xl font-extrabold {style["text"]}">${p.get("price", "0")}<span className="text-base font-normal {style["muted"]}">/mo</span></div>
<p className="{style["muted"]} mb-6">{p.get("description", "")}</p>
<ul className="mb-8 space-y-1 text-left {style["muted"]}">
{features_jsx}
</ul>
<a href="{p.get("cta_url", "#")}" className="block w-full rounded-lg {style["btn"]} py-3 text-center font-semibold transition-colors">
{p.get("cta_text", "Choose Plan")}
</a>
</div>''')
cards_jsx = "\n ".join(cards)
return f'''function Pricing() {{
return (
<section className="{style["section_alt"]} px-6 py-24">
<div className="mx-auto max-w-5xl">
<h2 className="mb-16 text-center text-4xl font-bold {style["text"]}">{title}</h2>
<div className="grid gap-8 lg:grid-cols-{min(len(plans), 3)}">
{cards_jsx}
</div>
</div>
</section>
);
}}'''
def tsx_cta(cta: Dict[str, Any], style: Dict[str, str]) -> str:
accent = style["accent"]
return f'''function CTASection() {{
return (
<section className="bg-{accent}-600 px-6 py-24 text-center text-white">
<div className="mx-auto max-w-3xl">
<h2 className="mb-4 text-4xl font-bold">{cta.get("headline", "Ready to get started?")}</h2>
<p className="mb-10 text-xl opacity-90">{cta.get("subheadline", "")}</p>
<a href="{cta.get("url", "#")}" className="rounded-lg bg-white px-8 py-3 text-lg font-semibold text-{accent}-600 transition-colors hover:bg-gray-100">
{cta.get("text", "Start Free Trial")}
</a>
</div>
</section>
);
}}'''
def tsx_footer(config: Dict[str, Any], style: Dict[str, str]) -> str:
brand = config.get("brand", "Company")
year = datetime.now().year
footer_text = config.get("footer_text", f"{year} {brand}. All rights reserved.")
return f'''function Footer() {{
return (
<footer className="border-t {style["border"]} {style["bg"]} px-6 py-10 text-center {style["muted"]}">
<p>© {footer_text}</p>
</footer>
);
}}'''
def generate_tsx(config: Dict[str, Any]) -> str:
"""Generate complete Next.js/React TSX landing page with Tailwind CSS."""
style_name = config.get("design_style", "clean-minimal")
style = DESIGN_STYLES.get(style_name, DESIGN_STYLES["clean-minimal"])
components = []
component_names = []
components.append(tsx_nav(config, style))
component_names.append("Navbar")
if config.get("hero"):
components.append(tsx_hero(config["hero"], style))
component_names.append("Hero")
if config.get("features"):
components.append(tsx_features(config["features"], style))
component_names.append("Features")
if config.get("testimonials") and config["testimonials"].get("items"):
components.append(tsx_testimonials(config["testimonials"], style))
component_names.append("Testimonials")
if config.get("pricing") and config["pricing"].get("plans"):
components.append(tsx_pricing(config["pricing"], style))
component_names.append("Pricing")
if config.get("cta"):
components.append(tsx_cta(config["cta"], style))
component_names.append("CTASection")
components.append(tsx_footer(config, style))
component_names.append("Footer")
title = config.get("title", "Landing Page")
meta_desc = config.get("meta_description", "")
page_body = "\n ".join(f"<{name} />" for name in component_names)
all_components = "\n\n".join(components)
return f'''// Generated by Landing Page Scaffolder — {datetime.now().strftime("%Y-%m-%d")}
// Stack: Next.js 14+ App Router, React, Tailwind CSS
// Design style: {style_name}
import type {{ Metadata }} from "next";
export const metadata: Metadata = {{
title: "{title}",
description: "{meta_desc}",
openGraph: {{
title: "{title}",
description: "{meta_desc}",
type: "website",
}},
}};
{all_components}
export default function LandingPage() {{
return (
<main>
{page_body}
</main>
);
}}
'''
# ---------------------------------------------------------------------------
# HTML generators (existing)
# ---------------------------------------------------------------------------
def generate_css(config: Dict[str, Any]) -> str:
"""Generate responsive CSS from config theme."""
theme = config.get("theme", {})
primary = theme.get("primary_color", "#2563eb")
secondary = theme.get("secondary_color", "#1e40af")
bg = theme.get("background", "#ffffff")
text_color = theme.get("text_color", "#1f2937")
font = theme.get("font", "Inter, system-ui, -apple-system, sans-serif")
return f"""
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{ font-family: {font}; color: {text_color}; background: {bg}; line-height: 1.6; }}
.container {{ max-width: 1200px; margin: 0 auto; padding: 0 24px; }}
nav {{ padding: 16px 0; border-bottom: 1px solid #e5e7eb; position: sticky; top: 0; background: {bg}; z-index: 100; }}
nav .container {{ display: flex; justify-content: space-between; align-items: center; }}
.nav-logo {{ font-size: 1.5rem; font-weight: 700; color: {primary}; text-decoration: none; }}
.nav-links {{ display: flex; gap: 24px; list-style: none; }}
.nav-links a {{ text-decoration: none; color: {text_color}; font-weight: 500; }}
.nav-cta {{ background: {primary}; color: white; padding: 8px 20px; border-radius: 6px; text-decoration: none; font-weight: 600; }}
.hero {{ padding: 80px 0; text-align: center; }}
.hero h1 {{ font-size: 3.5rem; font-weight: 800; line-height: 1.1; margin-bottom: 24px; max-width: 800px; margin-left: auto; margin-right: auto; }}
.hero p {{ font-size: 1.25rem; color: #6b7280; max-width: 600px; margin: 0 auto 32px; }}
.hero-cta {{ display: inline-flex; gap: 16px; }}
.btn-primary {{ background: {primary}; color: white; padding: 14px 32px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 1.1rem; }}
.btn-secondary {{ background: transparent; color: {primary}; padding: 14px 32px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 1.1rem; border: 2px solid {primary}; }}
.features {{ padding: 80px 0; background: #f9fafb; }}
.section-title {{ text-align: center; font-size: 2.5rem; font-weight: 700; margin-bottom: 16px; }}
.section-subtitle {{ text-align: center; color: #6b7280; font-size: 1.1rem; margin-bottom: 48px; max-width: 600px; margin-left: auto; margin-right: auto; }}
.features-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 32px; }}
.feature-card {{ background: white; padding: 32px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }}
.feature-icon {{ font-size: 2rem; margin-bottom: 16px; }}
.feature-card h3 {{ font-size: 1.25rem; margin-bottom: 12px; }}
.feature-card p {{ color: #6b7280; }}
.testimonials {{ padding: 80px 0; }}
.testimonials-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 24px; }}
.testimonial-card {{ padding: 32px; border: 1px solid #e5e7eb; border-radius: 12px; }}
.testimonial-text {{ font-size: 1.1rem; font-style: italic; margin-bottom: 20px; }}
.testimonial-author {{ display: flex; align-items: center; gap: 12px; }}
.author-info strong {{ display: block; }}
.author-info span {{ color: #6b7280; font-size: 0.9rem; }}
.pricing {{ padding: 80px 0; background: #f9fafb; }}
.pricing-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 24px; max-width: 900px; margin: 0 auto; }}
.pricing-card {{ background: white; padding: 32px; border-radius: 12px; border: 2px solid #e5e7eb; text-align: center; }}
.pricing-card.featured {{ border-color: {primary}; position: relative; }}
.pricing-card.featured::before {{ content: "Most Popular"; position: absolute; top: -12px; left: 50%; transform: translateX(-50%); background: {primary}; color: white; padding: 4px 16px; border-radius: 20px; font-size: 0.8rem; font-weight: 600; }}
.pricing-name {{ font-size: 1.25rem; font-weight: 600; margin-bottom: 8px; }}
.pricing-price {{ font-size: 3rem; font-weight: 800; margin: 16px 0; }}
.pricing-price span {{ font-size: 1rem; font-weight: 400; color: #6b7280; }}
.pricing-features {{ list-style: none; text-align: left; margin: 24px 0; }}
.pricing-features li {{ padding: 8px 0; border-bottom: 1px solid #f3f4f6; }}
.pricing-features li::before {{ content: "\\2713 "; color: {primary}; font-weight: 700; }}
.cta-section {{ padding: 80px 0; text-align: center; background: {primary}; color: white; }}
.cta-section h2 {{ font-size: 2.5rem; margin-bottom: 16px; }}
.cta-section p {{ font-size: 1.1rem; opacity: 0.9; margin-bottom: 32px; }}
.btn-white {{ background: white; color: {primary}; padding: 14px 32px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 1.1rem; }}
footer {{ padding: 40px 0; border-top: 1px solid #e5e7eb; color: #6b7280; text-align: center; }}
@media (max-width: 768px) {{
.hero h1 {{ font-size: 2.25rem; }}
.hero-cta {{ flex-direction: column; align-items: center; }}
.nav-links {{ display: none; }}
.features-grid {{ grid-template-columns: 1fr; }}
.pricing-grid {{ grid-template-columns: 1fr; }}
}}
"""
def render_nav(config: Dict[str, Any]) -> str:
brand = escape(config.get("brand", "Brand"))
nav_links = config.get("nav_links", [])
cta = config.get("nav_cta", {"text": "Get Started", "url": "#"})
links = "\n".join(
f'<li><a href="{escape(l.get("url", "#"))}">{escape(l.get("text", ""))}</a></li>'
for l in nav_links
)
return f"""
<nav><div class="container">
<a href="#" class="nav-logo">{brand}</a>
<ul class="nav-links">{links}</ul>
<a href="{escape(cta.get('url', '#'))}" class="nav-cta">{escape(cta.get('text', 'Get Started'))}</a>
</div></nav>"""
def render_hero(hero: Dict[str, Any]) -> str:
h1 = escape(hero.get("headline", "Your Headline Here"))
sub = escape(hero.get("subheadline", ""))
primary_cta = hero.get("primary_cta", {"text": "Get Started", "url": "#"})
secondary_cta = hero.get("secondary_cta", None)
cta_html = f'<a href="{escape(primary_cta.get("url", "#"))}" class="btn-primary">{escape(primary_cta.get("text", "Get Started"))}</a>'
if secondary_cta:
cta_html += f'\n<a href="{escape(secondary_cta.get("url", "#"))}" class="btn-secondary">{escape(secondary_cta.get("text", "Learn More"))}</a>'
return f"""
<section class="hero"><div class="container">
<h1>{h1}</h1>
<p>{sub}</p>
<div class="hero-cta">{cta_html}</div>
</div></section>"""
def render_features(features: Dict[str, Any]) -> str:
title = escape(features.get("title", "Features"))
subtitle = escape(features.get("subtitle", ""))
items = features.get("items", [])
cards = "\n".join(f"""
<div class="feature-card">
<div class="feature-icon">{escape(f.get('icon', ''))}</div>
<h3>{escape(f.get('title', ''))}</h3>
<p>{escape(f.get('description', ''))}</p>
</div>""" for f in items)
return f"""
<section class="features"><div class="container">
<h2 class="section-title">{title}</h2>
<p class="section-subtitle">{subtitle}</p>
<div class="features-grid">{cards}</div>
</div></section>"""
def render_testimonials(testimonials: Dict[str, Any]) -> str:
title = escape(testimonials.get("title", "What Our Customers Say"))
items = testimonials.get("items", [])
if not items:
return ""
cards = "\n".join(f"""
<div class="testimonial-card">
<p class="testimonial-text">"{escape(t.get('quote', ''))}"</p>
<div class="testimonial-author">
<div class="author-info">
<strong>{escape(t.get('name', ''))}</strong>
<span>{escape(t.get('title', ''))}, {escape(t.get('company', ''))}</span>
</div>
</div>
</div>""" for t in items)
return f"""
<section class="testimonials"><div class="container">
<h2 class="section-title">{title}</h2>
<div class="testimonials-grid">{cards}</div>
</div></section>"""
def render_pricing(pricing: Dict[str, Any]) -> str:
title = escape(pricing.get("title", "Pricing"))
plans = pricing.get("plans", [])
if not plans:
return ""
cards = "\n".join(f"""
<div class="pricing-card {'featured' if p.get('featured') else ''}">
<div class="pricing-name">{escape(p.get('name', ''))}</div>
<div class="pricing-price">${escape(str(p.get('price', '0')))}<span>/mo</span></div>
<p>{escape(p.get('description', ''))}</p>
<ul class="pricing-features">
{"".join(f'<li>{escape(f)}</li>' for f in p.get('features', []))}
</ul>
<a href="{escape(p.get('cta_url', '#'))}" class="btn-primary">{escape(p.get('cta_text', 'Choose Plan'))}</a>
</div>""" for p in plans)
return f"""
<section class="pricing"><div class="container">
<h2 class="section-title">{title}</h2>
<div class="pricing-grid">{cards}</div>
</div></section>"""
def render_cta(cta: Dict[str, Any]) -> str:
return f"""
<section class="cta-section"><div class="container">
<h2>{escape(cta.get('headline', 'Ready to get started?'))}</h2>
<p>{escape(cta.get('subheadline', ''))}</p>
<a href="{escape(cta.get('url', '#'))}" class="btn-white">{escape(cta.get('text', 'Start Free Trial'))}</a>
</div></section>"""
def generate_html(config: Dict[str, Any]) -> str:
"""Generate complete HTML landing page."""
title = escape(config.get("title", "Landing Page"))
css = generate_css(config)
sections = []
sections.append(render_nav(config))
if config.get("hero"):
sections.append(render_hero(config["hero"]))
if config.get("features"):
sections.append(render_features(config["features"]))
if config.get("testimonials"):
sections.append(render_testimonials(config["testimonials"]))
if config.get("pricing"):
sections.append(render_pricing(config["pricing"]))
if config.get("cta"):
sections.append(render_cta(config["cta"]))
sections.append(f"""
<footer><div class="container">
<p>{escape(config.get('footer_text', f'{datetime.now().year} {config.get("brand", "Company")}. All rights reserved.'))}</p>
</div></footer>""")
return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<meta name="description" content="{escape(config.get('meta_description', ''))}">
<style>{css}</style>
</head>
<body>
{"".join(sections)}
</body>
</html>"""
def main():
parser = argparse.ArgumentParser(
description="Generate landing pages as HTML or Next.js TSX with Tailwind CSS"
)
parser.add_argument("input", help="Path to page config JSON")
parser.add_argument(
"--format", choices=["html", "tsx", "json"], default="tsx",
help="Output format: tsx (Next.js + Tailwind), html (standalone), json (metadata)"
)
parser.add_argument("--output", type=str, default=None, help="Output file path")
args = parser.parse_args()
with open(args.input) as f:
config = json.load(f)
if args.format == "json":
output = json.dumps({
"generated_at": datetime.now().isoformat(),
"config": config,
"formats_available": ["html", "tsx"],
"sections": [k for k in ["nav", "hero", "features", "testimonials", "pricing", "cta", "footer"]
if config.get(k) or k in ("nav", "footer")]
}, indent=2)
elif args.format == "tsx":
output = generate_tsx(config)
else:
output = generate_html(config)
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"Landing page written to {args.output}")
else:
print(output)
if __name__ == "__main__":
main()