Communitygithub.com

kyjongSNSoft/landing-page-skill

Generates a deploy-ready promotional landing page (static HTML + CSS + JS) from a Figma design link or file, using rem layout, exported cutout images, and a fixed analytics/postMessage runtime.

What is landing-page-skill?

This skill automatically converts a Figma design file into a fully functional, deployable static landing page for marketing campaigns (e.g., event landing pages). It produces an index.html, CSS file, JavaScript files (tool.js, pm_main.js, main.js), and optimized images in WebP format. The skill is designed for developers and AI agents to streamline the process from design to deployment, supporting both H5 (mobile) and PC (desktop) responsive layouts using a unified 576px breakpoint and rem scaling. It integrates with Figma via MCP or REST APIs, automatically recognizing frames, extracting assets with a four-tier fallback strategy, and generating BEM-compliant code with interactive components like countdown timers and carousels. The workflow includes a user confirmation gate, automated screenshot comparison for up to three iteration rounds, and a final delivery report with checklist validation.

Works with~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/kyjongSNSoft/landing-page-skill/tree/main/skill

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Preview

From the skill README

landing-page-skill preview

Documentation

Figma → Landing Page

Generate a deployable static event landing page from a Figma design: index.html + css/main.css + js/{tool,pm_main,main}.js + images/*.webp.

Invocation: /figma-landing-page <Figma link> [event name] [output directory]

Fixed contract (must not deviate under any circumstances):

  • Template placeholders are exactly 3: __PAGE_TITLE__, __FUNCTION_NAME__ (both in main.js, title also appears in index.html), __PC_BASE__ (in tool.js; 375 = H5 only; 1920 = H5+PC dual frames).
  • CTA elements must additionally carry the .join-btn class (fixed JS selector contract), coexisting with BEM classes.
  • Breakpoint 576px unified in three places: JS rem branch, CSS @media (min-width: 576px), platform determination.
  • px normalization: H5 px × 375 ÷ H5 frame width ÷ 100 = rem; PC px × 1920 ÷ PC frame width ÷ 100 = rem.
  • Template JS trio (tool.js rem adaptation / pm_main.js postMessage bridge / main.js event business layer, loaded in that order) – fixed sections must not be changed; page interactions are only inserted under // === Page-specific interactions === in main.js (inside the IIFE, can directly use $p/pm/track/Date.prototype.format).
  • Detailed rules (cutout boundaries, class names, interaction inference table) are in references/patterns.md, delivery standards in references/checklist.md—read both files before generation.

Step 1 — Parse Input & Environment Check

  1. Extract file key and node-id from the link. Convert hyphens in node-id to colons (123-456 → 123:456) before calling any Figma API; if the API returns 404/empty results, first check this step.
  2. Three environment checks (fix first before proceeding if not passing):
    • Figma MCP connected (if not, guide user to authenticate; authentication interactions do not count as the single interruption in the confirmation gate). First call whoami (exempt from quota) to confirm seat: View/Collab or Starter seats only allow 6 MCP calls/month—when budget is tight, skip MCP and go directly to Step 2/4 REST route (PAT stored in ~/.figma_token, if missing ask user to generate one in Figma Settings → Security, have the user write it to their terminal file to avoid chat history).
    • which cwebp—if missing, tell user brew install webp; if user refuses, fall back to png output for the entire page and note it in the delivery report.
    • node -e "require('playwright-core')"—if missing, npm i playwright-core in the scratchpad directory; reuse the local Chrome browser (channel: 'chrome'), do not download browser binaries.
  3. Output directory defaults to ./output/<event-name-slug>/ (all skill output is placed under output/; if event name is missing, infer from Figma page/frame name and verify in the confirmation gate). If the directory exists and is not empty, do not interrupt now—overwrite confirmation belongs to the confirmation gate.

Step 2 — Survey the Design

  • Use sparse metadata tools (get_metadata class) to read the node tree; do not fetch the full design context of the root frame in one go.
  • When MCP is unavailable/budget exhausted: REST GET /v1/files/<key>/nodes?ids=<node> (without depth = full subtree JSON, containing all text, fonts, fills gradient original values, precise bbox—more complete information than get_metadata). Cache locally and parse repeatedly; one call covers the whole process; CSS color values/gradients/text are taken directly from this JSON, no need for get_code.
  • Frame identification: width ≤ 480 → H5 frame; ≥ 1200 → PC frame; frames that cannot be determined are listed in the confirmation gate. Only H5 frame → __PC_BASE__ = 375; H5+PC dual frames → __PC_BASE__ = 1920.
  • Output asset inventory: section list (hero, banner, steps, tnc…); each node is marked as cutout image / real text / interactive component (determination rules in references/patterns.md).
  • Mark all CTAs (Join Now / Register / Sign Up type buttons)—they must carry .join-btn.

Step 3 — Confirmation Gate (the only interruption before generation in the normal flow)

List all of the following in one message and get user confirmation at once; then no interruptions until delivery:

  • Event name, __PAGE_TITLE__ value, __FUNCTION_NAME__ value (e.g., WinsdayBillionDropLandingPage)
  • Output directory (confirm overwrite if directory is not empty)
  • Cutout image list (node → images/<name>.webp)
  • CTA node list
  • Interaction list: each item includes inference basis and required parameters (countdown→deadline/expiry behavior; carousel→autoplay/interval; popup→trigger timing; see table in references/patterns.md). Parameters that cannot be inferred from the design should be filled in by the user here; nodes that cannot be classified are marked "static".
  • Items with questionable frame identification

Step 4 — Export Cutout Images (four-level fallback chain, degrade based on availability)

  1. MCP download_assets (when seat budget is sufficient): ≤ 20 nodes per batch, scale 2x, format png; download temporary URLs with curl.
  2. REST render GET /v1/images/<key>?ids=…&format=png&scale=2: batch by comma but ≤ 4 large nodes per batch (exceeding causes "Render timeout"); 429 = seat render budget exhausted (View seat triggers after ~12 images, window hourly), switch to next level, do not wait indefinitely.
  3. REST original image endpoint GET /v1/files/<key>/images: returns the full file's imageRef → original image URL mapping, uses file budget, almost not subject to render rate limiting. Suitable for all image fill nodes (photos/stickers/icons—match node JSON's fills[].imageRef to get original resolution, often far exceeding 2x, crop/trim alpha based on fill's scaleMode then scale). Not suitable for vector composite nodes (panels/ribbons).
  4. User manual export (zero quota): ask the user to select and export the node in Figma. Prefer SVG format—vector parts infinitely clear, bitmaps embedded at original resolution as base64 (can regex extract <image> and decode, best source for transparent elements). SVG→2x bitmap rasterization pipeline: copy SVG to a temp directory (kebab-safe filename, original names often contain &/spaces), write an HTML wrapper <img src="./x.svg"> in the same directory (opening SVG directly via file:// or cross-directory file references will be rejected by Chrome), screenshot with playwright deviceScaleFactor:2 + omitBackground:true to get transparent PNG.
  • General: convert to webp cwebp -q 80; name semantically in kebab-case, no hash; pure gradient/solid color nodes read directly from JSON and write CSS, no image output.
  • Note: Figma-exported SVG viewBox may contain drop shadows/bleed (larger than the node box); when the container positions by the node box, let the image overflow proportionally, do not squash.
  • Single image export failure: retry 2 times → degrade to next level chain; skip only if all levels fail and note in delivery report.

Step 5 — Generate Code

  • Start from assets/template/: the content of base.css serves as the initial skeleton of css/main.css (the output has only a single main.css, no separate base.css output).
  • index.html: replace <title>; replace font links with the design's fonts (if Google Fonts cannot match, choose the closest and note in the report); generate semantic structure inside #app by section, BEM class names (.hero__bg, .banner__headline), CTA elements additionally carry join-btn.
  • css/main.css: values taken from Figma precise dimensions/color values/gradients (get_code plain HTML+CSS output can assist in obtaining values), all px-normalized first then written in rem; background background-image, content images <img>. Dual-frame pages: H5 rules first, PC overrides all in @media (min-width: 576px).
  • js/: copy the three template files (tool.js / pm_main.js / main.js), replace the 3 placeholders (__PC_BASE__ in tool.js, the other two in main.js); insert confirmed interaction code below the marker in main.js.
  • Elements that bleed to the frame edge and are cropped (bbox exceeds frame width) should have the container set to overflow: hidden to reproduce frame cropping, otherwise the page will overflow horizontally.
  • Self-check: node --check js/*.js all three files pass.

Step 6 — Screenshot Comparison Iteration (by section blocks, max 3 rounds)

  • Start a local server in the output directory: python3 -m http.server <port>.
  • Write the following screenshot script to scratchpad and use it to screenshot by section blocks (clip coordinates from DOM: use page.evaluate to get getBoundingClientRect for each section element):
// shot.mjs <url> <out.png> <width> <dsf> [clipY clipH]
import { chromium } from 'playwright-core';
const [url, out, width, dsf, clipY, clipH] = process.argv.slice(2);
const browser = await chromium.launch({ channel: 'chrome' });
const page = await browser.newPage({
  viewport: { width: +width, height: 800 },
  deviceScaleFactor: +dsf,
});
await page.goto(url, { waitUntil: 'networkidle' });
await page.screenshot(clipY === undefined
  ? { path: out, fullPage: true }
  : { path: out, clip: { x: 0, y: +clipY, width: +width, height: +clipH } });
await browser.close();
  • H5 comparison: local width 375, dsf 2; Figma side uses download_assets to export 2x png comparison images by section node (comparison images go to scratchpad, not images/).
  • Compare side-by-side section by section, correct differences; max 3 rounds, remaining differences are noted in the delivery report.
  • After convergence, do a quick full-page regression with width 414 (dsf 2) to confirm no misalignment/overflow (checklist requires 375/414 two profiles).
  • Full-frame screenshots are only for delivery report visuals, never as a basis for difference determination (long pages will be downsampled to illegible font sizes/color differences).
  • Dual-frame pages: additionally repeat the above section-by-section comparison for PC layout with width 1920, dsf 1 (against 1x export of PC frame); and a quick full-page regression with width 1280 (checklist dual-frame profiles require 1280/1920).
  • Check incidentally: browser console has no errors (page.on('console') or DevTools).

Step 7 — Delivery

The delivery report includes:

  1. Output directory tree
  2. references/checklist.md checked item by item
  3. Remaining known differences (not eliminated within 3 rounds)
  4. Section comparison screenshot paths
  5. Fallbacks used (png instead of webp, approximate fonts, skipped cutouts)

Error Handling Quick Reference

ScenarioHandling
Figma MCP not connected/authentication expiredGuide user to authenticate, then continue from the interrupted step (does not count as confirmation gate interruption)
node-id API returns 404/emptyCheck hyphen→colon conversion
Cutout export fails/rate limited 429Retry 2 times → degrade to next level chain (see Step 4) → if still fails, skip and note in report
cwebp missing and user refuses to installFall back to images/*.png + note in report
Frame structure cannot be identifiedList in confirmation gate and ask user (need specific node-id)
Output directory not emptyConfirm overwrite together in the confirmation gate

Related Skills