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-btnclass (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; PCpx × 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 inreferences/checklist.md—read both files before generation.
Step 1 — Parse Input & Environment Check
- 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. - 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 userbrew 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-corein the scratchpad directory; reuse the local Chrome browser (channel: 'chrome'), do not download browser binaries.
- Figma MCP connected (if not, guide user to authenticate; authentication interactions do not count as the single interruption in the confirmation gate). First call
- Output directory defaults to
./output/<event-name-slug>/(all skill output is placed underoutput/; 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)
- MCP
download_assets(when seat budget is sufficient): ≤ 20 nodes per batch, scale 2x, format png; download temporary URLs with curl. - 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. - 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'sfills[].imageRefto 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). - 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 playwrightdeviceScaleFactor:2+omitBackground:trueto 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 ofbase.cssserves as the initial skeleton ofcss/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#appby section, BEM class names (.hero__bg,.banner__headline), CTA elements additionally carryjoin-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; backgroundbackground-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: hiddento reproduce frame cropping, otherwise the page will overflow horizontally. - Self-check:
node --check js/*.jsall 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.evaluateto getgetBoundingClientRectfor 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_assetsto export 2x png comparison images by section node (comparison images go to scratchpad, notimages/). - 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:
- Output directory tree
references/checklist.mdchecked item by item- Remaining known differences (not eliminated within 3 rounds)
- Section comparison screenshot paths
- Fallbacks used (png instead of webp, approximate fonts, skipped cutouts)
Error Handling Quick Reference
| Scenario | Handling |
|---|---|
| Figma MCP not connected/authentication expired | Guide user to authenticate, then continue from the interrupted step (does not count as confirmation gate interruption) |
| node-id API returns 404/empty | Check hyphen→colon conversion |
| Cutout export fails/rate limited 429 | Retry 2 times → degrade to next level chain (see Step 4) → if still fails, skip and note in report |
| cwebp missing and user refuses to install | Fall back to images/*.png + note in report |
| Frame structure cannot be identified | List in confirmation gate and ask user (need specific node-id) |
| Output directory not empty | Confirm overwrite together in the confirmation gate |