| name | pixel-perfect-replication |
| description | Image-to-code replication pipeline. When the user provides a screenshot or design reference, this skill runs a structured extraction across seven layers (grid, type, color, spacing, components, atmosphere, interaction), builds an Extraction Sheet before any code is written, implements with exact fidelity to the reference, and verifies through inline Quality Gates and a final Visual Diff. The reference image is the spec. The code is a translation, not an interpretation. |
Pixel-Perfect Design Replication
This skill fires when the user provides a screenshot, mockup, Figma export, or any design image and asks you to replicate it in code. The reference image is the specification. Your role is translator, not designer. Every visual decision - font size, spacing, color, radius, shadow, layout proportion - comes from the image, not from your preferences.
The Pipeline
Every replication job follows this flow. No phase can be skipped. No phase can start before the previous one completes.
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ IMAGE IN ──→ Phase 1: Intake ──→ Phase 2: Deep Extraction │
│ (classify) (7 layers, fill sheets) │
│ │
│ ──→ Phase 3: Build │
│ (structure-first, exact CSS) │
│ │
│ ──→ Phase 4: Visual Diff │
│ (verify against reference) │
│ │
│ Each phase has a ✓ Quality Gate. Failing a gate blocks the next. │
│ │
└─────────────────────────────────────────────────────────────────────┘
Phase 1: Image Intake
Receive the reference image. Before doing anything else, classify what you are looking at.
→ Classify the image
Fill in this table for every reference image:
| Field | Your answer |
|---|
| Image type | Full-page screenshot / single section / component detail / mobile view / desktop view / Figma frame / design tool export / live site screenshot |
| Sections visible | List top-to-bottom, e.g. "Nav → Hero → Features → Testimonials → Footer" |
| Target viewport | Estimated width: 1440px (desktop), 1280px (laptop), 768px (tablet), 375px (mobile) |
| Fidelity | High-res export (sub-pixel details are intentional) / compressed screenshot (some lossy artifacts) |
| Theme | Light / Dark / Mixed |
→ Output the Extraction Summary
Before any code, state what you see in structured natural language. This anchors every decision that follows.
Example:
Light mode, 1440px desktop. Five sections: sticky frosted nav with logo left / links center / CTA right, hero with massive serif heading left-aligned over full-bleed photography, 3-col feature grid with icon-top cards, testimonial carousel with large quotation marks, minimal footer with 4-col link grid. Palette: warm cream base, near-black text, terracotta accent on CTAs. Typography: serif display heading (likely Playfair Display), geometric sans body (likely Outfit). Cards are sharp-cornered, buttons are pill-shaped. No visible shadows - flat design with subtle border separators.
→ If the image is unclear
Do not guess. Ask specifically:
"The nav links are too compressed to read at this resolution. The body font could be Outfit or Satoshi - they share near-identical geometry at this size. Can you provide a closer crop of the nav, or confirm the font stack?"
✓ Quality Gate: Intake
Before moving to Phase 2, confirm:
- You have classified the image type, section count, viewport, and fidelity
- You have written the Extraction Summary
- You have flagged any unclear areas and asked for clarification (or confirmed everything is readable)
Phase 2: Deep Extraction
Run all seven extraction layers on the reference image. Each layer focuses on one dimension of the design. Fill in the Extraction Sheet for each. Skipping a layer causes drift - small errors here compound into "it looks off" in the final build.
Layer 1: Layout Grid
Extract the spatial skeleton.
Extraction Sheet:
| Property | Measured value |
|---|
| Container max-width | e.g. 1280px, 1440px - measure by proportion against viewport edges |
| Column system | e.g. grid-cols-[1.15fr_1fr], grid-cols-3, single column centered |
| Horizontal padding | e.g. px-6 md:px-12 lg:px-20 - measure the gap between content edge and viewport edge |
| Section heights | min-h-[100dvh] for full-viewport, auto for content-driven |
| Section spacing | Vertical gap between sections, e.g. py-24 lg:py-32 |
| Alignment | Per-section: left / center / right / mixed |
| Z-axis layering | Any overlaps? Elements stacked on top of others? |
How to measure proportions from images:
- If the hero heading occupies ~60% of viewport width, on a 1440px target that is roughly
max-w-[54rem]
- If one column is visually 1.5x wider than the adjacent column, use
grid-cols-[1.5fr_1fr]
- If empty space above a heading is roughly 2x the heading font size, the padding is approximately
2em relative to the heading
⚠ Drift Warning: The most common layout error is getting the container max-width wrong. A design with max-w-[1200px] looks noticeably different from one with max-w-[1440px] - the whitespace proportions change completely. Measure carefully.
Layer 2: Typography
This is the most critical extraction. Wrong typography is the #1 reason a replication "looks off."
Extraction Sheet (fill for EVERY visible text element):
| Element | Font family | Weight | Size | Line-height | Letter-spacing | Transform | Color |
|---|
| Nav links | | | | | | | |
| Eyebrow/label | | | | | | | |
| H1 (hero) | | | | | | | |
| H2 (section) | | | | | | | |
| H3 (card title) | | | | | | | |
| Body text | | | | | | | |
| Caption/meta | | | | | | | |
| CTA text | | | | | | | |
| Footer links | | | | | | | |
Font identification - what to look for:
Fonts reveal themselves through specific characters. Study these before guessing:
| Check this character | What it tells you |
|---|
Lowercase a | Single-story (Geist, Helvetica) vs double-story (Outfit, Satoshi, DM Sans) |
Lowercase g | Open-tail (most sans-serifs) vs closed-tail (Futura, some geometric) |
Lowercase t | Curved crossbar (humanist: Manrope, Jakarta) vs straight (geometric: Outfit, Satoshi) |
Capital R | Straight leg (Geist, Helvetica) vs curved leg (Outfit, Satoshi) |
Capital Q | Tail style varies dramatically between fonts - strong identifier |
Lowercase e | High crossbar (geometric) vs centered (humanist) |
Numbers 1, 4, 6, 9 | Highly distinctive shapes across fonts |
Common web font quick-reference:
| Visual character | Strong candidates |
|---|
Geometric, double-story a, round counters | Outfit, Satoshi, DM Sans, Plus Jakarta Sans |
Grotesque, single-story a, flat terminals | Geist, Suisse Intl, Helvetica Neue |
| Humanist, open counters, calligraphic stress | Manrope, Plus Jakarta Sans, Nunito Sans |
| Condensed, tall x-height | Barlow Condensed, Oswald, Archivo Narrow |
| Modern serif, high contrast, sharp serifs | Playfair Display, Bodoni Moda |
| Transitional serif, moderate contrast | Lora, Merriweather, Source Serif Pro |
| Display sans, wide, heavy | Cabinet Grotesk, Clash Display, Monument Extended |
| Monospace | JetBrains Mono, Fira Code, IBM Plex Mono, Geist Mono, Space Mono |
If you cannot confidently identify the font, state your top 2-3 candidates with the distinguishing character that makes you lean one way. Example: "The double-story 'a' and round 'o' suggest Outfit, but the slightly squared terminals could indicate Satoshi. Defaulting to Outfit - swap by changing --font-display if incorrect."
⚠ Drift Warning: Never assume a heading is font-weight: 700 because "headings are bold." Many premium designs use 500 or 600 for headings with a heavier font face. Look at stem thickness relative to the counter space.
Layer 3: Color Palette
Extract every distinct color. Not "it uses blue" - extract the hex.
Extraction Sheet:
| Role | Hex value | Notes |
|---|
| Background (primary) | | e.g. #F5F0EB warm cream, not plain #FFFFFF |
| Background (secondary) | | Alternate section BG, card BG |
| Background (dark section) | | If any sections flip to dark |
| Text (primary) | | Heading + body text on primary BG |
| Text (secondary) | | Muted descriptions, metadata |
| Text (tertiary) | | Placeholders, disabled states |
| Accent | | CTAs, active indicators, links |
| Accent (hover) | | Darker/lighter variant on interaction |
| Border | | Card borders, dividers, input borders |
| Shadow | | If tinted, note the hue |
Extracting colors from compressed screenshots:
Screenshots compress colors. To get accurate values:
- Sample from the largest flat area of the color, not from edges or JPEG artifacts
- Cross-reference with common web values - if you measure
#0b0b0b, it is almost certainly #0a0a0a (standard off-black). If you measure #f4f3f1, it is likely #f5f4f2 (common warm cream)
- After extracting, verify WCAG AA contrast between text and background colors to confirm the values are reasonable
⚠ Drift Warning: The difference between #FFFFFF (pure white) and #F5F0EB (warm cream) completely changes the feel of a page. Do not default to #FFFFFF or #000000 unless the reference genuinely shows pure values - most premium designs use off-white and off-black.
Layer 4: Spacing System
Spacing is what separates "looks close" from "looks identical."
Extraction Sheet:
| Measurement | Value | How to verify |
|---|
| Base unit | 4px or 8px | Measure the smallest repeated gap |
| Button padding (H) | e.g. 24px / px-6 | Horizontal space between text edge and button edge |
| Button padding (V) | e.g. 12px / py-3 | Vertical space |
| Card internal padding | e.g. 32px / p-8 | Space from card edge to card content |
| Grid gap | e.g. 24px / gap-6 | Space between cards/columns |
| Heading → subtext | e.g. 16px / mt-4 | Gap between heading baseline and subtext top |
| Subtext → CTA | e.g. 32px / mt-8 | Gap between subtext and button |
| Section padding (top) | e.g. 96px / pt-24 | Space from section top to first element |
| Section padding (bottom) | e.g. 128px / pb-32 | Space from last element to section bottom |
| Nav height | e.g. 64px / h-16 | Total nav bar height |
| Nav link gap | e.g. 32px / gap-8 | Space between nav links |
⚠ Drift Warning: Top and bottom section padding are often not equal. Many designs use more bottom padding than top (or vice versa) for optical balance. Do not assume py-24 when the reference shows pt-20 pb-28. Measure each side independently.
Layer 5: Component Inventory
Catalog every distinct UI component visible in the image.
For each component, fill in:
| Component | Shape (radius) | Border | Shadow | Background | States visible | Icon style |
|---|
| Primary button | | | | | | |
| Secondary button | | | | | | |
| Card | | | | | | |
| Input field | | | | | | |
| Badge/pill | | | | | | |
| Avatar | | | | | | |
| Navigation | | | | | | |
| Divider | | | | | | |
Border-radius consistency check: Most designs commit to one radius language. Check whether the design uses:
- Sharp -
0px everywhere (brutalist, editorial)
- Subtle -
4-8px everywhere (SaaS, product)
- Rounded -
12-16px everywhere (modern, friendly)
- Pill -
9999px on buttons, rounded on cards (premium, polished)
- Mixed - different radii for different components (verify each one)
⚠ Drift Warning: If buttons are pill-shaped (rounded-full) in the reference, they cannot be rounded-lg in the code. Radius mismatches are immediately visible - the eye detects them faster than color or spacing errors.
Layer 6: Atmosphere and Texture
Extract the subtle details that make a design feel alive vs flat.
Extraction Sheet:
| Property | Present? | Details |
|---|
| Noise/grain overlay | yes/no | Opacity level (typically 0.03-0.06) |
| Radial ambient glow | yes/no | Position, color, spread |
| Frosted glass (backdrop-blur) | yes/no | On what elements, blur amount |
| Gradient backgrounds | yes/no | Direction, stops, colors |
| Tinted shadows | yes/no | Shadow hue, not just black |
| Image overlays/scrims | yes/no | Gradient direction, opacity |
| Background images/patterns | yes/no | Subtle texture, dots, lines |
| Depth/layering feel | flat / subtle / heavy | Overall shadow usage |
Layer 7: Responsive Cues and Interaction Inference
Even from a static image, extract clues about behavior.
Extraction Sheet:
| Signal | Inference |
|---|
| Multi-column layout | Will collapse to single column below 768px |
| Horizontal nav bar | Will need mobile menu below 768px |
| Sticky-looking nav | position: fixed; top: 0 with backdrop-blur likely |
| Elements positioned as if "just landed" | Entry animation implied (fade-up with stagger) |
| Buttons with visual depth | Lift on hover (translateY(-1px), shadow increase) |
| Cards with borders | Border color change or subtle background shift on hover |
| Dot indicators near images | Carousel/slider component |
| Active/selected tab styling | Tab component with state management |
| Form inputs visible | Focus ring, validation states needed |
✓ Quality Gate: Extraction
Before moving to Phase 3, confirm:
- All seven Extraction Sheets are filled in
- Font candidates are identified with reasoning
- Every distinct color has a hex value
- Spacing values are measured, not assumed
- Component inventory is complete with radius, border, shadow noted per component
- Anything unclear has been flagged to the user
Phase 3: Build
Implementation starts here. Follow this exact build order - each step depends on the one before it.
→ Step 1: Global Foundation
Set the design tokens first. Everything else references these.
@import url('https://fonts.googleapis.com/css2?family=FONT_NAME:wght@300;400;500;600;700&display=swap');
:root {
--color-bg: #____;
--color-surface: #____;
--color-text: #____;
--color-text-2: #____;
--color-text-3: #____;
--color-accent: #____;
--color-accent-hover: #____;
--color-border: rgba(_, _, _, _);
--font-display: 'FONT_NAME', Georgia, serif;
--font-body: 'FONT_NAME', system-ui, sans-serif;
--font-mono: 'FONT_NAME', monospace;
--space-section: clamp(5rem, 10vw, 8rem);
--space-component: 2rem;
--space-element: 1rem;
--radius-card: __px;
--radius-button: __px;
--radius-input: __px;
}
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
: (--font-body);
: ;
: ;
: (--color-text);
: (--color-bg);
-webkit-: antialiased;
-moz-osx-: grayscale;
}
→ Step 2: Layout Skeleton
Build the section containers with correct dimensions. No content yet - just the boxes.
.section {
width: 100%;
max-width: ____px;
margin: 0 auto;
padding: var(--space-section) clamp(1.5rem, 5vw, 5rem);
}
.section--full {
min-height: 100dvh;
display: flex;
flex-direction: column;
justify-content: center;
}
.grid-2-asymmetric {
display: grid;
grid-template-columns: 1.15fr 1fr;
align-items: center;
gap: 4rem;
}
.grid-3-equal {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
@media (max-width: 768px) {
.grid-2-asymmetric,
{
: fr;
: ;
}
}
→ Step 3: Typography Pass
Apply all text styles from the Layer 2 Extraction Sheet.
.heading-display {
font-family: var(--font-display);
font-size: clamp(2.5rem, 6vw, 5rem);
font-weight: 700;
line-height: 0.95;
letter-spacing: -0.03em;
color: var(--color-text);
text-wrap: balance;
max-width: 18ch;
}
.heading-section {
font-family: var(--font-display);
font-size: clamp(1.75rem, 4vw, 3rem);
font-weight: 600;
line-height: 1.1;
letter-spacing: -0.02em;
color: var(--color-text);
}
.body-text {
font-family: var(--font-body);
font-size: clamp(0.9375rem, , );
: ;
: ;
: (--color-text-);
: ;
}
{
: (--font-mono);
: ;
: ;
: ;
: uppercase;
: (--color-text-);
}
{
: ;
: ;
: (--color-text-);
}
⚠ Drift Warning: Do not round font sizes to convenient values. If the extraction shows 15px body text, use 0.9375rem, not 1rem. If the heading looks like 72px, use 4.5rem, not 5rem. Rounding accumulates across the page.
→ Step 4: Components Pass
Build each component from the Layer 5 Extraction Sheet.
.btn-primary {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
font-family: var(--font-body);
font-size: 0.875rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
border-radius: var(--radius-button);
background: var(--color-accent);
color: #FFFFFF;
border: none;
cursor: pointer;
text-decoration: none;
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.btn-primary:hover {
background: var(--color-accent-hover);
transform: translateY(-);
: (, , , );
}
{
: () ();
}
{
: solid (--color-accent);
: ;
}
{
: inline-flex;
: center;
: center;
: ;
: ;
: (--font-body);
: ;
: ;
: (--radius-button);
: transparent;
: (--color-text);
: solid (--color-border);
: pointer;
: none;
: all (, , , );
}
{
: (--color-text);
: (, , , );
}
{
: (--color-surface);
: (--radius-card);
: (--space-component);
: solid (--color-border);
}
{
: fixed;
: ;
: ;
: ;
: ;
: ;
: flex;
: center;
: space-between;
: (, , );
: (, , , );
: ();
-webkit-: ();
: solid (--color-border);
}
{
: ;
: ;
: (--color-text-);
: none;
: color ease;
}
{
: (--color-text);
}
→ Step 5: Spacing Adjustments
Walk through every element gap and verify against the Layer 4 Extraction Sheet. This is where implementations most commonly drift.
→ Step 6: Atmosphere Pass
Add texture and depth from the Layer 6 Extraction Sheet. Only add what the reference shows.
.grain-overlay {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 9999;
opacity: 0.04;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
}
.ambient-glow {
position: absolute;
inset: 0;
background: radial-gradient(
ellipse 60% 50% at 50% 40%,
rgba(255, 255, 255, 0.035) 0%,
transparent 70%
);
pointer-events: none;
}
.surface-frosted {
background: rgba(255, 255, , );
: () ();
-webkit-: () ();
}
⚠ Drift Warning: Do not add grain, glow, or blur if the reference does not show them. These are atmosphere details, not defaults. Adding texture the reference does not have is interpretation, not replication.
→ Step 7: Responsive Pass
If the reference is desktop-only, infer mobile behavior from the layout structure. If both desktop and mobile references are provided, match both exactly.
@media (max-width: 768px) {
.grid-2-asymmetric,
.grid-3-equal {
grid-template-columns: 1fr;
}
.heading-display {
max-width: 100%;
}
.nav {
}
.btn-primary,
.btn-ghost,
.nav-link {
min-height: 44px;
}
}
→ Step 8: Interaction Pass
Add hover states and entry animations. Static images cannot show interaction, but every interactive element needs feedback.
@keyframes enter {
from {
opacity: 0;
transform: translateY(24px);
filter: blur(6px);
}
to {
opacity: 1;
transform: translateY(0);
filter: blur(0);
}
}
.animate-in {
animation: enter 0.7s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.animate-in:nth-child(1) { animation-delay: 0.05s; }
.animate-in:nth-child(2) { animation-delay: 0.15s; }
.animate-in:nth-child(3) { animation-delay: 0.25s; }
.animate-in:nth-child(4) { animation-delay: 0.35s; }
@media (prefers-reduced-motion: reduce) {
{
: none;
: ;
: none;
: none;
}
}
, , , , , {
: all (, , , );
}
✓ Quality Gate: Build
Before moving to Phase 4, confirm:
- Global tokens are set from extraction values, not defaults
- Layout skeleton matches the reference section structure
- Typography uses explicit values for every property (no browser defaults)
- Components match extraction sheets for radius, border, shadow, and padding
- Spacing between elements is verified against the reference
- Atmosphere effects are only present if the reference shows them
- Responsive collapse is implemented
- Hover/focus states are present on all interactive elements
Phase 4: Visual Diff
Compare your implementation against the reference image. This is the final verification. Walk through every category below. Mark each item PASS or FAIL. Any FAIL blocks delivery.
Layout Diff
| Check | PASS/FAIL |
|---|
| Section count and order match | |
| Container max-width proportions match | |
| Horizontal padding matches | |
| Grid column counts and ratios match | |
| Section heights feel proportionally correct | |
| Vertical spacing between sections matches | |
| Element alignment (left/center/right) matches per section | |
| Z-axis layering matches (if present) | |
Typography Diff
| Check | PASS/FAIL |
|---|
| Font family loaded and rendering correctly | |
| Heading size proportionally correct against viewport | |
| Heading weight matches (not too thin, not too heavy) | |
| Heading line-height matches (tight vs relaxed) | |
| Heading letter-spacing matches (tight vs wide) | |
| Body text size matches | |
| Body line-height and max-width match | |
| Eyebrow/label styling matches (size, case, tracking, font) | |
| Text colors match per element type | |
Color Diff
| Check | PASS/FAIL |
|---|
| Primary background color matches (not #FFF when it should be #F5F0EB) | |
| Text colors match (primary and secondary) | |
| Accent color matches | |
| Border/divider colors match | |
| No unexpected color shifts between sections | |
Component Diff
| Check | PASS/FAIL |
|---|
| Button radius matches (pill vs rounded vs sharp) | |
| Button padding matches | |
| Button text styling matches (size, weight, case, tracking) | |
| Card styling matches (radius, padding, border, shadow) | |
| Navigation matches (height, background treatment, link styling) | |
| Image treatment matches (radius, aspect ratio, object-fit) | |
Spacing Diff
| Check | PASS/FAIL |
|---|
| Heading → subtext gap matches | |
| Subtext → CTA gap matches | |
| Card internal padding matches | |
| Grid gap between elements matches | |
| Section padding (top AND bottom independently) matches | |
| Overall breathing room / negative space feels correct | |
Atmosphere Diff
| Check | PASS/FAIL |
|---|
| Background atmosphere matches (flat/grain/gradient/glow) | |
| Shadow presence and intensity match | |
| Frosted glass / blur effects present where reference shows them | |
| No atmosphere effects added that the reference does NOT show | |
Technical Checks
| Check | PASS/FAIL |
|---|
| Zero console errors | |
| All fonts loaded (no FOUT/FOIT) | |
| All images loaded (no broken placeholders) | |
| No horizontal overflow at any viewport | |
| Hover states on all interactive elements | |
| Focus-visible states for keyboard navigation | |
min-h-[100dvh] used, not h-screen | |
prefers-reduced-motion respected | |
Handling Artistic Assets (Photographs, Illustrations, Textures)
CSS can reproduce layout, typography, colors, and geometric shapes. It CANNOT reproduce photographs, hand-drawn illustrations, organic brush strokes, marble textures, or painterly effects. This is the #1 source of drift between reference and output.
→ Classify every visual element in the reference
Walk through the reference and tag each visual element:
| Element type | Can CSS reproduce it? | What to do instead |
|---|
| Solid color blocks | ✅ Yes | Use exact hex from extraction |
| Linear/radial gradients | ✅ Yes | Match direction, stops, and colors |
| Geometric shapes (circles, rectangles, lines) | ✅ Yes | Use CSS shapes or simple SVG |
| Icons (outlined/filled) | ✅ Yes | Use an icon library or inline SVG |
| Photographs | ❌ No | Generate a mood-matched image or use picsum.photos/seed/{keyword}/{w}/{h} |
| Hand-drawn illustrations | ❌ No | Generate a matching illustration with generate_image tool |
| Organic brush strokes / paint textures | ❌ No | Generate as an image asset - do NOT approximate with CSS gradients |
| Marble / fluid / organic textures | ❌ No | Generate as an image asset or use a high-quality stock match |
| 3D renders | ❌ No | Generate a matching render or use a placeholder with similar lighting/angle |
| Abstract art / mixed media | ❌ No | Generate with generate_image describing the exact style, colors, and composition |
→ When the reference contains photographs
- First choice: Use
generate_image to create a photograph that matches the mood, color palette, subject, and composition of the reference
- Second choice: Use
picsum.photos/seed/{descriptive-keyword}/{w}/{h} with a keyword that matches the content (e.g., picsum.photos/seed/ocean-waves/800/600 for ocean imagery)
- Never: Use a CSS gradient, striped pattern, or solid color block as a stand-in for a photograph. This is the most visible form of drift
→ When the reference contains illustrations or brush strokes
- First choice: Use
generate_image with a detailed prompt describing the illustration style, colors, stroke quality, and composition. Include the mood: "organic hand-painted pink brush stroke with visible texture, diagonal across white background, coral/hot-pink color, similar to expressive abstract art"
- Second choice: If
generate_image is not available, find a stock illustration with matching style and color
- Never: Approximate organic, hand-drawn artwork with CSS gradients or geometric shapes. A diagonal CSS gradient is NOT a brush stroke. A CSS
border-radius blob is NOT an organic shape. The eye detects the difference instantly
→ When the reference contains textured surfaces
Marble, wood grain, concrete, fabric, water, clouds - these need real image assets:
- Generate the texture with
generate_image describing the specific surface
- Apply as a
background-image with appropriate background-size, object-fit, and positioning
- Match the scale - a zoomed-in marble texture looks different from a zoomed-out one
→ Asset sizing and positioning
When placing generated image assets into the layout:
.asset-container {
position: relative;
overflow: hidden;
border-radius: var(--radius-card);
}
.asset-image {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
⚠ Drift Warning: This is the most common replication failure. When the AI cannot reproduce an artistic element, it substitutes a CSS pattern (stripes, gradients, solid blocks). This ALWAYS looks wrong because geometric CSS patterns have a fundamentally different visual quality than photographs or hand-drawn art. Always generate an image asset instead - even an imperfect generated image is closer to the reference than a CSS approximation.
Edge Cases
When you cannot identify the font
- State your top 2-3 candidates with the distinguishing character that makes you lean one way
- Suggest the user inspect the live site via DevTools → Computed Styles →
font-family
- Default to the closest Google Font match
- Structure your CSS so the font can be swapped by changing a single
--font-display variable
When the reference is low resolution
- Extract what you can confidently determine (layout, palette, general typography)
- Flag uncertain measurements explicitly
- Ask for a higher-res image or a URL to the live site
- Do not invent sub-pixel details from a blurry screenshot
When the reference shows content you cannot reproduce
Some references show dynamic content (live chat, real user avatars, real-time data):
- Reproduce the visual appearance with static placeholder data
- Use realistic content - real-sounding names, organic numbers (not "John Doe" or "99.99%")
- Note which elements are placeholder in your delivery
For artistic content (photographs, illustrations, textures), see the "Handling Artistic Assets" section above. Never approximate with CSS - always generate or source a real image.
When multiple reference images are provided
- Run the full extraction on each image independently
- Confirm the design system is consistent across images (same fonts, colors, components)
- If inconsistencies exist, ask the user which image is authoritative
- Desktop + mobile pairs: use desktop for the design system, mobile for responsive breakpoints
When the reference uses a recognizable component library
If you spot shadcn/ui, Radix, Material, or another library:
- State which library you believe is in use
- Ask if the user wants the library or manual reproduction
- If using the library: install properly and theme to match
- If reproducing manually: match the visual output without the dependency
The Core Principle
The reference image is the specification. The code is a translation. You are a translator, not a designer. Match the image, not your preferences. If the reference contradicts your aesthetic instincts, the reference wins. The only technical override allowed is min-h-[100dvh] over h-screen, because h-screen is a browser bug, not a design choice.