- name
- landing-page-builder
- description
- Build premium landing pages that don't smell like AI slop — concept-first atmosphere, real proof points, motion that respects users, and JSON-LD that gets you cited by Google rich results and AI search engines. Use when building a marketing site or landing page from scratch (Next.js / Tailwind / shadcn), iterating on a hero that feels generic or "Bootstrap-y", launching a SaaS / B2B / agency single-page offer, or requesting dark + light hero variants for A/B feel.
- metadata
- {"author":"DevOtts","author_url":"https://github.com/DevOtts"}
# Landing Page Builder
Build premium landing pages that don't smell like AI slop — concept-first atmosphere, real proof points, motion that respects users, and JSON-LD that gets you cited by Google rich results and AI search engines.
---
## When to use
Trigger this skill when the user (or any user) is:
- Building a new marketing site / landing page from scratch (Next.js, Tailwind, shadcn)
- Iterating on a hero or above-the-fold and the current state feels "generic", "AI slop", "vanilla", or "Bootstrap-y"
- Launching a SaaS, AI product, B2B service, agency, or POC offer that needs a single high-conversion page
- Mentioning "hero section", "conversion", "lead capture", "lead modal", "above the fold", "premium feel", or "marketing site"
- Asking for a side-by-side dark + light hero variant so they can A/B feel
- Adding structured data / JSON-LD / GEO (Generative Engine Optimization) coverage to an existing site
Do **not** trigger for app/dashboard UI work, internal tooling, or component libraries — that's `ui-styling` or `ui-ux-pro-max` territory without the LP framing.
---
## 1. Skill dependencies & orchestration
This skill is a conductor, not a soloist. **Invoke these in order:**
1. **`/ui-ux-pro-max` — FIRST, always.** Pull design intelligence for the product type before you write a line of JSX. It returns typography pairings, color systems, layout rules, and product-type-specific UX patterns (SaaS LP ≠ portfolio LP ≠ agency LP). Skipping this step is how you end up with "AI slop": generic gradient + 3-up feature grid + bare CTA.
2. **`mcp__magic__21st_magic_component_inspiration`** — Pull hero, pricing, CTA, FAQ section ideas from the 21st.dev gallery. Use early, during the "what does this look like" phase. Returns curated component refs, often with code.
3. **`mcp__magic__21st_magic_component_builder` / `_refiner`** — Once you've picked a direction, use the builder to scaffold a section and the refiner to evolve it. Don't reach for these before step 1 + 2.
4. **`/chrome-cdp-control` or headless Playwright** — Capture screenshots of reference sites (see Section 4). Real visual study beats remembered impressions. CDP if the user has it running; otherwise a one-shot `playwright screenshot` script.
5. **`/favicon-builder`** — At handoff time, generate the favicon set from the product logomark. Never ship the full horizontal marketing logo as a favicon — it renders as a 2-pixel smear.
6. **`/banner-design` (optional)** — When you need OG images, social cards, or hero illustrations beyond what shadcn provides.
7. **`/design`** (optional) — Brand identity / CIP work if the project is greenfield.
The unhappy path: jumping straight to writing `<HeroSection>` without `/ui-ux-pro-max`. Every time you do this, the result is "AI slop." Don't.
---
## 2. Reference sites — capture, then steal
Open each in Chrome (CDP) or headless Playwright, screenshot above-the-fold + scroll halfway + scroll to footer. Save to a `references/` folder in the project. Study before you build.
- **<https://supahero.io/>** — Curated hero gallery. Composition study: how columns split, where eyebrow/headline/sub/CTA stack, how product imagery is framed. Use as a mood board before picking a direction.
- **<https://shadcnstudio.com/blocks/marketing-ui/hero-section>** — shadcn-native marketing blocks. Source these when the project is already on shadcn — the components drop in with zero theme reconciliation.
- **<https://sentine-lp.vercel.app/>** — Minimalist dark hero, perfect ratio of negative space to content. Steal the proof-pill row pattern and the subtle aurora atmosphere.
- **<https://www.redentia.com.br/>** — The reference for the 2-column dark-pitch + light-form lead capture modal we shipped. Mimic the panel ratio, the value-prop bullets on the dark side, the form density on the light side.
- **<https://betterstack.com/>** — Premium SaaS polish. Watch how every element has a faint shadow, a hairline border, and a single accent color carrying the brand. The discipline is in what's *missing*.
- **<https://wope.com/>** — Motion-heavy reference. Scroll-orchestrated reveals, sticky transforms, animated counters tied to viewport position. Pull pacing ideas, not the entire choreography.
- **<https://www.dialedweb.com/>** — Typography-first. Oversized headlines, tight leading, gradient on one word only. The opposite of "huge hero image" — let type carry the page.
Rule: never copy 1:1. Steal a *pattern* (column split, atmosphere recipe, reveal cadence), reskin it with the project's brand tokens.
---
## 3. Page structure — the canonical 7 sections
Every premium LP we've shipped collapses to this skeleton. Build each as its own file under `components/marketing/landing/`. Use the Next.js App Router route group `(marketing)` so the marketing site shares no layout chrome with the authenticated app.
```
app/
(marketing)/
layout.tsx # marketing-only chrome (different header/footer)
page.tsx # the LP itself — composes the 7 sections
robots.ts
sitemap.ts
components/marketing/
landing/
HeroSection.tsx # 1. Hero (dark variant)
HeroCleanSection.tsx # 1b. Hero (light variant) — A/B swap
TrustStrip.tsx # 2. logos / press / social proof bar
ProblemSection.tsx # 3. the pain you solve
SolutionSection.tsx # 4. how the product works
OfferSection.tsx # 5. price / POC / what they get
FAQSection.tsx # 6. answer-first Q&A
CTASection.tsx # 7. final ask
LeadCaptureModal.tsx # 2-column lead modal
LeadModalProvider.tsx # context: openLeadModal()
primitives/
AnimatedCounter.tsx
ScrollReveal.tsx
ParticleField.tsx
smoothScrollTo.ts
StructuredData.tsx # all JSON-LD components
```
`page.tsx` composes the 7 sections and wraps inner anchors with `scroll-mt-24` so the sticky header doesn't eat the headline on hash navigation:
```tsx
<main className="flex flex-col min-h-screen">
<HeroSection />
<TrustStrip />
<div id="problem" className="scroll-mt-24"><ProblemSection /></div>
<div id="solution" className="scroll-mt-24"><SolutionSection /></div>
<div id="offer" className="scroll-mt-24"><OfferSection /></div>
<div id="faq" className="scroll-mt-24"><FAQSection /></div>
<CTASection />
</main>
```
Each section file owns its own background, padding, and theme tokens — never one giant `page.tsx` doing it all.
---
## 4. Tech stack patterns
- **Next.js 15 App Router** — Server Components by default; only `"use client"` on motion + interactive bits.
- **Tailwind v4** — Theme via CSS variables in `globals.css`. shadcn tokens (`bg-background`, `text-foreground`, `bg-card`, `ring-border`) so dark/light swap is one root-class change.
- **shadcn/ui** — `Button`, `Dialog`, `Input`, `Label`, `Accordion` as the foundation. Never re-roll these.
- **`motion@^12+`** (the Framer Motion successor, package name `motion`) — `import { motion, useReducedMotion, useInView, animate } from "motion/react"`. Always import from `motion/react`, not `framer-motion`.
- **lucide-react** for icons. 1.5px stroke (`strokeWidth={1.5}`) at most sizes; 2px only for chunky 24px+ icons.
- **JSON-LD structured data** — Organization, WebSite, SoftwareApplication, FAQPage, LandingWebPage, BreadcrumbList. Cross-link via stable `@id` URLs (e.g. `${baseUrl}/#organization`) so engines resolve the graph.
- **AI bot allowlist in `app/robots.ts`** — explicit `allow: "/"` for: `GPTBot`, `ChatGPT-User`, `OAI-SearchBot`, `ClaudeBot`, `anthropic-ai`, `Claude-Web`, `PerplexityBot`, `Perplexity-User`, `CCBot`, `Google-Extended`, `GoogleOther`, `Applebot-Extended`, `Bytespider`, `Amazonbot`, `Meta-ExternalAgent`, `FacebookBot`. Being absent from these crawlers' allowlists is being invisible in AI search.
- **`app/sitemap.ts`** — generated from a small URL list, includes `lastModified` per route.
---
## 5. Animation primitives — build these once, reuse forever
These four primitives carry every premium LP we've shipped. Put them in `components/marketing/landing/primitives/` and never re-implement.
### `AnimatedCounter`
```ts
<AnimatedCounter to={1_847_320} duration={2.6} className="text-5xl font-bold" />
```
Animates from `from` (default 0) to `to` over `duration` seconds. Triggers when the element enters the viewport via `useInView`. Locale-aware via `Intl.NumberFormat` (default `pt-BR`, easy to swap). **Respects `useReducedMotion`** — snaps to the final value instead of animating. Wraps in a `font-tabular` span so the number doesn't jitter mid-count.
Critical detail from our real code:
```tsx
const intlBR = new Intl.NumberFormat("pt-BR", { maximumFractionDigits: 0 });
// inside the effect:
const controls = animate(from, to, {
duration,
ease: [0.16, 1, 0.3, 1],
onUpdate: (latest) => setDisplay(format(latest)),
});
```
### `ScrollReveal`, `ScrollRevealGroup`, `ScrollRevealItem`
```tsx
<ScrollReveal direction="up" delay={0.1}>...</ScrollReveal>
<ScrollRevealGroup staggerChildren={0.08}>
<ScrollRevealItem>...</ScrollRevealItem>
<ScrollRevealItem>...</ScrollRevealItem>
</ScrollRevealGroup>
```
`ScrollReveal` is the atomic entrance primitive — `opacity 0 → 1` plus a directional offset (`up`/`down`/`left`/`right`/`none`). Uses `motion`'s `whileInView` with `viewport={{ once: true, amount: 0.2 }}` so each block fires once when 20% visible. `Group` + `Item` is for orchestrated staggers — feature grids, FAQ accordions, value-prop lists. Always honors `useReducedMotion`.
### `ParticleField`
```tsx
<ParticleField count={36} color="text-emerald-300" seed={11} className="-z-10 opacity-70" />
```
CSS-only floating particle dots for hero atmosphere. **SSR-safe**: positions are computed via a seeded `mulberry32` PRNG so server-rendered HTML matches the first client render (no hydration mismatch warnings). Each particle floats via `animate-float-y` with negative animation delays so the field is already in motion on mount instead of all starting from `y: 0`.
```ts
function mulberry32(seed: number) {
return () => {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
```
Layer two fields (emerald + blue, different seeds) for richer texture.
### `smoothScrollTo`
```tsx
<a href="#problem" onClick={smoothScrollTo("problem")}>How it works</a>
```
`onClick` factory that prevents the default browser jump, calls `el.scrollIntoView({ behavior: "smooth", block: "start" })`, and syncs the URL hash via `history.replaceState`. Native smooth scrolling honors `prefers-reduced-motion` automatically in Safari/Firefox.
```ts
export function smoothScrollTo(targetId: string) {
return (e: MouseEvent<HTMLAnchorElement>) => {
const el = document.getElementById(targetId);
if (!el) return;
e.preventDefault();
el.scrollIntoView({ behavior: "smooth", block: "start" });
history.replaceState(null, "", `#${targetId}`);
};
}
```
---
## 6. CSS utilities — add to `globals.css`
These are the brushes the hero uses to paint atmosphere. Add once, reach for everywhere.
```css
/* === Background utilities === */
.bg-mesh-aurora {
background:
radial-gradient(at 20% 30%, rgba(56,189,248,0.15) 0%, transparent 50%),
radial-gradient(at 80% 20%, rgba(16,185,129,0.15) 0%, transparent 50%),
radial-gradient(at 50% 90%, rgba(139,92,246,0.10) 0%, transparent 50%);
}
.bg-mesh-aurora-dense {
background:
radial-gradient(at 15% 25%, rgba(56,189,248,0.25) 0%, transparent 45%),
radial-gradient(at 85% 15%, rgba(16,185,129,0.22) 0%, transparent 45%),
radial-gradient(at 50% 95%, rgba(139,92,246,0.15) 0%, transparent 45%);
}
.bg-grid-dots {
background-image: radial-gradient(circle, currentColor 1px, transparent 1px);
background-size: 22px 22px;
}
/* === Keyframes === */
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
@keyframes scan-beam {
0%, 100% { transform: translateY(0); opacity: 0; }
50% { opacity: 1; }
100% { transform: translateY(100%); }
}
@keyframes profit-pulse {
0%, 100% { transform: scale(1); opacity: 0.7; }
50% { transform: scale(1.6); opacity: 0; }
}
@keyframes float-y {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
@keyframes mesh-drift {
0%, 100% { background-position: 0% 0%; }
50% { background-position: 100% 100%; }
}
.animate-shimmer { animation: shimmer 3s linear infinite; }
.animate-scan-beam { animation: scan-beam 4s ease-in-out infinite; }
.animate-profit-pulse { animation: profit-pulse 2s ease-in-out infinite; }
.animate-float-y { animation: float-y 6s ease-in-out infinite; }
.animate-mesh-drift { animation: mesh-drift 20s ease-in-out infinite; }
/* === Tabular numerals — counter stability === */
.font-tabular { font-variant-numeric: tabular-nums; }
/* === Global reduced-motion neutralizer === */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
View on GitHub