# Product Page

> Build a {{BRAND_NAME}} product / generator / builder / creator landing page end-to-end, starting with a Phase 0 SERP + LLM ranking-factor recon before any copy is written. Ships as a CMS pages entry that mirrors the ai-form-generator design exactly (6 macro components + 6 raw-html components + 1 raw-html tail — 13 slots total). Trigger with `/product-page` or when the user asks to "build a product page", "build the <keyword> generator page", "build a {{BRAND_NAME}} tool page" and wants SERP + LLM rankings baked into the design.

- Skill: `harsh719/product-page` (Agent Skill)
- Install (CLI): `npx skillmds@latest add harsh719/product-page`
- Raw SKILL.md: https://api.skillmd.com/api/skills/harsh719/product-page/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: harsh719 (https://skillmd.com/u/harsh719)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/harsh719/product-page

---

<!-- SETUP:BEGIN -->
## Before you run this skill

This skill is brand-neutral. It reads its brand, palette and endpoints from
`brand.config.json` at the repo root.

**On first use, do this before anything else:**

1. Run `python3 brandkit.py`. It prints the config source and any placeholder
   that is still unset.
2. If it says `configured: False`, copy `brand.config.example.json` to
   `brand.config.json`.
3. Ask the operator for each value under `missing`, then write them in. Do not
   guess a brand name, a domain, or a colour.
4. Anything the skill writes out should be passed through
   `brandkit.fill(text)`, which swaps every `{{TOKEN}}` for its configured value
   and remaps the default palette to the operator's.

Text below uses `{{TOKEN}}` where a value is operator-specific. Treat an
unresolved `{{TOKEN}}` in your output as a bug, not as literal copy.

<!-- SETUP:END -->

<!-- CMS:BEGIN -->
## Publishing: which CMS

Never write CMS calls by hand in this skill. Use the shared adapter layer, which
covers Strapi, WordPress, Contentful, Sanity, Ghost, Webflow, Payload, Directus,
and plain files.

```python
from cms import get_adapter, Page, CMSError

cms  = get_adapter()                       # reads cms.kind from brand.config.json
page = Page(slug=slug, title=title, description=meta_desc,
            html=body_html, blocks=components, jsonld=graph)

entry_id = cms.upsert(page)                # ALWAYS a draft, whatever the CMS
admin, public = cms.locate(page, entry_id)
```

Rules:

- `upsert()` creates a draft. There is no way to publish through it, by design.
- `cms.publish(page)` goes live. Call it **only** after the operator says so, for
  this page, in this session. Approval never carries forward.
- If `cms.kind` is `none`, `get_adapter()` raises. Report that and offer `files`,
  which writes the page to disk instead, rather than guessing a CMS.
- `python3 -c "import cms; print(cms.describe())"` tells the operator what is
  wired and whether that adapter has been verified live.
- Only `strapi` and `files` are verified against real instances. For the rest,
  build the page, run the audit, then tell the operator to expect a possible
  field-name mismatch on the first push.

<!-- CMS:END -->

# {{BRAND_NAME}} Product Page Builder (SERP + LLM aware)

Builds a {{BRAND_NAME}} product landing page (typically `/<keyword>-generator/`, `/<keyword>-builder/`, `/<keyword>-creator/`, or feature-page) end-to-end **with mandatory Phase 0 SERP + LLM ranking-factor recon before a single line of copy is written.** Every ranking factor from the top-10 competitor pages gets identified, then either matched or exceeded. Every top LLM-cited domain gets checked so we know what pattern we're competing against.

The skill produces a `pages` entry that **mirrors the /ai-form-generator/ design exactly**: 6 macro components + 6 raw-html micro components + 1 raw-html tail (13 slots total). Same rendering pipeline, same visual system, zero drift.

**Reference implementation:** `consent-form-generator` (page id 898, shipped 2026-08-03). When in doubt about a macro shape or a raw-html section, fetch that page's `components` array and copy the pattern.

---

## Rule 0 — Inputs (ask one at a time, echo back, confirm)

1. **Primary keyword** — the exact search phrase (US). Example: `consent form generator`, `photo release form maker`.
2. **Slug** — kebab-case URL path, no leading slash. Example: `consent-form-generator`.
3. **Page-type verb** — one of `generator`, `builder`, `creator`, `maker`. Sets the tone across the 13 components.
4. **Secondary keywords** — 4-6 phrases. Includes at least one AI variant (`ai <primary>`), one free variant (`free <primary>`), and one output-format variant (`<primary> pdf` or similar) when applicable.
5. **Regulated-industry framing** — one of:
   - `explicit-not-hipaa-with-routing` — name {{BRAND_NAME}} as not HIPAA-certified and route to Jotform Gold / Cognito Enterprise in the Clinicians / Healthcare persona (recommended for any consent / clinical / medical topic)
   - `compact-caveat` — one-sentence non-HIPAA note, no competitor routing
   - `not-applicable` — the topic is not health/consent-adjacent (event, contact, feedback, quiz, etc.) — skip HIPAA copy entirely
6. **Companion how-to blog (optional)** — the source tracker often has an adjacent blog row (`how to create a <keyword>`). Offer to ship both landing + blog as a pair. Default: no.

After all six, echo the block back and wait for explicit confirmation before Phase 0.

---

## Rule 1 — Phase 0 SERP + LLM recon (mandatory, BEFORE any content build)

Do NOT write copy or build components until this phase is complete and the ranking-factor synthesis is presented to the user.

### 1a — Google SERP top-10 (DataForSEO Live SERP)

Direct HTTP call — DataForSEO MCP tools may not be loaded. Load credentials from `.env`:

```python
import base64, ssl, json, urllib.request
ctx = ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
auth = base64.b64encode(f"{DFS_LOGIN}:{DFS_PASS}".encode()).decode()
payload = json.dumps([{
    "keyword": PRIMARY_KEYWORD,
    "location_name": "United States", "language_code": "en",
    "device": "desktop", "depth": 10,
}]).encode()
req = urllib.request.Request(
    "https://api.dataforseo.com/v3/serp/google/organic/live/advanced",
    data=payload, method="POST",
    headers={"Authorization": f"Basic {auth}", "Content-Type": "application/json"}
)
```

Extract from response:
- **Top 10 organic** — URL, domain, title, rank
- **SERP features** — AI Overview present? PAA questions? Video pack? Related searches? Discussions & Forums block?
- **Related searches** — often reveal the highest-intent long-tail variants (`<primary> free`, `<primary> pdf`, `<primary> for research`, etc.)

### 1b — Firecrawl the top 5-6 commercial competitors

Skip academic/IRB `.edu` results unless they represent a real audience {{BRAND_NAME}} wants. Extract per competitor:
- Word count, H1/H2/H3 tree
- Tools mentioned (which entities they name)
- Compare / benchmark: which pages ARE listicles vs product pages vs template galleries?
- Any structural pattern the top 3 share that {{BRAND_NAME}}'s default set doesn't have

### 1c — LLM citation probe (OpenAI gpt-4o-search-preview × 4 queries)

```python
payload = json.dumps({
    "model": "gpt-4o-search-preview",
    "messages": [{"role": "user", "content": QUERY}],
}).encode()
```

Run 4 query variants covering informational / free / format-specific / use-case intents. Parse `choices[0].message.annotations[].url_citation` for cited domains. Record:
- Does {{BRAND_NAME}} get cited (URL or in-answer name)?
- Which competitor domains get cited most?
- What criteria does the LLM emphasize (unlimited free, AI generation, PDF export, e-signature legality, HIPAA scope, etc.)?

### 1d — Ranking-factor synthesis (present to user, wait for approval)

Produce a compact report before touching components:

- Top 10 organic domain list with type (listicle / product page / template gallery / academic / brand)
- SERP features present + PAA questions verbatim
- Related-search intent list ranked by inferred volume
- Competitor structural pattern (word count, H2/H3 counts, distinctive sections)
- LLM citation status for {{BRAND_NAME}} on this keyword cluster (typically 0/4 on new keywords)
- Ranking factors the user's page will **take** from top-10 (design patterns + entity coverage + intent match)
- Ranking factors the user's page will **add on top** ({{BRAND_NAME}} differentiators: unlimited free, AI form gen from PDF, native e-signature, PDF export, GDPR + DPA §4.4, MCP for AI agents, etc.)
- Proposed component-by-component plan (13 slots — see Rule 2)
- Meta title + description proposals
- Regulated-industry framing decision (from Rule 0.5) applied to specific components

Wait for explicit approval before Phase 1.

---

## Rule 2 — Component structure (15 slots, LOCKED — updated 2026-08-09 for legal-form-management-software)

Every product page ships with **exactly 15 components in this order**. Slot type is fixed. Content varies by keyword.

| # | Slot | Component type | Purpose |
|---|---|---|---|
| 0 | **Custom animated hero** | `micro-components.raw-html` | Two-column hero: text left (badge / H1 with violet accent / description / CTAs / trust bullets), right = SINGLE panel with two states — State 1 prompt input with 2-line typing animation + Generate button, State 2 generated form preview with 6 fields drawing in. Both states occupy the same absolute-positioned space, crossfade on a 14s loop. Whole State-2 form card is a clickable link to signup. See "Hero pattern" below. |
| 1 | 3-ways | `micro-components.raw-html` | 3 cards: Template · Prompt AI · Upload PDF |
| 2 | Trust seals | `macro-components.trust-seals` | `showTrustBadges: False` (org logo band only, no 4 rating cards) |
| 3 | Quality control | `micro-components.raw-html` | 5-item bullet list of what makes the output reliable |
| 4 | How it works | `macro-components.simple-steps-create` | 3 numbered steps with title + description each |
| 5 | **See in action (video/GIF grid)** | `micro-components.raw-html` | 2×2 grid of 4 short GIFs captured from `{{APP_HOST}}` showing the product in motion. Frame-accurate trim to skip page-load blank frames. `<img>` tags for GIFs (not `<video>`) so no autoplay-blank issue. See "GIF grid pattern" below. |
| 6 | Who uses this | `micro-components.raw-html` | 5-8 personas grid (apply HIPAA framing per Rule 0.5) |
| 7 | API + MCP | `micro-components.raw-html` | 2 cards: MCP for AI agents + REST API v2 |
| 8 | Privacy | `micro-components.raw-html` | DPA §4.4 + GDPR/UK GDPR/CCPA + encryption |
| 9 | **Feature grid (with topic icons)** | `micro-components.raw-html` | 6 feature cards. Each card has a topic-specific 40×40 icon tile (violet-25 bg + violet-100 border + violet-600 Lucide icon) above the title. **NOT the `standout-feature-detail` macro** — that macro's schema doesn't accept icons. See "Icon feature grid pattern" below. |
| 10 | Testimonial wall | `macro-components.testimonial-wall` | G2 / Trustpilot / Product Hunt / Capterra live reviews (`showG2/Trustpilot/ProductHunt/Capterra: True`) |
| 11 | Mid-page CTA | `macro-components.cta` | Existing CTA record `ctaValue: 67` (default: "Get access to advanced AI, unlimited forms & more.") |
| 12 | FAQs | `macro-components.faqs` | 9 Q&A pairs, PAA-matched, structured `faqList` (not raw-html) |
| 13 | Steps videos (optional) | `micro-components.raw-html` | 3-column grid of 3 GIFs — one per simple-step above. Optional; skip if the page doesn't have a natural 3-step flow. |
| 14 | More {{BRAND_NAME}} tools | `micro-components.raw-html` | 4 sibling tools cross-sell grid |

**Non-negotiable:**
- Slots 2, 4, 10, 11, 12 use macro components — do not replace with raw-html
- All other slots use `micro-components.raw-html` with **`markup` field** (never `body` — writing to `body` results in silently empty components; this bug shipped on the initial cognito-forms-alternative update on 2026-08-03 and was caught in prod)
- **Every raw-html component uses `useContainer: false`** so it renders edge-to-edge like AFG's macros. The site's default container wrapper (`useContainer: true`) constrains sections to a narrow column and breaks the full-width feel of the AFG-hybrid look. Each raw-html component MUST manage its own inner max-width via a `.__container` or `.__inner` element (`max-width: 1180px; margin: 0 auto; padding: 0 24px`). Verified fix applied on legal-form-management-software (page 903) + form-management-software (page 904) on 2026-08-09.
- Class prefix on raw-html: unique 3-char kebab code per page (e.g. `lfm` for legal-form-management-software, `fmg` for form-management-software)

### Hero pattern (slot 0) — reference: legal-form-management-software (page 903)

Full raw-html hero with the two-state animation. Non-negotiables:
- 2-column grid on desktop (`grid-template-columns: 1fr 1fr`), stacks to 1-column below 900px
- Top padding **120px on desktop** (was 72px — this gap is required to clear the sticky nav)
- Left column: violet-100 badge pill, H1 with `<span class="__accent">` violet on the second half, description paragraph, 2 CTAs (Start free primary + See pricing ghost), trust bullets row
- Right column: single `.__panel` with two absolutely-positioned `.__state` children. State 1 is the prompt view (icon + label header, prompt box with 2-line typing, generate button below). State 2 is the generated form preview.
- **Animation loop = 14s.** Timing: 0-27% typing, 27-30% button pulse, 30-36% spinner + State 1 fadeout, 36-40% State 2 fadein, 40-92% form fields draw in one by one, 92-100% fade back to State 1
- **Typing**: two `.__line` elements each with `white-space: nowrap`. Each `.__line-fill` has its own `max-width` keyframe animation. Line 1 reveals 0-8.5%, then ~1.5s natural pause, then Line 2 reveals 12-21%. Timing function `cubic-bezier(0.22, 1, 0.36, 1)` — strong ease-out feels like natural typing that decelerates at word boundaries. Two cursors, one per line, with softer blink (opacity dips to 0.15 not 0)
- **Clickable**: State-2 form is wrapped in a single `<a href="{{SIGNUP_URL}}">` so the entire card acts as a signup link during the "form visible" phase. Use `pointer-events: none` in the keyframes for the invisible state so users can't click through
- Legal-page canonical markup: see `output/alt-pages/legal-form-management-software/` OR pull page 903's slot-0 markup fresh (`GET /api/pages/903?populate[components][populate]=*&publicationState=preview`)

### Icon feature grid pattern (slot 9)

Not a macro. Full raw-html. Non-negotiables:
- 6 feature cards, 3-col desktop / 2-col tablet / 1-col mobile
- Each card: **40×40 icon tile at top-left** (border-radius 8px, background `#f7f3ff` violet-25, border `1px solid #e4d7ff` violet-100, icon color `#6941c6` violet-600)
- Icons: **Lucide-style, 20×20 viewBox, stroke-width 1.8, currentColor** — match the `3-ways` and `who-uses` icon style already used elsewhere on the page. Pick icons that DIRECTLY represent the feature meaning, not decorative filler. Sample mapping used on legal-form-management-software:

  | Card | Lucide-style icon |
  |---|---|
  | e-signature / signed forms | Pen writing a signature curve |
  | PDF export / document download | Document with down-arrow |
  | Conditional logic / branching | 3 connected circles (branch pattern) |
  | Custom domain / branding | Globe with meridian |
  | Team seats / roles | 2 users |
  | Integrations | Puzzle pieces / grid connectors |
  | AI form generator | Sparkles |
  | Native payments | Credit card |
  | Offline / PWA | Download cloud |
  | Multi-language | Globe with language char |
  | Privacy / GDPR | Shield check |
  | REST API / MCP | Code brackets `< >` |

- Card body: title (H3, 18px, weight 600) + description (15px, line-height 1.55, color `#475467`). No CTA link inside cards.
- Hover: border → `#9777e0` (violet), shadow lift, `translateY(-2px)`

### GIF grid pattern (slot 5)

Not a macro. Full raw-html. Non-negotiables:
- 2×2 grid on desktop, 1-col below 760px
- 4 clips ONLY. More clips dilute impact.
- **Format: `<img>` tags pointing at animated GIF files uploaded to your CMS media library** — NOT `<video>` tags. GIFs render instantly with no blank first frame, no autoplay-block issues, no seek-required behavior. Autoplay is guaranteed on every browser.
- **File size target**: 300-500 KB per GIF, ~1.5MB total for the section
- **Capture pipeline** (Playwright + ffmpeg):
  1. Playwright headed, log in with `BRAND_APP_EMAIL` + `BRAND_APP_PASSWORD` from `.env`, save `storage_state.json`
  2. Pre-extract a real form ID from the /forms dashboard (use JS to find `data-form-id`, else fall back to creating a Start-From-Scratch form)
  3. Record each interaction as a WebM (Playwright `record_video_dir` context option, viewport 1280×720)
  4. Convert WebM → GIF with ffmpeg using **frame-accurate trim** via the `trim=start=X:end=Y,setpts=PTS-STARTPTS` filter — NOT `-ss` before `-i` which does keyframe-based seek and often produces blank first frames
  5. GIF settings: `fps=12, scale=800:-1:flags=lanczos`, 2-pass palette generation with `stats_mode=diff` + `paletteuse=dither=bayer:bayer_scale=5`
  6. Upload each GIF via `POST /api/upload` multipart with `Content-Type: image/gif`
- **Card design**: same shape as other card grids — white bg, 1px grey border, 16px border-radius, subtle shadow, violet border on hover. Each card has a 16:10 aspect-ratio `.__frame` at top containing the GIF, then title + body below.
- **Playwright locator strategy**: coordinate clicks work better than text-based locators for Vue/Element-UI dialogs. Inspect the DOM first via a JS eval that dumps `{tag, classes, rect}` for candidate elements, then use `page.mouse.click(x, y)` at rect centers for Vue-tile clicks. Use CSS selectors like `button.template-card` or `button.fmst-btn` for real buttons.
- **Fallback**: if a specific interaction won't fire reliably from Playwright, ship the clip anyway showing the static screen — real product UI beats a fake mock. Note honestly in the report which clips are interactive vs static.

Reference implementation: legal-form-management-software (page 903) shipped 2026-08-09 with 4 GIFs (media ids 4780-4783) covering "Six ways to create a form", "Form builder with live design controls", "Ready-made template library", "Form-to-PDF export".

---

## Rule 3 — Design system (byte-for-byte from ai-form-generator)

The canonical source is `/ai-form-generator/` (reference entry). When building a new product page, fetch AFG's 13 components and:

1. **Macros**: mirror the field structure exactly. Only vary `title` text parts, `description`, `itemList` items, `suggestions`, `footerItems`, `faqList`. Leave `showG2/Trustpilot/ProductHunt/Capterra: True` and `ctaValue: 67` and `showTrustBadges` as-is.
2. **Raw-html**: copy from `HTML component/consent-form-generator/` (reference build), swap the 3-char prefix (`csg` → new prefix), rewrite the copy for the new keyword. **Do NOT copy from `HTML component/contact-form-generator/`** — that library is compact and misses some AFG design details. Consent-form-generator is the canonical raw-html reference going forward.

Class prefix substitution: pick a unique 3-char kebab code, do a global `csg-` → `<new-prefix>-` replace across all 6 raw-html files.

The 8 canonical topic SVG icons + Lucide shape rules from `~/.claude/skills/_shared/alt_page_design_system.py` also apply here — if the `who-uses` block has icons, they get topic-swapped by `apply_alt_page_rules()` automatically.

---

## Rule 4 — Voice + accuracy rules (mandatory)

Same as every {{BRAND_NAME}} content skill:

- **No em-dashes anywhere** in body copy. Preflight blocks publish if any leak in. Applies to macro content too, not just raw-html.
- **No hardcoded dates.** Every "As of X" resolves to `today()` at build time.
- **No placeholders.** Strip `TODO`, `FIXME`, `INSERT_`, `REPLACE_`, `PLACEHOLDER` before commit.
- **No external URLs in FAQ answers.** Internal `/features/...` links are fine.
- **No repeat internal links** in body: each internal URL linked at most once outside the more-tools grid.
- **Every {{BRAND_NAME}} claim** traces to `memory/brand-facts.md`. Never invent features. {{BRAND_NAME}} is NOT HIPAA-certified, NOT SOC 2, NOT ISO 27001, and NOT "on the HIPAA roadmap" — banned phrasing.
- **User-count is 56,000+ teams** unless a newer confirmed number lives in the current conversation.
- **Every competitor claim** traces to `competitor-research.md`. Correct brand casing (Jotform / Google Forms / Typeform / Microsoft Forms / Zoho Forms / Fillout / forms.app / KoboToolbox).
- **HIPAA framing (from Rule 0.5):**
  - `explicit-not-hipaa-with-routing` → the Clinicians / Healthcare persona card names Jotform Gold and Cognito Forms Enterprise as HIPAA-eligible alternatives, positions {{BRAND_NAME}} for non-PHI use only (marketing opt-in, patient education, event RSVPs).
  - `compact-caveat` → one-sentence note only ("{{BRAND_NAME}} is not HIPAA-certified. Use for non-PHI intake."), no competitor routing.
  - `not-applicable` → no HIPAA mention on the page.

---

## Rule 5 — Google Doc draft (approval gate, mandatory before CMS push)

After the 13 components are built locally, produce a Google Doc for content approval before any CMS write.

**Doc structure is LOCKED by the shared standard at [`~/.claude/skills/_shared/doc_content_standard.md`](../_shared/doc_content_standard.md).**

1. Metadata table first (Title, Description, Meta title, Meta description, Primary keyword, Primary keyword volume (US), Secondary keywords, Slug, Recommended URL, CMS link). No other rows.
2. Body: verbatim copy from the 13 components, in reading order, with explicit `[H1] / [H2] / [H3]` heading markers and `[Image: ...]` placement markers wherever an on-page image will render.
3. **No commentary, no rationale, no `**Why:**` lines, no `[REUSE: ...]` or `[NEW TEMPLATE]` annotations, no cover URL or media ids, no schema JSON, no preflight-status footer.**
4. CTAs render as plain lines: `Button: <label>` + `Link: <url>`.
5. Use `~/.claude/skills/gdoc/build_gdoc.py` with `GDOC_AUTH_DIR={{GOOGLE_AUTH_DIR}}` ({{GOOGLE_ACCOUNT}} OAuth, not {{GOOGLE_ACCOUNT}} — {{BRAND_NAME}} project rule).
6. Doc lands in the "{{BRAND_NAME}} SEO Audits" Drive folder.
7. When updating an existing page, regenerate in place via `--doc-id <existing_id>`.
8. Share URL with the user. Wait for explicit content approval before touching the CMS.

Content approval unlocks Rule 6.

---

## Rule 6 — CMS push (POST /api/pages, draft state)

After content approval:

1. **Preflight all 6 raw-html components** via `run_preflight()` from `~/.claude/skills/_shared/preflight.py`. This runs `strip_trailing_periods`, `fix_button_hover_color`, `fix_markdown_leakage`. Any change gets persisted to disk before push. Mandatory.

2. **Audit-scan all 6 raw-html components** via `python3 ~/.claude/skills/_shared/audit_scan.py <file>`. Exit 1 = do not push, fix and re-run. Catches {{BRAND_NAME}} HIPAA overclaim, "on roadmap" phrasing, em-dashes, hardcoded dates.

3. **Build the payload** in this exact shape:

```python
payload = {
    "data": {
        "slug": SLUG,
        "publishedAt": None,  # HARD RULE: never publish on POST
        "meta": {
            "title": META_TITLE,               # 30-60 chars, ends with "| {{BRAND_NAME}}"
            "description": META_DESCRIPTION,   # 120-160 chars
            "type": "website",
            "url": LIVE_URL,
            "keywords": [{"text": kw} for kw in KEYWORDS],
            "link": [{"hid": "canonical", "rel": "canonical", "href": LIVE_URL}],
            "jsonld": JSONLD_GRAPH,  # see Rule 7
        },
        "components": [
            {"__component": "micro-components.raw-html", "markup": CUSTOM_HERO, "useContainer": False},  # slot 0 — custom animated hero
            {"__component": "micro-components.raw-html", "markup": ..., "useContainer": True},  # slot 1  3-ways
            {"__component": "macro-components.trust-seals", "showTrustBadges": False},  # slot 2 (org logos only)
            {"__component": "micro-components.raw-html", "markup": ..., "useContainer": True},  # slot 3  quality
            SIMPLE_STEPS,                                                       # slot 4 macro (simple-steps-create)
            {"__component": "micro-components.raw-html", "markup": GIF_GRID, "useContainer": True},  # slot 5 — See in action GIF grid
            {"__component": "micro-components.raw-html", "markup": ..., "useContainer": True},  # slot 6  who-uses
            {"__component": "micro-components.raw-html", "markup": ..., "useContainer": True},  # slot 7  api-mcp
            {"__component": "micro-components.raw-html", "markup": ..., "useContainer": True},  # slot 8  privacy
            {"__component": "micro-components.raw-html", "markup": ICON_FEATURE_GRID, "useContainer": True},  # slot 9 — Icon feature grid (NOT standout-feature macro)
            TESTIMONIAL_WALL,                                                   # slot 10 macro
            {"__component": "macro-components.cta", "ctaValue": 67},            # slot 11 macro
            FAQS,                                                               # slot 12 macro (faqs)
            {"__component": "micro-components.raw-html", "markup": ..., "useContainer": True},  # slot 13 — steps videos (optional)
            {"__component": "micro-components.raw-html", "markup": ..., "useContainer": True},  # slot 14 — more {{BRAND_NAME}} tools
        ],
    }
}
```

4. **POST** to `{{CMS_BASE_URL}}/api/pages`. Use the CMS token from your environment.

5. **Verify** via `GET /api/pages/<id>?populate[components][populate]=*&populate[meta][populate]=*&publicationState=preview`. Confirm 14-15 components with correct types, `publishedAt: null`, canonical link set, JSON-LD @graph has 5 entities.

6. Report CMS admin URL to the user. Wait for explicit publish approval.

---

## Rule 7 — 5-entity JSON-LD @graph (mandatory)

Every product page's `meta.jsonld['@graph']` must include:

1. **Organization** — {{BRAND_NAME}} (referenced by `@id: {{SITE_URL}}/#organization`)
2. **WebPage** — url, name, description, `datePublished` = today, `dateModified` = today, inLanguage: `en-US`, isPartOf, breadcrumb
3. **BreadcrumbList** — Home → Page name
4. **FAQPage** — 9 verbatim Q&A pairs from the `faqs` macro's `faqList`. Structured as `{"@type": "Question", "name": q, "acceptedAnswer": {"@type": "Answer", "text": a}}`.
5. **SoftwareApplication** — {{BRAND_NAME}} product entity: name, url, `operatingSystem: "Web, iOS, Android"`, `applicationCategory: "BusinessApplication"`, `offers` array (Free at $0, Personal at $13), `aggregateRating` (4.7/5, 11 reviews on G2).

Dates resolve to today at build time. Never a hardcoded string.

---

## Rule 8 — SEO preflight (blocks publish)

Any FAIL blocks the publish step. Auto-run these checks before flipping `publishedAt`:

- Meta title 30-60 chars, ends with `| {{BRAND_NAME}}`
- Meta description 120-160 chars
- Slug matches URL path
- Exactly 1 `<h1>` across all components (from the ai-hero macro)
- No em-dashes anywhere in components or macro strings
- No banned placeholders (`TODO`, `FIXME`, `INSERT_`, `REPLACE_`, `PLACEHOLDER`)
- Canonical link set: `meta.link[0].rel == 'canonical'`
- `meta.jsonld['@graph']` has exactly 5 entities (Rule 7)
- FAQPage entity has ≥ 5 Q&A pairs, target 9
- `publishedAt: null` (still a draft)
- Component count = 13 in the exact order from Rule 2
- Every raw-html component uses `markup` field (not `body`) + `useContainer: true`
- `run_preflight()` + `audit_scan.py` exit 0

---

## Rule 9 — Publish (only on explicit approval)

Per `feedback_never_publish_without_approval.md`: never flip `publishedAt` without explicit user approval on THIS specific page. Prior approvals do not carry forward.

When approved:

1. Set `publishedAt` to `datetime.now(timezone.utc).replace(microsecond=0).isoformat() + 'Z'`.
2. PUT to `/api/pages/<id>`.
3. Poll `{{SITE_URL}}/<slug>/` for 200. Nuxt static rebuild takes ~5-15 minutes; 404 in that window is expected.
4. Log to Cluster Master Tracker `Published Content Tracker` tab if the source row exists.

---

## Rule 10 — Reference implementation

**Canonical reference for the 15-slot pattern (updated 2026-08-09):** `legal-form-management-software` (reference entry). Custom animated hero, GIF grid section, icon feature grid.

**Prior reference for the base 13-slot AFG-hybrid pattern:** `consent-form-generator` (reference entry).

When in doubt about:
- Macro field shapes → GET `/api/pages/1?populate[components][populate]=*&publicationState=preview` (ai-form-generator)
- Raw-html section design → read `HTML component/consent-form-generator/*.html`
- Component ordering → GET `/api/pages/898?populate[components][populate]=*&publicationState=preview` (consent-form-generator)
- Doc format → open the consent-form-generator doc (in "{{BRAND_NAME}} SEO Audits" Drive folder)

**Do NOT reference:**
- `contact-form-generator` — this predates the AFG-hybrid architecture; its 10 raw-html structure is deprecated for new builds
- `ai-form-generator` local `HTML component/ai-form-generator/*.html` files as a raw-html template — those are the OLD (pre-macro) raw-html versions. AFG's LIVE page uses the macros defined in the CMS, not those local files.

---

## Rule 11 — Things to never do

- Never publish without explicit approval on THIS page.
- Never invent {{BRAND_NAME}} features. HIPAA / SOC 2 / ISO 27001 are all NO. Never claim "on the roadmap" for any of these.
- Never use em-dashes.
- Never leave trailing periods on headings, badges, or CTAs.
- Never hardcode dates in generated components — always `today()` at build time.
- Never skip the Phase 0 SERP+LLM recon. It's the whole point of this skill vs the older `/landing-page`.
- Never skip the Google Doc approval gate.
- Never write to the `body` field on `micro-components.raw-html` — use `markup`. Writing to `body` results in silently empty components (**Strapi only:** it discards unknown fields). This bug shipped on the initial cognito-forms-alternative update 2026-08-03 and was caught in prod.
- Never reuse a class prefix across pages. Each page gets a unique 3-char kebab code.
- Never let the `type` field on `macro-components.ai-hero` be anything except `Form`, `Score Quiz`, or `Outcome Quiz` (the CMS enum). "Consent form" or similar strings will 400 the POST.


## No self-referential media captions (HARD RULE, added 2026-08-09)

Never add authenticity remarks under or near screenshots, GIFs, or videos: "Real {{BRAND_NAME}} UI, captured in-app", "actual product screenshot", "real UI, not a mockup", "captured from the live app", or any similar meta-commentary about the asset being real. These read as AI-generated filler and were ordered removed site-wide. The asset speaks for itself. If a media element genuinely needs a caption, the caption states only WHAT the user is looking at (e.g. "Form builder, Design tab"), never that it is real, actual, or captured. This applies to every page type: product, landing, alternative, blog, listicle, review, template.


## Inspiration pages are design-only, never fact sources (GLOBAL HARD RULE, added 2026-08-10)

Root cause: the Aug 2026 factual audit found 4 alternative pages shipped with the inspiration page's facts (Wufoo's pricing tiers, ownership, founding year) left in place for different competitors. This must never recur.

- An internal inspiration/reference page may be reused ONLY for: page structure, section structure, layout, design patterns, visual treatment, formatting, component patterns, UX approach.
- NEVER carry over content, claims, facts, statistics, pricing, plan names, limits, ownership, founding dates, quotes, or any information from the inspiration page into a new page.
- Research comes FIRST on every new page: (1) research the subject independently, (2) identify current correct information, (3) verify every factual claim against first-party/authoritative sources (official pricing page, docs, company newsroom), (4) write original content from that research.
- Never assume something is correct because it appears on an existing {{BRAND_NAME}} page. Never fill gaps with guesses. If a claim cannot be verified, flag it explicitly instead of presenting it as fact.
- Cloned template blocks that carry competitor-specific facts (pricing tables, "why teams leave" cards, honest-comparison cards, free-plan tables) must be rebuilt from fresh research for every new subject, not find-and-replaced.

