agentsclimarketplace

Capture site

Skill cozats/figma-site-capture/skills/capture-site

Fully capture a live website into Figma as editable design layers. Use this skill when the user asks to "capture site to figma", "import site to figma", "clone site to figma", "recreate website in figma", "copy production site to figma", or provides a URL and wants it in Figma. Requires Figma Remote MCP and Playwright MCP to be connected.From its SKILL.md

Install
npx -y skills add cozats/figma-site-capture --skill capture-site

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

8.0 KB, ~1.8k tokens by cl100k_base, as published. Nobody here has run it

You are executing a full-site Figma capture. Your goal is to import every page, every breakpoint, and key UI states of a live website into a structured Figma file — ready to be the foundation of a design system.

Step 0: Gather inputs

Ask the user (in a single message):

  1. "What is the URL of the site you want to capture?" (if not already provided in $ARGUMENTS)
  2. "Add to an existing Figma file, or create a new one?"
    • If existing: ask for the Figma URL → extract fileKey from it
    • If new: auto-generate a file name from the domain (e.g. costas.design — Full Capture)

Then proceed autonomously without further interruptions unless blocked.


Step 1: Preflight checks

Verify both MCPs are available before doing anything:

  • Call mcp__figma-remote__generate_figma_design without parameters to confirm it's reachable
  • Confirm Playwright tools are listed

If either is missing, stop and tell the user which MCP needs to be connected, with setup instructions.


Step 2: Site discovery

Use this Playwright script to crawl the site and build a complete page list:

async (page) => {
  await page.goto(TARGET_URL, { waitUntil: 'networkidle' });

  // Try sitemap first
  let sitemapUrls = [];
  try {
    const sitemapRes = await page.context().request.get(new URL('/sitemap.xml', TARGET_URL).href);
    if (sitemapRes.ok()) {
      const xml = await sitemapRes.text();
      sitemapUrls = [...xml.matchAll(/<loc>(.*?)<\/loc>/g)].map(m => m[1]);
    }
  } catch (e) {}

  // Extract nav links from the page
  const navLinks = await page.evaluate((base) => {
    const domain = new URL(base).hostname;
    return [...document.querySelectorAll('a[href]')]
      .map(a => { try { return new URL(a.href).href; } catch { return null; } })
      .filter(href =>
        href &&
        new URL(href).hostname === domain &&
        !href.match(/\.(pdf|zip|png|jpg|svg|webp|mp4|mp3)(\?|$)/i) &&
        !href.includes('#') &&
        !href.match(/\?(utm_|ref=)/i)
      );
  }, TARGET_URL);

  // Merge and deduplicate
  const all = [...new Set([...sitemapUrls, ...navLinks])];

  return all;
}

Deduplicate, filter to same-domain URLs only, strip tracking params.

Present the page list to the user:

"Found N pages: [list]. Starting capture now."

If more than 20 pages, ask the user if they want to scope it (e.g. top-level only, or specific sections).


Step 3: Define the capture matrix

For each page, capture:

Breakpoints:

NameWidth
Mobile375px
Tablet768px
Desktop1440px

UI states (only where interactive elements exist — detect via Playwright):

StateTrigger
DefaultPage load
Nav openClick hamburger or hover primary nav
Modal openClick first CTA that opens an overlay
Form focusedClick first form input
Accordion openClick first accordion item (if present)
Tab 2 activeClick second tab (if tabs present)

Frame naming convention:

  • Home / Desktop
  • Home / Mobile
  • About / Tablet
  • Contact / Desktop / Form Focused
  • Work — Project Name / Desktop

Step 4: Pre-capture Playwright script

Run this before every capture to ensure full rendering:

async (page, targetUrl, viewportWidth) => {
  // Strip CSP so capture script can inject
  await page.route('**/*', async (route) => {
    const response = await route.fetch();
    const headers = { ...response.headers() };
    delete headers['content-security-policy'];
    delete headers['content-security-policy-report-only'];
    await route.fulfill({ response, headers });
  });

  // Set viewport
  await page.setViewportSize({ width: viewportWidth, height: 900 });

  // Load page
  await page.goto(targetUrl, { waitUntil: 'networkidle' });

  // Kill animations so nothing is mid-transition
  await page.addStyleTag({
    content: `
      *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-delay: 0.001ms !important;
        transition-duration: 0.001ms !important;
        transition-delay: 0.001ms !important;
        scroll-behavior: auto !important;
      }
    `
  });

  // Scroll full page to trigger lazy-load and reveal animations
  await page.evaluate(async () => {
    await new Promise((resolve) => {
      let total = 0;
      const step = 300;
      const cap = Math.min(document.body.scrollHeight, 10000);
      const timer = setInterval(() => {
        window.scrollBy(0, step);
        total += step;
        if (total >= cap) {
          clearInterval(timer);
          window.scrollTo(0, 0);
          resolve();
        }
      }, 80);
    });
  });

  // Wait for images and fonts
  await page.waitForTimeout(1000);
}

Step 5: Capture execution

For each page × breakpoint:

  1. Run the pre-capture script (Step 4)
  2. Inject the Figma capture script and submit:
async (page) => {
  const r = await page.context().request.get('https://mcp.figma.com/mcp/html-to-design/capture.js');
  await page.evaluate((s) => {
    const el = document.createElement('script');
    el.textContent = s;
    document.head.appendChild(el);
  }, await r.text());
  await page.waitForTimeout(500);
  return await page.evaluate(() => window.figma.captureForDesign({
    captureId: 'CAPTURE_ID_HERE',
    endpoint: 'https://mcp.figma.com/mcp/capture/CAPTURE_ID_HERE/submit',
    selector: 'body'
  }));
}
  1. Poll generate_figma_design with the captureId every 5 seconds until status: completed (max 10 attempts)
  2. On completion, move to next capture

Batching

  • Generate all capture IDs upfront
  • Run in parallel batches of 5 max
  • Retry failed captures once before marking skipped

For UI states:

After the default capture for a page, use Playwright to trigger each relevant state, then run a fresh capture with the state name appended to the frame name.


Step 6: Figma file organization

Suggest this page structure in Figma (one Figma page per section):

🏠 Home
📄 About
💼 Work
📬 Contact
🔧 [Components]  ← reserved for design system work later

Step 7: Post-capture report

After all captures complete, output:

✅ Site Capture Complete

📄 Pages captured: N
📐 Breakpoints: Mobile / Tablet / Desktop
🖱️  UI states: N
⚠️  Skipped: [any failed pages and why]

🔗 Figma file: [link]

Suggested next steps:
→ Review all frames in Figma
→ Identify repeated components (nav, cards, footer, buttons)
→ Extract a component library from the most reused patterns
→ Define color, typography, and spacing tokens
→ Apply a design system across all frames

Error handling

SituationAction
Playwright MCP not availableStop. Tell user to install it.
Figma Remote MCP not availableStop. Tell user to connect it via /mcp.
Page returns 404 or redirect loopSkip, log in final report
Capture still pending after 10 pollsRetry once with a new capture ID, then skip
Site requires loginAsk user if credentials or cookies can be provided
More than 20 pagesAsk user to confirm scope before proceeding
Figma rate limitWait 10s, reduce batch size to 3

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,512. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.