From 8690081a049024c91d950503ebad1f9033d49304 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 15 May 2026 16:39:29 +0000 Subject: [PATCH] =?UTF-8?q?feat(marketing):=20landing=20skill=20=E2=80=94?= =?UTF-8?q?=20Path-B=20generator=20slice=20from=20megaprompt=2004?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice 4 of 13: generator shape. Validates the Path-B conversion pattern for skills that produce a single artifact (HTML file) with motion-design discipline. Also introduces the marketing/ domain folder (parallel to productivity/, separate from the existing structured marketing-skill/ folder that houses the 44 pod-based marketing skills). SOURCE SPEC megaprompts/04-landing-megaprompt.md (PR #657). The megaprompt is the canonical spec; this plugin is the working implementation. WHAT THE SKILL DOES Premium single-file HTML landing page generator. Outputs one polished .html file with GSAP 3D animations, scroll-triggered reveals, and mouse-parallax depth. All CSS inline, all JS inline; only externals are Google Fonts (Inter) + GSAP via CDN. Phase 0: 4 forcing intake questions (one at a time): Q1 — product/service (refuses vague pitches) Q2 — audience register (technical/business/consumer/internal) Q3 — brand overrides (HEX vars, or "default") Q4 — tone (professional/playful/authoritative/minimal) Then generates a single .html with three sections (Hero, Features, Closing CTA), GSAP entrance timeline, mouse-parallax handler, ScrollTrigger feature reveals, CSS floating shapes, scroll indicator. DOMAIN FOLDER DECISION (marketing/ — new) The existing marketing-skill/ folder houses 44 pod-based marketing skills with its own internal structure. v2 megaprompt-derived landing skill is a single self-contained plugin — placing it inside marketing-skill/ would disrupt that folder's pod structure. Creating marketing/ alongside it, parallel to the new productivity/ folder, gives the v2 megaprompt slices a clean visually-parallel home. Both folders coexist. DISAMBIGUATION FROM EXISTING landing-page-generator The repo already has product-team/skills/landing-page-generator/ — the v2.1.2 work that outputs Next.js TSX + Tailwind for conversion-optimized lead-gen with copy frameworks (PAS/AIDA/BAB). The v2 megaprompt 04 is a DIFFERENT skill: single-file HTML with GSAP for premium visual one-pagers. Different output (HTML vs TSX), different optimization target (visual premium vs conversion), different animation approach (GSAP vs static). Both skills coexist. README.md disambiguates clearly. Pick by use case: visual premium one-pager → marketing/landing/ conversion lead-gen → product-team/skills/landing-page-generator/ PATH-B CONVERSION DISCIPLINE - Frontmatter description preserved verbatim from megaprompt spec. - Workflow structure (megaprompt lines 28-43) became SKILL.md section ordering 1:1. - All 4 grill-me intake questions preserved verbatim with "why I'm asking" rationale. - All 5 animation patterns preserved (Hero Entrance / Mouse Parallax / ScrollTrigger Reveals / CSS Floats / Scroll Indicator). - Default brand palette preserved verbatim (--navy / --teal / --teal-glow / --amber / --off-white / --text-muted / --card-bg / --card-border). - All 3 sections (Hero, Features, Closing CTA) preserved with full spec. - Required CDN dependencies preserved verbatim. - Anti-patterns + error-handling table + validation checklist preserved. REPO STRUCTURE marketing/landing/ ├── .claude-plugin/plugin.json ← source.spec field points at megaprompt ├── README.md ← disambig from landing-page-generator ├── agents/cs-landing.md ← landing generator persona, FOUC enforcer ├── commands/cs-landing.md ← /cs:landing └── skills/landing/ ├── SKILL.md ← Path-B converted from megaprompt 04 ├── references/ │ ├── brand_system_design.md ← color theory + WCAG + algorithmic │ │ derivation (7 sources: WCAG 2.2, │ │ Refactoring UI, Material Design, │ │ IBM Carbon, APCA, Tailwind, etc.) │ ├── gsap_animation_patterns.md ← 5 animation patterns canon (7 sources: │ │ GSAP docs, Val Head, Rachel Nabors, │ │ Sarah Drasner, GPU-accel CSS, Material │ │ motion, WCAG 2.3.3) │ └── single_file_html_discipline.md ← inline + CDN-only rationale (7 sources: │ MDN, Inclusive Components, Resilient │ Web Design, no-build advocacy, etc.) └── scripts/ ├── brand_palette_validator.py ← stdlib: HEX validation + WCAG contrast │ + algorithmic palette derivation in HSL ├── kebab_slug_generator.py ← stdlib: name → kebab + duplicate detection └── html_validator.py ← stdlib: 11-rule structural post-gen check 11 files, 1,979 lines. Slightly heavier than capture (1,560) and pulse (1,643) due to denser SKILL.md (346 lines — full CSS + JS code blocks for all 5 animation patterns) and heavier html_validator.py (11 rules vs the simpler 7-rule checks in other slices). VERIFIED CLEAN - brand_palette_validator.py: sample (orange primary + teal accent + near-black bg) correctly surfaces real WCAG issue: white text on #FF6B35 = 2.64:1 (FAILs body-text 4.5:1 threshold). Real-world validation working as designed. Algorithmic palette derivation produces full var set in HSL space. - kebab_slug_generator.py: "Quill AI — Async Standup Tool" → kebab slug correctly handling em-dash. Duplicate detection + timestamp suffix suggestion. - html_validator.py: 17/17 PASS on clean sample; correctly catches 9 FAILs + 6 WARNs on violation sample (missing viewport, external CSS file, external JS file, missing 2 of 3 sections, missing gsap.set() before timeline, missing both responsive breakpoints, div with onclick, duplicate H1). - All 3 with --output json: valid JSON. - plugin.json validates; conforms to repo schema with source + distinct_from attribution. MEGAPROMPT FIDELITY MARKERS (in SKILL.md) Phase 0: 1 Hero: 18 900px: 3 gsap.set: 6 Features: 5 580px: 3 mouse parallax: 3 Closing CTA: 1 OUTPUT_DIR: 3 ScrollTrigger: 4 GSAP: 25 kebab: 4 CSS keyframes: 1 Google Fonts: 5 FOUC: 3 All megaprompt-mandated terms surface multiple times. VERTICAL-SLICE STATUS ✓ Slice 1: capture (light prompt-flow, PR #659 merged) ✓ Slice 2: pulse (research-pack, PR #660 merged) ✓ Slice 3: email-pair (workflow-pair, PR #661 merged) ✓ Slice 4: landing (generator — this PR; introduces marketing/) ☐ Slice 5: orchestrator/router (13-research) — last shape; must reconcile with existing engineering/autoresearch-agent/ After Slice 5, all 5 shapes are validated. The remaining 8 megaprompts (02-reflect light prompt-flow + 6 research-pack siblings + 03-notebooklm which is research-flavored) can be batched in larger PRs grouped by shape. NOT DONE IN THIS PR (intentional) - .claude-plugin/marketplace.json not updated (separate concern; done after all 13 ship) - .codex/skills/landing symlink not added (auto-sync workflow handles on merge per existing pattern) - engineering/capture/ NOT moved to productivity/capture/ (would break anyone who installed from current path; address in separate cleanup PR) https://claude.ai/code/session_01FEUmeuYhmnxVFq7EZM8ZSw --- marketing/landing/.claude-plugin/plugin.json | 18 + marketing/landing/README.md | 79 ++++ marketing/landing/agents/cs-landing.md | 179 +++++++++ marketing/landing/commands/cs-landing.md | 124 +++++++ marketing/landing/skills/landing/SKILL.md | 346 ++++++++++++++++++ .../landing/references/brand_system_design.md | 158 ++++++++ .../references/gsap_animation_patterns.md | 218 +++++++++++ .../references/single_file_html_discipline.md | 179 +++++++++ .../scripts/brand_palette_validator.py | 263 +++++++++++++ .../skills/landing/scripts/html_validator.py | 295 +++++++++++++++ .../landing/scripts/kebab_slug_generator.py | 120 ++++++ 11 files changed, 1979 insertions(+) create mode 100644 marketing/landing/.claude-plugin/plugin.json create mode 100644 marketing/landing/README.md create mode 100644 marketing/landing/agents/cs-landing.md create mode 100644 marketing/landing/commands/cs-landing.md create mode 100644 marketing/landing/skills/landing/SKILL.md create mode 100644 marketing/landing/skills/landing/references/brand_system_design.md create mode 100644 marketing/landing/skills/landing/references/gsap_animation_patterns.md create mode 100644 marketing/landing/skills/landing/references/single_file_html_discipline.md create mode 100644 marketing/landing/skills/landing/scripts/brand_palette_validator.py create mode 100644 marketing/landing/skills/landing/scripts/html_validator.py create mode 100644 marketing/landing/skills/landing/scripts/kebab_slug_generator.py diff --git a/marketing/landing/.claude-plugin/plugin.json b/marketing/landing/.claude-plugin/plugin.json new file mode 100644 index 00000000..037b515c --- /dev/null +++ b/marketing/landing/.claude-plugin/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "landing", + "description": "Premium single-file HTML landing page generator with GSAP 3D animations, scroll-triggered effects, and mouse-parallax depth. Forcing 3-4 question grill-me intake (product+pitch, audience register, brand overrides, tone) locks down positioning before any copy or markup is written. Outputs a single self-contained HTML file (Claude Code) or HTML artifact (Claude.ai) with all CSS/JS inline — only externals are Google Fonts + GSAP via CDN. Configurable brand colors via CSS custom property overrides. Source spec: megaprompts/04-landing-megaprompt.md (PR #657). Distinct from product-team/skills/landing-page-generator (which outputs Next.js TSX for conversion-optimized lead-gen) — this skill is for premium visual one-pagers with motion design.", + "version": "1.0.0", + "author": { + "name": "Alireza Rezvani", + "url": "https://alirezarezvani.com" + }, + "homepage": "https://github.com/alirezarezvani/claude-skills/tree/main/marketing/landing", + "repository": "https://github.com/alirezarezvani/claude-skills", + "license": "MIT", + "skills": ["./skills/landing"], + "source": { + "spec": "megaprompts/04-landing-megaprompt.md", + "build_pattern": "Path B (direct conversion). Generator shape — produces a single .html artifact (not multi-file scaffolding). Wrapper additions (3 stdlib validators, 3 references, cs-landing agent, /cs:landing command) layered on top per repo convention.", + "distinct_from": "product-team/skills/landing-page-generator/ — different output format (HTML vs TSX), different optimization target (visual premium vs conversion), different motion approach (GSAP vs static)." + } +} diff --git a/marketing/landing/README.md b/marketing/landing/README.md new file mode 100644 index 00000000..45d989a7 --- /dev/null +++ b/marketing/landing/README.md @@ -0,0 +1,79 @@ +# landing + +Premium single-file HTML landing page generator. Outputs one polished `.html` file with GSAP 3D animations, scroll-triggered reveals, and mouse-parallax depth — all CSS inline, all JS inline, only externals are Google Fonts + GSAP via CDN. + +## Important: distinct from `product-team/skills/landing-page-generator/` + +This is **NOT** the same skill as the existing `landing-page-generator` in `product-team/`. They serve different needs: + +| Skill | Output format | Optimization target | Animation approach | When to use | +|---|---|---|---|---| +| **`marketing/landing/`** (this skill) | Single self-contained `.html` file | **Visual premium / one-pager** | GSAP 3D + mouse parallax + scroll-trigger | Launch page, product showcase, brand site where the page IS the experience | +| **`product-team/skills/landing-page-generator/`** | Next.js TSX components + Tailwind | **Conversion / lead-gen** | Static, copy-framework-driven (PAS / AIDA / BAB) | Lead capture, A/B test variants, campaign pages where conversion rate is the goal | + +If you want the prospect to **convert** → use `landing-page-generator`. +If you want the prospect to **be impressed** → use `landing`. + +Both are valid; they sit at different points on the visual-premium / conversion-optimization axis. + +## What this skill does + +Run via `/cs:landing` or trigger phrases like "create a landing page" / "build a landing page". + +The skill walks **3–4 forcing intake questions** (one at a time, dependency-ordered): + +1. **Product / service** — name + 1–2 sentence elevator pitch (refuses vague answers) +2. **Audience register** — technical / business / consumer / internal (forcing choice) +3. **Brand overrides** — default dark navy + teal, OR provide primary HEX + accent HEX + optional bg HEX (algorithmic derivation if only primary given) +4. **Tone** — professional / playful / authoritative / minimal (forcing choice) + +Then generates a single `.html` file with three sections: + +- **Hero** — 100vh, animated entrance via GSAP timeline, depth layers behind H1, mouse parallax +- **Features** — 3-column grid (responsive: 2-col at 900px, 1-col at 580px), SVG icons, scroll-triggered card reveals +- **Closing CTA** — ambient radial-gradient glow behind button, large closing headline + +Output path: `${OUTPUT_DIR}/.html` (default `${OUTPUT_DIR}=./landing-pages/`). + +## Plugin layout + +``` +marketing/landing/ +├── .claude-plugin/plugin.json +├── README.md +├── agents/cs-landing.md ← landing-generation persona, FOUC-prevention enforcer +├── commands/cs-landing.md ← /cs:landing +└── skills/landing/ + ├── SKILL.md ← Path-B converted from megaprompt 04 + ├── references/ + │ ├── brand_system_design.md ← color theory + override patterns (7+ sources) + │ ├── gsap_animation_patterns.md ← entrance + scroll-trigger + parallax + CSS floats (7+ sources) + │ └── single_file_html_discipline.md ← why inline + CDN-only externals (7+ sources) + └── scripts/ + ├── brand_palette_validator.py ← stdlib: HEX validation + WCAG contrast + derived palette + ├── kebab_slug_generator.py ← stdlib: product-name → kebab slug + duplicate detection + └── html_validator.py ← stdlib: post-generation structural check +``` + +## Quick start + +```bash +# Validate a brand override before generation +python skills/landing/scripts/brand_palette_validator.py \ + --primary "#FF6B35" --accent "#2EC4B6" --bg "#011627" + +# Generate output filename +python skills/landing/scripts/kebab_slug_generator.py \ + --product "Quill AI" --output-dir ./landing-pages + +# Validate generated HTML structurally +python skills/landing/scripts/html_validator.py --file ./landing-pages/quill-ai.html +``` + +## Source spec + +[`megaprompts/04-landing-megaprompt.md`](../../megaprompts/04-landing-megaprompt.md) (PR #657). The megaprompt is canonical; this plugin is the working implementation. Drift between the two is a bug — re-grill with `/cs:grill-with-docs` if they diverge. + +## License + +MIT. diff --git a/marketing/landing/agents/cs-landing.md b/marketing/landing/agents/cs-landing.md new file mode 100644 index 00000000..3e7cf482 --- /dev/null +++ b/marketing/landing/agents/cs-landing.md @@ -0,0 +1,179 @@ +--- +name: cs-landing +description: Premium HTML landing page generator persona. Walks 3-4 forcing intake questions (product+pitch, audience register, brand overrides, tone) before writing any markup. Refuses vague product descriptions. Refuses to skip gsap.set() initial states (causes FOUC). Refuses to hardcode brand colors. Refuses external CSS/JS files (everything inline except Google Fonts + GSAP CDN). Outputs one self-contained .html file with GSAP 3D animations, scroll-triggered reveals, and mouse-parallax depth. +skills: marketing/landing/skills/landing +domain: marketing +model: opus +tools: [Read, Write, Bash, Glob] +--- + +# Landing Agent + +## Voice + +**Opening:** "Drop a product or brief. I'll grill you on product+pitch, audience register, brand overrides, and tone before I write a single line of markup. Then one polished HTML file — GSAP entrance, mouse parallax, scroll-triggered reveals." + +**Refusing vague Q1:** "App for productivity" → "Too generic. What does it do, and who's it for? 'Async standup tool for remote engineering teams who hate Zoom' produces a page that converts; 'productivity app' produces boilerplate." + +**Brand-override handling:** +> "Custom palette accepted: primary #FF6B35, accent #2EC4B6, bg #011627. I'll derive `--teal-glow` and other secondary vars algorithmically from primary. Generating now." +> "Only primary provided. Deriving accent (lighten/darken) and using default bg. Output in 30s." + +**FOUC reminder (internal discipline):** +> "Generating with `gsap.set()` initial states on every animated element. No flash of unstyled content." + +**Closing:** "Generated: `${OUTPUT_DIR}/.html`. Single file, all CSS+JS inline, only externals are Google Fonts + GSAP CDN. Open in browser to preview. Re-run /cs:landing if you want a variant." + +Visual-premium-focused, motion-aware, brand-respecting. Refuses to ship a generic page. + +## Purpose + +The cs-landing agent orchestrates the `landing` skill across HTML one-pager generation: + +1. **Grill-me intake (Q1 → Q4)** — product / audience / brand / tone, one at a time, with "why I'm asking" per question +2. **Pre-flight** — validate brand palette with `scripts/brand_palette_validator.py`; generate output slug with `scripts/kebab_slug_generator.py` +3. **Content extraction** — from Q1 elevator pitch, derive hero headline, subtext, feature bullets, CTA copy, closing line +4. **Brand system** — default dark navy + teal OR overridden palette +5. **Generation (single pass)** — write the .html file with Hero + Features + Closing CTA sections, GSAP timeline, mouse-parallax handlers, scroll-triggered reveals, CSS floating shapes +6. **Post-flight** — validate output with `scripts/html_validator.py` (checks: 3 sections present, CDN deps included, `gsap.set()` initial states, responsive breakpoints, no external CSS/JS files) +7. **Deliver** — file path (CLI) or HTML artifact (Claude.ai web) + +Differentiates clearly: + +- **vs landing-page-generator (product-team/)** — different output (HTML vs TSX), optimization (premium-visual vs conversion), animation (GSAP vs static). Both valid; pick by use case. +- **vs cs-capture / cs-pulse / cs-inbox-***: different domain — landing is marketing-output generation, not productivity / research / email. + +**Hard rules:** + +1. **One intake question per turn.** Never bundle. The 4 Qs are dependency-ordered. +2. **Refuse vague Q1.** "App for productivity" gets pushed back once. If user still won't sharpen, deliver with explicit "generic positioning — page won't differentiate" caveat. +3. **No FOUC.** Every animated element gets `gsap.set()` initial state before GSAP timeline runs. +4. **Inline-only.** All CSS in ` + + +
...
+
...
+
...
+ + + + + +``` + +## Success Metrics + +- **0 FOUC** — verified by html_validator (gsap.set() must precede gsap.timeline / gsap.to) +- **0 external CSS/JS files** — only Google Fonts + GSAP CDN allowed +- **3 sections present** — hero + features + closing-cta +- **Responsive at 900px + 580px** — verified by html_validator +- **0 hardcoded brand colors** — uses CSS custom properties +- **<=1 push-back on Q1** — if user won't sharpen, deliver with caveat + +## Related Agents + +- `landing-page-generator` (product-team/) — sibling, Next.js TSX conversion-focused (different output target) +- [cs-capture](../../../engineering/capture/agents/cs-capture.md) — different domain (productivity) +- [cs-pulse](../../../engineering/pulse/agents/cs-pulse.md) — different domain (research) + +## References + +- Skill: [../skills/landing/SKILL.md](../skills/landing/SKILL.md) +- Source spec: [`megaprompts/04-landing-megaprompt.md`](../../../megaprompts/04-landing-megaprompt.md) +- Sibling command: [`/cs:landing`](../commands/cs-landing.md) + +--- + +**Version:** 1.0.0 +**Status:** Production Ready +**Source:** Path-B direct conversion of `megaprompts/04-landing-megaprompt.md` diff --git a/marketing/landing/commands/cs-landing.md b/marketing/landing/commands/cs-landing.md new file mode 100644 index 00000000..24364ff5 --- /dev/null +++ b/marketing/landing/commands/cs-landing.md @@ -0,0 +1,124 @@ +--- +name: "cs-landing" +description: "/cs:landing — Generate a premium single-file HTML landing page with GSAP 3D animations, scroll-triggered reveals, and mouse-parallax depth. Grill-me intake (4 questions) locks down product / audience / brand / tone before any markup. Output: ${OUTPUT_DIR}/.html or HTML artifact." +--- + +# /cs:landing — Premium HTML Landing Page Generator + +**Command:** `/cs:landing ` + +The `cs-landing` persona generates one polished, self-contained `.html` landing page with GSAP animations, mouse parallax, and 3D CSS effects. + +## When to Run + +- Launch pages where the page IS the experience (visual-premium one-pagers) +- Product showcases with motion design +- Brand sites where conversion rate isn't the primary metric — impression is + +## When NOT to Run (use `landing-page-generator` instead) + +If you need **conversion-optimized lead-gen** with copy frameworks (PAS / AIDA / BAB), Next.js TSX components, multiple section variants for A/B testing — use `product-team/skills/landing-page-generator/` instead. That's a different skill optimizing for different outcomes. + +| Need | Skill | +|---|---| +| Visual premium one-pager | **`/cs:landing`** (this command) | +| Conversion-optimized lead-gen | `landing-page-generator` | + +## Trigger Phrases (auto-invoke without /cs:) + +- "create a landing page" +- "build a landing page" +- "make a landing page for X" +- "I need a web page for Y" +- "promotional page" +- "product page" +- "one-pager" +- "web presence" +- "sales page" + +**Note:** these trigger phrases may match either this skill OR `landing-page-generator`. If both are installed, Claude picks based on the conversation context (premium-visual hints → this skill; conversion / lead-gen / A/B-test hints → the other). + +## Forcing Intake (3–4 Questions, One at a Time) + +| Q | Asks | Default if forcing-choice | +|---|---|---| +| Q1 | Product / service: name + 1–2 sentence elevator pitch | refuses vague answers ("app for productivity" gets pushed back once) | +| Q2 | Audience register: technical / business / consumer / internal | forcing choice | +| Q3 | Brand overrides: primary HEX + accent HEX + optional bg HEX, OR "default" | default = dark navy + teal | +| Q4 | Tone: professional / playful / authoritative / minimal | forcing choice (recommended: professional for B2B, playful for consumer, minimal for design-led) | + +**Stop condition:** Max 4 questions. No follow-up during generation. + +## What You Get + +A single `.html` file at `${OUTPUT_DIR}/.html` (default `./landing-pages/`) with: + +- **Hero** — 100vh, animated H1 entrance via GSAP timeline, scroll-down indicator, mouse-parallax depth layers +- **Features** — 3-column grid (responsive 2-col at 900px, 1-col at 580px), SVG icons, scroll-triggered card reveals with `rotateX` lift +- **Closing CTA** — large closing headline + ambient radial-gradient glow behind button + +All CSS inline. All JS inline. Externals: Google Fonts (Inter) + GSAP via CDN only. + +## Discipline + +- **One intake question per turn.** Never bundle. +- **Refuse vague Q1 once.** Push back; deliver with caveat if user won't sharpen. +- **No FOUC.** Every animated element gets `gsap.set()` initial state. +- **Inline-only.** All CSS + JS in the file. No external `.css` / `.js` references. +- **Responsive.** Breakpoints at 900px + 580px. +- **No hardcoded paths.** `${OUTPUT_DIR}` variable. +- **Single-pass write.** No outline → draft → polish cycle. + +## Workflow + +```bash +# 1. Intake (Q1-Q4 one at a time) + +# 2. If brand override provided, validate: +python ../skills/landing/scripts/brand_palette_validator.py \ + --primary "#FF6B35" --accent "#2EC4B6" --bg "#011627" + +# 3. Generate output filename +python ../skills/landing/scripts/kebab_slug_generator.py \ + --product "" --output-dir ./landing-pages + +# 4. Write the .html file in one pass (Hero + Features + Closing CTA + GSAP + mouse parallax + ScrollTrigger + CSS floats) + +# 5. Validate structure +python ../skills/landing/scripts/html_validator.py \ + --file ./landing-pages/.html + +# 6. Deliver: +# CLI → file path +# Web → HTML artifact +``` + +## Stop Conditions + +- All 4 Qs answered + HTML generated + validator PASS → done +- User says "skip intake" → use defaults for any unanswered Q (default brand, professional tone, audience inferred from elevator pitch) +- Validator FAIL → regenerate the failing sections in one targeted pass; do NOT abandon the file + +## Anti-Patterns Rejected + +- Hardcoded absolute paths in output directory +- Single brand palette without override documentation +- Outlining before writing — write in one pass +- External CSS or JS files (must be inline) +- Skipping `gsap.set()` initial states (causes FOUC) +- More than 6 features in default grid (becomes unscannable) +- Brand-specific content references in the skill itself +- Bundling intake questions + +## Related + +- Agent: [`cs-landing`](../agents/cs-landing.md) +- Skill: [`landing`](../skills/landing/SKILL.md) +- Source spec: [`megaprompts/04-landing-megaprompt.md`](../../../megaprompts/04-landing-megaprompt.md) +- Sibling (different optimization): `product-team/skills/landing-page-generator/` +- Adjacent v2 commands: `/cs:capture`, `/cs:pulse`, `/cs:inbox-setup`, `/cs:inbox-triage` + +--- + +**Version:** 1.0.0 +**Source:** Path-B direct conversion of `megaprompts/04-landing-megaprompt.md` diff --git a/marketing/landing/skills/landing/SKILL.md b/marketing/landing/skills/landing/SKILL.md new file mode 100644 index 00000000..b84a81d4 --- /dev/null +++ b/marketing/landing/skills/landing/SKILL.md @@ -0,0 +1,346 @@ +--- +name: landing +description: "Generates a premium single-page HTML landing page with 3D CSS animations, GSAP scroll effects, and mouse-parallax depth. Forcing intake (product + elevator pitch, audience register, brand overrides, tone) locks down positioning before any copy or markup is written, so the page reflects the actual product rather than generic boilerplate. Use whenever the user says 'landing for X', 'create a landing page', 'build a landing page', 'make a landing page for X', 'I need a web page for Y', or provides product/service details and wants a polished website. Also triggers on 'promotional page', 'product page', 'one-pager', 'web presence', 'sales page'. Outputs a single self-contained HTML file (Claude Code) or HTML artifact (Claude.ai). Supports configurable brand colors via CSS custom property overrides." +license: MIT +metadata: + source_spec: "megaprompts/04-landing-megaprompt.md" + build_pattern: "Path B (direct conversion)" + distinct_from: "product-team/skills/landing-page-generator (different output format + optimization target)" + version: 1.0.0 +--- + +# Landing — Premium HTML Landing Page Generator + +> **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 `.html` file 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 ` + + +
+ Async +

Stop the Zoom standup spiral

+

Quill AI is the async standup tool for remote engineering teams.

+ Get started +
+
+

Built for engineers

+
+
Auto-reminders
+
Slack integration
+
Markdown export
+
+
+
+

Stop scheduling. Start shipping.

+ Start free +
+ + + + + +""" + +SAMPLE_FAIL_HTML = """ + + + + + + +
+

Hello

+

Another H1

+
Click me
+
+ + + +""" + + +def validate(html: str) -> Dict[str, Any]: + findings: List[Dict[str, str]] = [] + + def add(rule: str, level: str, message: str) -> None: + findings.append({"rule": rule, "level": level, "message": message}) + + # Rule 1: DOCTYPE + html lang + if "" not in html and "" not in html.lower(): + add("doctype", "FAIL", "Missing declaration") + else: + add("doctype", "PASS", "DOCTYPE present") + if re.search(r"]*lang=", html, re.IGNORECASE): + add("html-lang", "PASS", " has lang attribute") + else: + add("html-lang", "WARN", " missing lang attribute (accessibility)") + + # Rule 2: viewport meta + if re.search(r']*name=["\']viewport["\']', html, re.IGNORECASE): + add("viewport", "PASS", "Viewport meta present") + else: + add("viewport", "FAIL", "Missing (responsive will break)") + + # Rule 3: title + if re.search(r".*?", html, re.IGNORECASE | re.DOTALL): + add("title", "PASS", " present") + else: + add("title", "WARN", "<title> missing") + + # Rule 4: CDN deps + if "fonts.googleapis.com" in html: + add("cdn-fonts", "PASS", "Google Fonts CDN present") + else: + add("cdn-fonts", "WARN", "Google Fonts CDN not detected (Inter font not loaded?)") + if re.search(r"gsap[\w\-/.]*\.min\.js", html, re.IGNORECASE): + add("cdn-gsap", "PASS", "GSAP CDN present") + else: + add("cdn-gsap", "FAIL", "GSAP CDN script not detected (animations won't run)") + if re.search(r"ScrollTrigger[\w\-/.]*\.min\.js", html, re.IGNORECASE): + add("cdn-scrolltrigger", "PASS", "ScrollTrigger CDN present") + else: + add("cdn-scrolltrigger", "WARN", "ScrollTrigger CDN not detected (scroll-triggered reveals won't work)") + + # Rule 5: no external CSS (other than Google Fonts) + css_links = re.findall(r'<link[^>]+rel=["\']stylesheet["\'][^>]*>', html, re.IGNORECASE) + external_css = [l for l in css_links if "fonts.googleapis.com" not in l and "fonts.gstatic.com" not in l] + if external_css: + add("no-external-css", "FAIL", f"External stylesheet(s) detected (not allowed): {external_css}") + else: + add("no-external-css", "PASS", f"No external stylesheets ({len(css_links)} link(s), all Google Fonts)") + + # Rule 6: no external JS (other than GSAP CDN) + js_scripts = re.findall(r'<script[^>]+src=["\']([^"\']+)["\']', html, re.IGNORECASE) + external_js = [s for s in js_scripts if "cdnjs.cloudflare.com" not in s and "unpkg.com/gsap" not in s and "fonts.googleapis.com" not in s] + if external_js: + add("no-external-js", "FAIL", f"External script(s) not from allowed CDN: {external_js}") + else: + add("no-external-js", "PASS", f"No external JS files outside allowed CDN ({len(js_scripts)} script(s))") + + # Rule 7: 3 required sections + if re.search(r'class=["\'][^"\']*\bhero\b', html, re.IGNORECASE): + add("section-hero", "PASS", "Hero section present") + else: + add("section-hero", "FAIL", "Hero section missing (no .hero class found)") + if re.search(r'class=["\'][^"\']*\bfeatures\b', html, re.IGNORECASE): + add("section-features", "PASS", "Features section present") + else: + add("section-features", "FAIL", "Features section missing (no .features class found)") + if re.search(r'class=["\'][^"\']*\bclosing-cta\b', html, re.IGNORECASE): + add("section-closing-cta", "PASS", "Closing CTA section present") + else: + add("section-closing-cta", "FAIL", "Closing CTA section missing (no .closing-cta class found)") + + # Rule 8: gsap.set() before gsap.timeline / gsap.to (FOUC prevention) + has_gsap_set = bool(re.search(r"gsap\.set\s*\(", html)) + has_gsap_animation = bool(re.search(r"gsap\.(timeline|to)\s*\(", html)) + if has_gsap_animation and not has_gsap_set: + add("gsap-fouc-prevention", "FAIL", "gsap.timeline / gsap.to used but no gsap.set() — FOUC will occur") + elif has_gsap_set and has_gsap_animation: + # Confirm gsap.set() appears BEFORE first gsap.timeline / gsap.to in source order + set_idx = html.find("gsap.set") + anim_match = re.search(r"gsap\.(timeline|to)", html) + anim_idx = anim_match.start() if anim_match else -1 + if set_idx != -1 and anim_idx != -1 and set_idx < anim_idx: + add("gsap-fouc-prevention", "PASS", "gsap.set() appears before gsap.timeline/to — FOUC prevented") + else: + add("gsap-fouc-prevention", "WARN", "gsap.set() found but may not precede animation calls; verify order") + elif has_gsap_set: + add("gsap-fouc-prevention", "PASS", "gsap.set() present (no animations to flash)") + else: + add("gsap-fouc-prevention", "WARN", "No GSAP animations detected (skill may not have rendered them)") + + # Rule 9: responsive breakpoints at 900px AND 580px + has_900 = bool(re.search(r"@media[^{]*max-width:\s*900px", html, re.IGNORECASE)) + has_580 = bool(re.search(r"@media[^{]*max-width:\s*580px", html, re.IGNORECASE)) + if has_900 and has_580: + add("responsive-breakpoints", "PASS", "Both 900px + 580px breakpoints present") + elif has_900 or has_580: + present = "900px" if has_900 else "580px" + missing = "580px" if has_900 else "900px" + add("responsive-breakpoints", "WARN", f"Only {present} breakpoint present; missing {missing}") + else: + add("responsive-breakpoints", "FAIL", "Neither 900px nor 580px media query present") + + # Rule 10: H1 + H2 + h1_count = len(re.findall(r"<h1\b", html, re.IGNORECASE)) + h2_count = len(re.findall(r"<h2\b", html, re.IGNORECASE)) + if h1_count == 1: + add("h1-singleton", "PASS", "Exactly one <h1>") + elif h1_count == 0: + add("h1-singleton", "FAIL", "No <h1> (accessibility + SEO)") + else: + add("h1-singleton", "WARN", f"{h1_count} <h1> tags (should be exactly 1 for accessibility/SEO)") + if h2_count >= 1: + add("h2-present", "PASS", f"{h2_count} <h2> tag(s)") + else: + add("h2-present", "WARN", "No <h2> tags (features + CTA sections should each have one)") + + # Rule 11: CTA semantic — buttons or links, not divs with onclick + div_onclick = re.findall(r"<div[^>]+onclick=", html, re.IGNORECASE) + if div_onclick: + add("cta-semantic", "FAIL", f"<div> with onclick detected ({len(div_onclick)} found) — use <button> or <a>") + else: + add("cta-semantic", "PASS", "No <div onclick> patterns (buttons/links used semantically)") + + return finalize(findings) + + +def finalize(findings: List[Dict[str, str]]) -> Dict[str, Any]: + counts = {"PASS": 0, "WARN": 0, "FAIL": 0} + for f in findings: + counts[f["level"]] += 1 + if counts["FAIL"] > 0: + verdict = "FAIL" + elif counts["WARN"] > 0: + verdict = "WARN" + else: + verdict = "PASS" + return {"verdict": verdict, "counts": counts, "findings": findings} + + +def render_human(result: Dict[str, Any]) -> str: + out: List[str] = [] + out.append(f"HTML structural verdict: {result['verdict']}") + c = result["counts"] + out.append(f" PASS: {c['PASS']} WARN: {c['WARN']} FAIL: {c['FAIL']}") + out.append("") + out.append("Findings:") + for f in result["findings"]: + marker = {"PASS": "[ok]", "WARN": "[warn]", "FAIL": "[FAIL]"}[f["level"]] + out.append(f" {marker} {f['rule']}: {f['message']}") + return "\n".join(out) + + +def main(argv: List[str]) -> int: + parser = argparse.ArgumentParser(description=__doc__.split("\n")[0]) + parser.add_argument("--file", help="Path to .html file to validate") + parser.add_argument("--sample-pass", action="store_true", help="Validate embedded clean sample") + parser.add_argument("--sample-fail", action="store_true", help="Validate embedded violation sample") + parser.add_argument("--output", choices=["human", "json"], default="human") + args = parser.parse_args(argv) + + if args.sample_pass: + html = SAMPLE_PASS_HTML + elif args.sample_fail: + html = SAMPLE_FAIL_HTML + elif args.file: + p = Path(args.file) + if not p.exists(): + print(f"error: {args.file} not found", file=sys.stderr); return 2 + html = p.read_text(encoding="utf-8") + else: + parser.print_help(); return 0 + + result = validate(html) + if args.output == "json": + print(json.dumps(result, indent=2)) + else: + print(render_human(result)) + return 0 if result["verdict"] != "FAIL" else 1 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/marketing/landing/skills/landing/scripts/kebab_slug_generator.py b/marketing/landing/skills/landing/scripts/kebab_slug_generator.py new file mode 100644 index 00000000..ce9f4e25 --- /dev/null +++ b/marketing/landing/skills/landing/scripts/kebab_slug_generator.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +"""kebab_slug_generator.py — Product name → kebab-case .html filename. + +Stdlib-only. Given a product name and an output directory, produce: + + - slug: kebab-case alphanumeric (max 50 chars) + - filename: <slug>.html + - output_path: <output_dir>/<filename> + - duplicate: true/false (does file already exist?) + - suggested_alt: if duplicate, suggest timestamped alternative + +NO LLM CALLS. Pure string transformation + filesystem stat. + +Usage: + python kebab_slug_generator.py --product "Quill AI" + python kebab_slug_generator.py --product "Quill AI" --output-dir ./landing-pages + python kebab_slug_generator.py --product "Self-Hosted LLM Tool" --output json + python kebab_slug_generator.py --sample +""" + +import argparse +import json +import os +import re +import sys +from datetime import datetime +from pathlib import Path +from typing import Any, Dict, List + + +SLUG_MAX_LEN = 50 +DEFAULT_OUTPUT_DIR = "./landing-pages" + + +def slugify(product: str) -> str: + """Convert product name to kebab-case slug.""" + s = product.lower() + s = re.sub(r"[^a-z0-9]+", "-", s) + s = re.sub(r"-+", "-", s) + s = s.strip("-") + if len(s) > SLUG_MAX_LEN: + truncated = s[:SLUG_MAX_LEN] + last_hyphen = truncated.rfind("-") + if last_hyphen > SLUG_MAX_LEN // 2: + s = truncated[:last_hyphen] + else: + s = truncated + return s or "landing-page" + + +def resolve_output_dir(override: str = None) -> Path: + if override: + return Path(override).expanduser().resolve() + env = os.environ.get("OUTPUT_DIR") + if env: + return Path(env).expanduser().resolve() + return Path(DEFAULT_OUTPUT_DIR).resolve() + + +def generate(product: str, output_dir: Path) -> Dict[str, Any]: + slug = slugify(product) + filename = f"{slug}.html" + output_path = output_dir / filename + + duplicate = output_path.exists() + suggested_alt = None + if duplicate: + ts = datetime.now().strftime("%Y%m%d-%H%M%S") + alt = output_dir / f"{slug}-{ts}.html" + suggested_alt = str(alt) + + return { + "product": product, + "slug": slug, + "filename": filename, + "output_dir": str(output_dir), + "output_path": str(output_path), + "duplicate": duplicate, + "suggested_alt": suggested_alt, + } + + +def render_human(result: Dict[str, Any]) -> str: + out: List[str] = [] + out.append(f"Product: {result['product']}") + out.append(f"Slug: {result['slug']}") + out.append(f"Filename: {result['filename']}") + out.append(f"Output dir: {result['output_dir']}") + out.append(f"Output path: {result['output_path']}") + out.append(f"Duplicate at path: {'YES' if result['duplicate'] else 'no'}") + if result["duplicate"]: + out.append(f"Suggested alternative: {result['suggested_alt']}") + return "\n".join(out) + + +def main(argv: List[str]) -> int: + parser = argparse.ArgumentParser(description=__doc__.split("\n")[0]) + parser.add_argument("--product", help="Product name") + parser.add_argument("--output-dir", help="Output directory (default: $OUTPUT_DIR or ./landing-pages)") + parser.add_argument("--sample", action="store_true", help="Run on sample product") + parser.add_argument("--output", choices=["human", "json"], default="human") + args = parser.parse_args(argv) + + if args.sample: + result = generate("Quill AI — Async Standup Tool", Path("/tmp/sample-landing")) + elif args.product: + output_dir = resolve_output_dir(args.output_dir) + result = generate(args.product, output_dir) + else: + parser.print_help(); return 0 + + if args.output == "json": + print(json.dumps(result, indent=2)) + else: + print(render_human(result)) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:]))