Distinct from
product-team/skills/landing-page-generator/. That skill outputs Next.js TSX components optimized for conversion / lead-gen. THIS skill outputs a single self-contained.htmlfile optimized for premium visual experience with GSAP animations. Pick by use case.
Generate a polished, self-contained .html landing page from a text prompt or brief. The output is ONE HTML file: all CSS inline in <style>, all JS inline in <script>, only external dependencies being Google Fonts + GSAP via CDN. The page is visually distinctive, animated, and production-quality.
In Claude Code CLI, write the file to disk at the specified path. In Claude.ai web, create an HTML artifact with the same content.
Dependency-ordered. Each question carries explicit "why I'm asking". Stop condition: max 4.
What's the product or service? Give me the name + a 1–2 sentence elevator pitch — what does it do, and who's it for?
Why I'm asking: The headline, subtext, and feature copy all derive from this. "App for productivity" produces generic boilerplate; "Async standup tool for remote engineering teams who hate Zoom" produces a landing page that converts.
Refuse mush. If user gives just a name with no pitch, push back once: "What does it do? Who's it for?" If still no pitch after push-back, deliver with explicit "generic positioning" caveat.
Who's the audience? Pick one:
- Technical buyers (engineers, ops, security)
- Business buyers (PMs, execs, ops leaders)
- Consumers (general public, hobbyists)
- Internal (employees, partners — not for public sale)
Why I'm asking: Audience dictates copy register, jargon level, social-proof choices, and CTA framing. Technical buyers want specifics; consumers want benefits; internal pages can skip persuasion.
Forcing choice.
Brand colors / fonts to override the default (dark navy + teal + Inter)? Provide as: primary HEX, accent HEX, optional bg HEX. Or say "default" if you want the polished default.
Why I'm asking: The default is intentionally beautiful, but matching your brand makes the page feel native to your existing site. Even just a primary color override goes a long way.
Accept "default" or partial overrides (e.g., just primary). If only primary provided, derive accent algorithmically (lighten / darken).
Tone — pick one:
- Professional — confident, restrained, B2B-friendly
- Playful — warm, light, occasional humor
- Authoritative — expert, data-forward, trust-building
- Minimal — terse, design-led, low copy density
Why I'm asking: Tone affects every sentence — headlines, microcopy, button text, closing copy. Picking upfront prevents tonal whiplash across sections.
Forcing choice. Recommended default: professional if Q2 = technical/business; playful if Q2 = consumer; minimal if the product is design-led.
Stop condition: After Q4, commit and generate. No follow-up questions during generation.
From Q1's elevator pitch, derive:
Fallback when input is sparse: invent compelling content from product-name semantics + audience register. Flag inferred content with a comment in the HTML source (<!-- inferred: ... -->). Don't stall waiting for more input.
:root {
--navy: #0A1628;
--navy-mid: #0D1F38;
--teal: #00D4AA;
--teal-glow: rgba(0, 212, 170, 0.12);
--amber: #F5A623;
--off-white: #F7F7F2;
--text-muted: rgba(247, 247, 242, 0.68);
--card-bg: rgba(0, 212, 170, 0.06);
--card-border:rgba(0, 212, 170, 0.15);
}
When Q3 provides custom brand values, the skill substitutes them into the :root block:
Brand override:
- primary: #FF6B35 → --navy / hero bg
- accent: #2EC4B6 → --teal / CTA / highlights
- bg: #011627 → --navy-mid / section bg
- text: #FDFFFC → --off-white
If only primary provided, derive accent algorithmically (lighten 15% for accent; darken 8% for navy-mid; convert to rgba at 0.12 alpha for glow). Use scripts/brand_palette_validator.py for the deterministic derivation.
See references/brand_system_design.md for color theory + WCAG + algorithmic palette derivation canon.
.btn-primary — CTA button with hover state (lift + brightness).feature-card — card with hover lift (translateY(-6px) + border-brighten).eyebrow — letter-spaced (0.2em) uppercase category labelmin-height: 100vh, flex-centered content.hero-shapes-back — large blurred circles, absolute-positioned, low opacity.hero-shapes-mid — smaller shapes, sharper edges, higher opacityrepeat(3, 1fr) grid)transform: translateY(-6px)
border-color: var(--teal) (brighten from --card-border)transition: 0.3s ease
background: var(--navy-mid)
padding: 120px 24px, text-align: centerbackground: radial-gradient(circle, var(--teal-glow) 0%, transparent 70%);
See references/gsap_animation_patterns.md for the canon. Five patterns required:
// MUST use gsap.set() FIRST to prevent FOUC
gsap.set([".eyebrow", ".hero h1", ".hero .subtitle", ".btn-primary", ".scroll-down"], {
opacity: 0,
y: 30
});
const tl = gsap.timeline({ defaults: { ease: "power3.out" } });
tl.to(".eyebrow", { opacity: 1, y: 0, duration: 0.6 })
.to(".hero h1", { opacity: 1, y: 0, duration: 0.8 }, "-=0.3")
.to(".hero .subtitle", { opacity: 1, y: 0, duration: 0.6 }, "-=0.5")
.to(".btn-primary", { opacity: 1, y: 0, duration: 0.5 }, "-=0.3")
.to(".scroll-down", { opacity: 1, y: 0, duration: 0.4 }, "-=0.2");
const hero = document.querySelector(".hero");
hero.addEventListener("mousemove", (e) => {
const x = (e.clientX / window.innerWidth - 0.5) * 2;
const y = (e.clientY / window.innerHeight - 0.5) * 2;
gsap.to(".hero-shapes-back", { x: x * 45, y: y * 22, duration: 0.8 });
gsap.to(".hero-shapes-mid", { x: x * 22, y: y * 11, duration: 0.8 });
gsap.to(".hero .container", { x: x * 8, y: y * 5, duration: 0.8 });
});
gsap.set(".feature-card", { opacity: 0, y: 55, rotateX: 18 });
ScrollTrigger.batch(".feature-card", {
start: "top 80%",
onEnter: batch => gsap.to(batch, {
opacity: 1, y: 0, rotateX: 0,
duration: 0.8,
stagger: 0.11,
ease: "power2.out"
})
});
CSS handles ambient continuous motion (smoother, cheaper than GSAP for indefinite animations):
@keyframes floatA {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
50% { transform: translate(20px, -30px) rotate(8deg); }
}
@keyframes floatB { /* different duration + rotation */ }
@keyframes floatC { /* different duration + rotation */ }
.hero-shapes-back .shape-a { animation: floatA 12s ease-in-out infinite; }
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(8px); }
}
.scroll-down { animation: bounce 2s ease-in-out infinite; }
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
NO other external CSS or JS files. All custom CSS in <style>, all custom JS in <script> blocks within the same HTML file.
See references/single_file_html_discipline.md for the inline-only rationale.
120px 24px (vertical 120, horizontal 24, scales down on mobile)<meta name="viewport" content="width=device-width, initial-scale=1">
${OUTPUT_DIR}/<product-name-kebab>.html
${OUTPUT_DIR}: ./landing-pages/
quill-ai.html). Use scripts/kebab_slug_generator.py for deterministic slug generation + duplicate detection.<style>, all JS in <script>, only Google Fonts + GSAP CDN external.Run scripts/html_validator.py --file ${OUTPUT_DIR}/<slug>.html after generation. Checks:
.hero, .features, .closing-cta)gsap.set() initial states precede any gsap.timeline or gsap.to (FOUC prevention)<link rel="stylesheet"> other than Google Fonts<script src=> other than GSAP CDN<meta name="viewport"> present| Situation | Behavior |
|---|---|
| Input is just a name with no context | Invent compelling content from name semantics + audience register; flag as <!-- inferred --> in HTML source |
| Input file is large or PDF | Read fully before generating; don't truncate |
| Brand colors insufficient (only 1 HEX provided) | Use as primary; derive secondary/accent algorithmically (lighten/darken via brand_palette_validator.py) |
| Features count not specified | Default to 4 |
| Output dir doesn't exist | Create it |
| Existing file at output path | Append timestamp suffix or ask user (kebab_slug_generator.py flags duplicates) |
| html_validator returns FAIL | Regenerate ONLY the failing sections in one targeted pass; do NOT abandon the file |
| Script | Role |
|---|---|
scripts/brand_palette_validator.py |
Validates HEX format, checks WCAG AA contrast, generates derived palette from primary (algorithmic lighten/darken). |
scripts/kebab_slug_generator.py |
Product name → kebab-case filename + duplicate detection in output dir. |
scripts/html_validator.py |
Post-generation structural check: 3 sections, CDN deps, gsap.set() initial states, responsive breakpoints, no external files. |
references/brand_system_design.md — color theory + WCAG + algorithmic palette derivation (7+ sources)references/gsap_animation_patterns.md — entrance timeline + ScrollTrigger reveals + mouse parallax + CSS floats + scroll indicator (7+ sources)references/single_file_html_discipline.md — why inline + CDN-only externals + accessibility minimums + no-build rationale (7+ sources)gsap.set() initial states (causes FOUC)Version: 1.0.0
Source spec: megaprompts/04-landing-megaprompt.md
Build pattern: Path B (direct conversion). Distinct from product-team/skills/landing-page-generator/.