| name | screenshot-to-html |
| description | Replicate a UI screenshot/image/mockup into a high-fidelity, self-contained HTML page. Faithful image-to-HTML / screenshot-to-code cloning that matches the RENDERED result (not copied computed numbers) through a render -> screenshot -> compare -> refine loop using the local Chrome. Use whenever the user gives an image/screenshot/mockup/Figma export and wants HTML/CSS (plain or Tailwind), or asks to "复刻 / 还原 / clone / recreate / turn this design into a webpage". |
screenshot-to-html — Screenshot to High-Fidelity HTML
Turn a UI image into a clean, self-contained HTML page whose rendered output
matches the target image. Respond to the user in their language (they often
write Chinese); keep chat replies short and let the work happen in files.
Core principle: replicate the RENDERED state, not the COMPUTED state
The target image is the ground truth of the final rendered pixels. Reproduce
that, not a mechanical transcription of per-element values.
- Computed-state cloning (do NOT do this). Eyedropper every hex, "measure"
pixel coordinates/sizes, and paste
getComputedStyle-style numbers into
absolute-positioned divs. Result: magic-number soup, non-semantic, not
responsive, brittle — and small extraction errors compound so it looks worse.
- Rendered-state cloning (DO this). Read the design like a developer: infer
semantic structure, a small set of design tokens (palette, type scale,
spacing rhythm, radii, shadows), and layout intent (flex/grid). Build it,
then render your code, screenshot it, and compare against the target, and
iterate. Optimize "does my render look like the target," goal:
render(code) ≈ target.
This is why the workflow is a feedback loop, not a one-shot dump.
Workflow
Copy this checklist and track progress:
- [ ] Phase 0 — Setup (inputs, stack, target width, deps, assets)
- [ ] Phase 1 — Read the design (global -> components -> exact text)
- [ ] Phase 2 — Build the first draft (semantic + tokens, self-contained)
- [ ] Phase 3 — Rendered-state loop (render, compare, fix; repeat 2-4x)
- [ ] Phase 4 — Interactivity & states (affordances + behavior, then verify)
- [ ] Phase 5 — Final checks
- [ ] Phase 6 — Enhance with motion (optional)
Phase 0 — Setup
- Confirm inputs: the target image path(s) and the desired output file path.
- Assets are automatic (no questions by default). Fill each slot with what gives the
sharpest, most faithful result — not "crop everything": user-supplied assets win; real logos as
inline SVG / official files; specific unique content cropped from the source
(
scripts/crop.mjs) when that region is sharp; generic photos from Unsplash / picsum; a
https://placehold.co block as last resort. Quality gate: never ship a blurry upscaled crop —
swap a soft crop for a sharp stock photo. Only ask once (non-blocking) if an asset is
brand-critical AND no layer can approximate it. See Images & assets in reference.md.
- Pick the stack (ask only if unclear):
vanilla (HTML + plain CSS,
self-contained, default) or tailwind (HTML + Tailwind CDN). See Stacks.
- Understand the canvas & scale FIRST — before writing any CSS. Read the pixel
size (
sips -g pixelWidth -g pixelHeight <image>), then decide a design width in
CSS px, not the raw pixel count:
- Screenshots are usually retina (
@2x/@3x), so the file is 2-3x the CSS width.
Do not just divide and build at that size — that ships a shrunken page with
~10px nav text that looks tiny at 100% zoom. Map to a real breakpoint:
web -> 1280 or 1440; mobile -> 375/390/393.
- Build at that width with real-world type sizes (hero ~48-72px, body ~16-18px)
and keep it responsive/fluid (
clamp(), max-width, %), so it opens at a
normal size and adapts — never a fixed miniature.
- Run the render/compare loop at this design width;
--scale 2 only sharpens the
diff, it does not mean "author at half size."
- Ensure render deps once (no browser download):
npm i -D playwright-core.
Phase 1 — Read the design (global first, then local)
Look at the image carefully and write a short build spec. Global before local:
- Layout: page max-width, column grid, section order, alignment.
- Color tokens: name 4-8 colors (bg, surface, ink, muted, brand, accent, line).
Account for gradients/overlays/opacity — never trust one pixel.
- Type: families (serif vs sans, geometric vs humanist -> closest Google Font),
the heading->body scale, weights, letter-spacing, line-height.
- Spacing rhythm (base 4/8px), border-radii, shadow depth, borders/dividers.
- Components: nav, hero, feature/bento, stats, cards, testimonials, CTA, footer —
note structure and repetition (build one, loop the data).
- Content: transcribe the exact visible text (do not invent or translate it).
Do NOT record pixel coordinates. Record relationships and tokens.
For the full rubric see reference.md.
Phase 2 — Build the first draft
- One self-contained file (inline
<style>, minimal inline JS only if needed).
- Semantic HTML (
header/nav/main/section/article/footer, headings in order).
- Put tokens in
:root CSS variables; build layout with flex/grid, fluid
sizing (clamp, min()), not fixed pixel frames.
- Use the exact text from the image.
- Images: fill each slot with the best layer for that slot (see Phase 0) — crop specific,
unique content from the source when it's sharp (
node scripts/crop.mjs --in <target> --rect x,y,w,h --out assets/<name>.png), use a sharp Unsplash/picsum photo for generic
imagery (a fuzzy crop is worse than a real photo), else https://placehold.co/<w>x<h> with a
<!-- asset: ... --> note. Icons: inline SVG (avoid emoji-as-icon).
- Match the image's breakpoint first; add sensible responsive behavior without
inventing layouts you cannot see.
Phase 3 — Rendered-state loop (the important part)
- Render your draft to a screenshot:
node scripts/shot.mjs \
--in <output.html> --out tmp/screenshot-to-html/v1.png --width <DESIGN_WIDTH>
- View both the target image and your render. Compare region by region and
list concrete diffs, prioritized: layout/structure > sizing/spacing >
color/contrast > typography > polish (shadows, radii, gradients).
- Fix the top issues with semantic edits (adjust tokens/layout, not magic
numbers). Re-render to
v2.png, v3.png, ...
- Repeat until differences are cosmetic (usually 2-4 rounds). Each round must
reduce the visual gap; if a change makes it worse, revert it.
Comparison tips and common illusions are in reference.md.
Phase 4 — Interactivity & states (make it a page, not an image)
A screenshot is one frozen frame — it can't show hover, focus, or what's clickable. Matching it
alone ships a "webpage that's secretly an image." Make the replica behave like a real page:
- Real elements. Anything that looks clickable is a
<button> or <a href> (or
role="button" + tabindex="0"), never an inert <div>/<span>; inputs are real <input>.
- Affordances by default (always). Every control gets
cursor: pointer, a visible :hover
change, a :focus-visible ring (keyboard), and an :active press, with a short
transition (~120-180ms). These don't alter the resting look — they add feedback.
- Functional behavior when the image implies it (minimal inline JS): tab/segmented controls
switch the visible panel; a multi-screen mobile app navigates inside its device frame
(bottom-nav / cards swap screens); modals/menus/dropdowns/accordions open and close (Esc /
backdrop to dismiss); in-page nav smooth-scrolls. Don't invent flows you cannot infer.
- A11y & motion. Logical heading order,
alt text, visible focus, decent contrast; honor
prefers-reduced-motion.
- Verify, don't assume:
node scripts/shot.mjs --in <file> --verify flags clickable-looking
elements that are inert or missing a hover/focus state; --states captures hover/focus/open
frames. Fix every dead control. Full detail in reference.md.
- Tactile depth (zero-dependency). For the controls the screenshot implies — swipeable
galleries, sliders, a heart that pops, drag-to-snap decks — make them feel hand-built with
plain JS + CSS (pointer drag, velocity snap, press/scale). No motion library needed for any of
it. Patterns and copy-paste snippets in references/interaction.md.
Phase 5 — Final checks
- Side-by-side render vs target reads as "the same page."
- Exact text; correct color/contrast; type scale and weights match.
- Responsive sanity: render at a second width (e.g.
--width 390) — nothing
overlaps or overflows.
- Interactivity: every clickable-looking element is real and shows hover/focus;
--verify passes.
- Self-contained and valid; no leftover probe/temp files (keep
tmp/screenshot-to-html/
out of the deliverable). Report the final file path + final render.
Phase 6 — Enhance with motion (optional, only on request)
Interactivity (Phase 4) ships by default; GSAP motion/animation does not. Rich tactile
feedback — drag, snap, like-pop — needs no library (references/interaction.md);
GSAP is only for heavier, explicitly-requested choreography. Do not add motion
on your own, and never ask about it — only enhance when the user explicitly requests animation ("加动效",
"make it animated"). Then add restrained GSAP animation — high-impact moments,
not AI-slop everywhere. Read references/animation.md for
the patterns (load-in stagger, scroll-triggered reveal, hover micro-interactions),
CDN setup, and pitfalls. Keep the HTML self-contained (GSAP via CDN) and honor
prefers-reduced-motion; verify a settled frame with node scripts/shot.mjs --motion.
This skill is self-contained; for deeper choreography you may optionally also install
the official GSAP skills (npx skills add github.com/greensock/gsap-skills) — not required.
Scripts (paths relative to this skill directory)
scripts/shot.mjs — render a local HTML file with installed Google Chrome via
playwright-core (no browser download); the render step of the loop.
node scripts/shot.mjs --in replica.html --out tmp/screenshot-to-html/v1.png --width 1280
If Chrome is missing it falls back to bundled chromium (npx playwright install chromium).
scripts/crop.mjs — crop a rectangle out of the source screenshot to reuse real
imagery (asset layer 2). Uses sharp if installed, else macOS sips.
node scripts/crop.mjs --in <target.png> --rect x,y,w,h --out assets/hero.png
Stacks
Pick per request; both produce a single self-contained file.
vanilla (default) — HTML + plain CSS in one file. Tokens in :root. Best for
matching a bespoke visual identity and staying dependency-free.
tailwind — add <script src="https://cdn.tailwindcss.com"></script> and build
with utilities; map the palette/scale via the tailwind.config inline object.
Both stacks:
- Fonts: Google Fonts
<link> (pick the closest match to the image).
- Icons: inline SVG, or Font Awesome
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css.
- Missing images: layered fallback (crop from source -> Unsplash/picsum ->
placehold.co).
Output & temp conventions
- Final HTML: the path the user asks for. Default
index.html; if a file
already exists there and is unrelated, do not overwrite it — confirm or write
to replica.html.
- Screenshots/intermediates:
tmp/screenshot-to-html/ (safe to delete). Create it if missing.
Quality expectations
- Semantic, readable, maintainable; design tokens over magic numbers.
- No absolute-position-everything layouts; no "measured" pixel coordinates.
- Fidelity to the image: layout, exact text, color, type, spacing, radii, shadows.
- Multiple screenshots = multiple pages/tabs: link or scaffold them sensibly.
Additional resources
- reference.md — analysis rubric, comparison methodology,
computed-state traps, fonts/colors/assets, the four-layer asset strategy.
- references/interaction.md — zero-dependency tactile-polish
standard (Phase 4): pointer-drag primitive, swipe/carousel snap, like-pop, toggles, reduced-motion.
- references/animation.md — optional GSAP motion patterns
(Phase 6): CDN setup, load-in/scroll/hover recipes, reduced-motion, pitfalls.