figma-code-agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +133 -0
  2. package/bin/install.js +328 -0
  3. package/knowledge/README.md +62 -0
  4. package/knowledge/css-strategy.md +973 -0
  5. package/knowledge/design-to-code-assets.md +855 -0
  6. package/knowledge/design-to-code-layout.md +929 -0
  7. package/knowledge/design-to-code-semantic.md +1085 -0
  8. package/knowledge/design-to-code-typography.md +1003 -0
  9. package/knowledge/design-to-code-visual.md +1145 -0
  10. package/knowledge/design-tokens-variables.md +1261 -0
  11. package/knowledge/design-tokens.md +960 -0
  12. package/knowledge/figma-api-devmode.md +894 -0
  13. package/knowledge/figma-api-plugin.md +920 -0
  14. package/knowledge/figma-api-rest.md +742 -0
  15. package/knowledge/figma-api-variables.md +848 -0
  16. package/knowledge/figma-api-webhooks.md +876 -0
  17. package/knowledge/payload-blocks.md +1184 -0
  18. package/knowledge/payload-figma-mapping.md +1210 -0
  19. package/knowledge/payload-visual-builder.md +1004 -0
  20. package/knowledge/plugin-architecture.md +1176 -0
  21. package/knowledge/plugin-best-practices.md +1206 -0
  22. package/knowledge/plugin-codegen.md +1313 -0
  23. package/package.json +31 -0
  24. package/skills/README.md +103 -0
  25. package/skills/audit-plugin/SKILL.md +244 -0
  26. package/skills/build-codegen-plugin/SKILL.md +279 -0
  27. package/skills/build-importer/SKILL.md +320 -0
  28. package/skills/build-plugin/SKILL.md +199 -0
  29. package/skills/build-token-pipeline/SKILL.md +363 -0
  30. package/skills/ref-html/SKILL.md +290 -0
  31. package/skills/ref-layout/SKILL.md +150 -0
  32. package/skills/ref-payload-block/SKILL.md +415 -0
  33. package/skills/ref-react/SKILL.md +222 -0
  34. package/skills/ref-tokens/SKILL.md +347 -0
@@ -0,0 +1,1210 @@
1
+ # Figma to PayloadCMS Mapping
2
+
3
+ ## Purpose
4
+
5
+ Authoritative reference for mapping Figma design components to PayloadCMS block structures. Documents the complete pipeline from Figma component identification through property-to-field mapping, container nesting rules, design token bridging, variant mapping, rich text extraction, and media asset handling. This module bridges the design-to-code knowledge (layout, visual, typography, assets, semantic) with the CMS block system (payload-blocks.md) to produce actionable, deterministic mapping rules.
6
+
7
+ ## When to Use
8
+
9
+ Reference this module when you need to:
10
+
11
+ - Convert a Figma page design into a PayloadCMS page block tree
12
+ - Determine which Figma component maps to which PayloadCMS block type
13
+ - Map Figma component properties (variants, text, booleans, instance swaps) to block field values
14
+ - Translate Figma Auto Layout nesting into Container block nesting
15
+ - Extract Figma design tokens and bridge them to the `--token-*` CSS custom properties consumed by blocks
16
+ - Map Figma component variants to block type variants (e.g., Hero impact levels)
17
+ - Convert Figma text content into Lexical rich text editor format
18
+ - Build a Figma-to-PayloadCMS importer plugin or automation
19
+ - Understand the mapping rules that power the `/figma:map-payload-block` skill
20
+
21
+ ---
22
+
23
+ ## Content
24
+
25
+ ### 1. Mapping Architecture Overview
26
+
27
+ The Figma-to-PayloadCMS mapping pipeline transforms a Figma page design into a structured block tree that PayloadCMS can store and render.
28
+
29
+ #### Pipeline Stages
30
+
31
+ ```
32
+ Figma Page
33
+ |
34
+ v
35
+ [1. Extract] ─────────── Figma REST API / Plugin API
36
+ | Read node tree, properties, styles
37
+ v
38
+ [2. Identify] ────────── Component-to-Block Type Mapping (Section 2)
39
+ | Determine which block type each component becomes
40
+ v
41
+ [3. Map Properties] ──── Property-to-Field Mapping (Sections 3-7)
42
+ | Convert Figma properties to PayloadCMS field values
43
+ v
44
+ [4. Structure] ────────── Container Nesting Rules (Section 4)
45
+ | Build the block hierarchy from Auto Layout nesting
46
+ v
47
+ [5. Extract Content] ─── Rich Text + Media (Sections 8-9)
48
+ | Convert text to Lexical, images to Media collection
49
+ v
50
+ [6. Bridge Tokens] ────── Design Token Bridge (Section 5)
51
+ | Map Figma variables/styles to --token-* properties
52
+ v
53
+ [7. Output] ──────────── PayloadCMS Block Tree
54
+ JSON structure ready for Pages.layout.blocks
55
+ ```
56
+
57
+ #### Data Flow Example
58
+
59
+ A Figma frame named "Hero Section" with:
60
+ - Background image fill
61
+ - Overlay rectangle
62
+ - Heading text node ("Welcome to Our Platform")
63
+ - Body text node ("We help teams build faster")
64
+ - Two button instances ("Get Started", "Learn More")
65
+
66
+ Maps to this PayloadCMS block:
67
+
68
+ ```json
69
+ {
70
+ "blockType": "hero",
71
+ "content": {
72
+ "richText": { "root": { "children": [...] } },
73
+ "buttonGroup": [
74
+ { "link": { "type": "custom", "url": "#", "label": "Get Started" } },
75
+ { "link": { "type": "custom", "url": "#", "label": "Learn More" } }
76
+ ]
77
+ },
78
+ "image": { "image": "<media-id>" },
79
+ "settings": {
80
+ "type": "highImpact",
81
+ "className": "",
82
+ "layoutMeta": { "marginTop": "mt-0", "marginBottom": "mb-0" }
83
+ }
84
+ }
85
+ ```
86
+
87
+ ---
88
+
89
+ ### 2. Component-to-Block Type Mapping
90
+
91
+ Given a Figma node, the following decision tree determines which PayloadCMS block type it maps to. Rules are evaluated in priority order -- the first matching rule wins.
92
+
93
+ #### Decision Tree
94
+
95
+ ```
96
+ Input: Figma SceneNode
97
+ |
98
+ ├─ Is it a COMPONENT_SET or COMPONENT with variant properties?
99
+ | ├─ YES: Check component name and structure against block patterns
100
+ | └─ NO: Analyze frame structure heuristically
101
+ |
102
+ v
103
+ [Rule 1] Hero Detection (confidence: 0.9)
104
+ Match IF:
105
+ - Frame/component name contains "hero" (case-insensitive) OR
106
+ - Frame is full-width (width >= parent width) AND has:
107
+ - Background image fill OR background color fill
108
+ - At least one large text node (fontSize >= 28px)
109
+ - At least one button-like child
110
+ Block: hero
111
+ |
112
+ v
113
+ [Rule 2] Card Detection (confidence: 0.85)
114
+ Match IF:
115
+ - Frame/component name contains "card" (case-insensitive) OR
116
+ - Frame has ALL of:
117
+ - Bounded width (not full-width, typically 200-500px)
118
+ - Image child (image fill or image frame)
119
+ - Text content (heading + body text)
120
+ - Optional CTA/button child
121
+ - Border radius > 0 OR box-shadow
122
+ Block: card
123
+ |
124
+ v
125
+ [Rule 3] Button Detection (confidence: 0.95)
126
+ Match IF:
127
+ - Frame/component name contains "button" or "btn" (case-insensitive) OR
128
+ - Frame has ALL of:
129
+ - Small dimensions (height < 60px, width < 300px)
130
+ - Background fill (solid color)
131
+ - Single text child (short label text, < 30 characters)
132
+ - Border radius > 0
133
+ - No image children
134
+ Block: button
135
+ |
136
+ v
137
+ [Rule 4] Navigation Detection (confidence: 0.85)
138
+ Match IF:
139
+ - Frame/component name contains "nav", "navigation", "menu" (case-insensitive) OR
140
+ - Frame has:
141
+ - Horizontal Auto Layout
142
+ - Multiple small text/link children (3-8 items)
143
+ - No image children
144
+ - Compact height (< 80px)
145
+ Block: subnavigation
146
+ |
147
+ v
148
+ [Rule 5] Accordion Detection (confidence: 0.8)
149
+ Match IF:
150
+ - Frame/component name contains "accordion", "collapse", "expand" OR
151
+ - Frame has:
152
+ - Vertical Auto Layout
153
+ - Repeating child pattern: header row + content area
154
+ - Toggle icon (chevron, plus/minus) in header
155
+ Block: accordion
156
+ |
157
+ v
158
+ [Rule 6] Tabs Detection (confidence: 0.8)
159
+ Match IF:
160
+ - Frame/component name contains "tab" (case-insensitive) OR
161
+ - Frame has:
162
+ - Tab bar (horizontal row of text labels with active/inactive states)
163
+ - Content panel below tab bar
164
+ - Component variants for active tab state
165
+ Block: tabs
166
+ |
167
+ v
168
+ [Rule 7] Stats/Metric Detection (confidence: 0.85)
169
+ Match IF:
170
+ - Frame/component name contains "stat", "metric", "number" OR
171
+ - Frame has:
172
+ - Large prominent text (fontSize >= 32px, often numeric)
173
+ - Smaller label text below
174
+ - Optional description and source text
175
+ - Compact, card-like dimensions
176
+ Block: stats
177
+ |
178
+ v
179
+ [Rule 8] Testimonial Detection (confidence: 0.85)
180
+ Match IF:
181
+ - Frame/component name contains "testimonial", "quote", "review" OR
182
+ - Frame has:
183
+ - Quote icon or quotation marks
184
+ - Body text (italic or quoted)
185
+ - Author name and title text
186
+ - Optional avatar image (small, circular)
187
+ Block: testimonial
188
+ |
189
+ v
190
+ [Rule 9] Carousel/Slider Detection (confidence: 0.8)
191
+ Match IF:
192
+ - Frame/component name contains "carousel", "slider", "swiper" OR
193
+ - Frame has:
194
+ - Horizontal overflow (clipContent: true, children extend beyond bounds)
195
+ - Navigation dots or arrows
196
+ - Multiple card-like children at same size
197
+ Block: carousel
198
+ |
199
+ v
200
+ [Rule 10] CallToAction Detection (confidence: 0.8)
201
+ Match IF:
202
+ - Frame/component name contains "cta", "callToAction", "banner" OR
203
+ - Frame has:
204
+ - Full-width or near-full-width
205
+ - Strong background color (not white/neutral)
206
+ - Title text + description
207
+ - Optional image on one side
208
+ - No nested complex blocks
209
+ Block: callToAction
210
+ |
211
+ v
212
+ [Rule 11] Media/Image Detection (confidence: 0.9)
213
+ Match IF:
214
+ - Node is an image fill frame with no text children OR
215
+ - Frame/component name contains "image", "photo", "illustration" OR
216
+ - Frame has:
217
+ - Single image fill or single image child
218
+ - No text content
219
+ - No interactive children
220
+ Block: media
221
+ |
222
+ v
223
+ [Rule 12] Video Detection (confidence: 0.85)
224
+ Match IF:
225
+ - Frame/component name contains "video", "player", "embed" OR
226
+ - Frame has:
227
+ - Play button icon overlay
228
+ - 16:9 or similar video aspect ratio
229
+ - Thumbnail image with play indicator
230
+ Block: video
231
+ |
232
+ v
233
+ [Rule 13] Form Detection (confidence: 0.85)
234
+ Match IF:
235
+ - Frame/component name contains "form", "input", "signup" OR
236
+ - Frame has:
237
+ - Multiple input field components
238
+ - Submit button
239
+ - Label + input pairs
240
+ Block: form
241
+ |
242
+ v
243
+ [Rule 14] RichText Fallback (confidence: 0.7)
244
+ Match IF:
245
+ - Frame contains primarily text content OR
246
+ - Node is a TEXT type with multiple styled segments OR
247
+ - Frame has only text children (no images, no interactive elements)
248
+ Block: richText
249
+ |
250
+ v
251
+ [Rule 15] Container Fallback (confidence: 0.6)
252
+ Match IF:
253
+ - Frame has Auto Layout AND contains multiple children that
254
+ individually match other block types
255
+ - This is the "structural wrapper" -- it groups children
256
+ Block: container
257
+ ```
258
+
259
+ #### Confidence Scores
260
+
261
+ Each rule has a confidence score (0.0-1.0) that indicates how reliably the heuristic identifies the correct block type. When implementing automated mapping:
262
+
263
+ - **0.9+** -- High confidence, auto-map without confirmation
264
+ - **0.8-0.89** -- Good confidence, auto-map with optional review flag
265
+ - **0.7-0.79** -- Moderate confidence, flag for human review
266
+ - **Below 0.7** -- Low confidence, require human selection
267
+
268
+ #### Name-Based vs Structure-Based Detection
269
+
270
+ The decision tree uses two complementary strategies:
271
+
272
+ 1. **Name-based** (higher priority) -- If the Figma component or frame name contains a recognizable keyword (hero, card, button, etc.), that provides strong signal. Designers who name their components well make mapping trivial.
273
+
274
+ 2. **Structure-based** (fallback) -- When names are generic (e.g., "Frame 427"), analyze the node's children, properties, and layout structure to infer the block type. This is less reliable but handles unnamed designs.
275
+
276
+ Always check name-based rules first, then fall back to structural analysis.
277
+
278
+ ---
279
+
280
+ ### 3. Property-to-Field Mapping Tables
281
+
282
+ For each block type, these tables define how Figma properties map to PayloadCMS field values.
283
+
284
+ #### Container Mapping
285
+
286
+ The Container block is the most direct mapping from Figma Auto Layout.
287
+
288
+ | Figma Property | PayloadCMS Field | Mapping Rule |
289
+ |---------------|-----------------|--------------|
290
+ | `layoutMode: HORIZONTAL` | `settings.layout` = `'row'` | Direct |
291
+ | `layoutMode: VERTICAL` | `settings.layout` = `'col'` | Direct |
292
+ | `layoutMode: NONE` + grid-like children | `settings.layout` = `'grid'` | Inferred |
293
+ | `primaryAxisAlignItems: MIN` | `settings.justifyContent` = `'justify-start'` | Direct |
294
+ | `primaryAxisAlignItems: CENTER` | `settings.justifyContent` = `'justify-center'` | Direct |
295
+ | `primaryAxisAlignItems: MAX` | `settings.justifyContent` = `'justify-end'` | Direct |
296
+ | `primaryAxisAlignItems: SPACE_BETWEEN` | `settings.justifyContent` = `'justify-between'` | Direct |
297
+ | `counterAxisAlignItems: MIN` | `settings.alignItems` = `'items-start'` | Direct |
298
+ | `counterAxisAlignItems: CENTER` | `settings.alignItems` = `'items-center'` | Direct |
299
+ | `counterAxisAlignItems: MAX` | `settings.alignItems` = `'items-end'` | Direct |
300
+ | `counterAxisAlignItems: BASELINE` | `settings.alignItems` = `'items-baseline'` | Direct |
301
+ | `itemSpacing` | `settings.gap` | Snap to nearest option (see below) |
302
+ | Frame name containing "section" | `settings.htmlTag` = `'section'` | Name heuristic |
303
+ | Frame name containing "nav" | `settings.htmlTag` = `'nav'` or `'header'` | Name heuristic |
304
+ | Frame name containing "footer" | `settings.htmlTag` = `'footer'` | Name heuristic |
305
+ | Frame name containing "aside" | `settings.htmlTag` = `'aside'` | Name heuristic |
306
+ | Frame width = parent width | `settings.width` = `'full'` | Size analysis |
307
+ | Frame maxWidth ~1400px | `settings.width` = `'wide'` | Size analysis |
308
+ | Frame maxWidth ~800px | `settings.width` = `'narrow'` | Size analysis |
309
+ | `paddingTop` | `settings.layoutMeta.paddingTop` | Snap to nearest |
310
+ | `paddingBottom` | `settings.layoutMeta.paddingBottom` | Snap to nearest |
311
+
312
+ **Gap snapping rule:** Map Figma `itemSpacing` to the nearest gap option:
313
+
314
+ | Figma itemSpacing (px) | PayloadCMS gap |
315
+ |------------------------|---------------|
316
+ | 0 | `gap-0` |
317
+ | 1-3 | `gap-1` |
318
+ | 4-6 | `gap-2` |
319
+ | 7-12 | `gap-4` |
320
+ | 13-20 | `gap-6` |
321
+ | 21-28 | `gap-8` |
322
+ | 29-40 | `gap-12` |
323
+ | 41+ | `gap-16` |
324
+
325
+ **Spacing snapping rule:** Map Figma padding/margin values to LayoutMeta options:
326
+
327
+ | Figma value (px) | PayloadCMS value |
328
+ |------------------|-----------------|
329
+ | 0 | `{prefix}-0` |
330
+ | 1-6 | `{prefix}-2` (XS) |
331
+ | 7-12 | `{prefix}-4` (SM) |
332
+ | 13-20 | `{prefix}-6` (MD) |
333
+ | 21-28 | `{prefix}-8` (LG) |
334
+ | 29-40 | `{prefix}-12` (XL) |
335
+ | 41+ | `{prefix}-16` (2XL) |
336
+
337
+ Where `{prefix}` is `mt`, `mb`, `pt`, or `pb`.
338
+
339
+ #### Hero Mapping
340
+
341
+ | Figma Property | PayloadCMS Field | Mapping Rule |
342
+ |---------------|-----------------|--------------|
343
+ | Large heading text node | `content.richText` | Extract as Lexical heading |
344
+ | Body text node | `content.richText` | Extract as Lexical paragraph |
345
+ | Button instances | `content.buttonGroup[]` | Map each to linkGroup item |
346
+ | Background image fill | `image.image` | Export and upload to Media |
347
+ | Component variant "impact" = "high" | `settings.type` = `'highImpact'` | Variant mapping |
348
+ | Component variant "impact" = "medium" | `settings.type` = `'mediumImpact'` | Variant mapping |
349
+ | Component variant "impact" = "low" | `settings.type` = `'lowImpact'` | Variant mapping |
350
+ | Frame height >= 400px | `settings.type` = `'highImpact'` | Size heuristic |
351
+ | Frame height 250-399px | `settings.type` = `'mediumImpact'` | Size heuristic |
352
+ | Frame height < 250px | `settings.type` = `'lowImpact'` | Size heuristic |
353
+
354
+ #### Card Mapping
355
+
356
+ | Figma Property | PayloadCMS Field | Mapping Rule |
357
+ |---------------|-----------------|--------------|
358
+ | Text content (heading + body) | `content.richText` | Extract as Lexical |
359
+ | Image child/fill | `image.image` | Export and upload |
360
+ | Button/CTA child text | `cta.label` | Extract text |
361
+ | Button links to page | `cta.ctaAction` = `'internal'`, `cta.internalLink` | If detectable |
362
+ | Button links to URL | `cta.ctaAction` = `'external'`, `cta.externalLink` | If detectable |
363
+ | CTA wraps entire card | `cta.layout` = `'wrap-all'` | If entire card is clickable |
364
+ | CTA wraps text area only | `cta.layout` = `'wrap-text-description'` | Default |
365
+
366
+ #### Button Mapping
367
+
368
+ | Figma Property | PayloadCMS Field | Mapping Rule |
369
+ |---------------|-----------------|--------------|
370
+ | Button text | `cta.label` | Extract text content |
371
+ | Links to internal page | `cta.type` = `'link'`, `cta.link.linkType` = `'internal'` | If detectable |
372
+ | Links to external URL | `cta.type` = `'link'`, `cta.link.linkType` = `'external'` | If URL present |
373
+ | Opens modal (no link) | `cta.type` = `'button'`, `cta.action.actionType` = `'modal'` | If no navigation |
374
+ | Scrolls to section | `cta.type` = `'button'`, `cta.action.actionType` = `'scroll'` | If anchor link |
375
+
376
+ #### Stats Mapping
377
+
378
+ | Figma Property | PayloadCMS Field | Mapping Rule |
379
+ |---------------|-----------------|--------------|
380
+ | Large number/metric text | `content.emphasis` | Extract text (largest font size) |
381
+ | Title text | `content.title` | Extract text (second largest) |
382
+ | Description text | `content.richText` | Extract as Lexical paragraph |
383
+ | Source/citation text | `content.source` | Extract text (smallest font size, often italic) |
384
+
385
+ #### Testimonial Mapping
386
+
387
+ | Figma Property | PayloadCMS Field | Mapping Rule |
388
+ |---------------|-----------------|--------------|
389
+ | Quote text (italic/body) | `content.richText` | Extract as Lexical |
390
+ | Author name text | `content.author` | Extract text (bold, below quote) |
391
+ | Author title/role text | `content.authorDescription` | Extract text (below author name) |
392
+ | Avatar image (small, circular) | `image.image` | Export and upload |
393
+
394
+ ---
395
+
396
+ ### 4. Container Nesting Rules
397
+
398
+ Figma designs naturally have deeply nested Auto Layout frames. PayloadCMS has a maximum nesting depth of 2 for Container blocks. These rules determine when to nest and when to flatten.
399
+
400
+ #### Nesting Decision Tree
401
+
402
+ ```
403
+ For each Auto Layout frame in the Figma hierarchy:
404
+
405
+ 1. IS IT THE TOP-LEVEL PAGE FRAME?
406
+ YES → Its children become root layout.blocks entries
407
+ (Do not create a Container for the page frame itself)
408
+
409
+ 2. DOES IT CONTAIN ONLY ONE CHILD THAT MAPS TO A BLOCK?
410
+ YES → Flatten: emit the child block directly, skip the wrapper
411
+ Rationale: A Container with one child adds no structural value
412
+
413
+ 3. DOES IT CONTAIN MULTIPLE CHILDREN OF MIXED TYPES?
414
+ YES → Create a Container block
415
+ - Set layout/alignment/gap from the frame's Auto Layout props
416
+ - Recursively map children as the Container's blocks
417
+ Rationale: This is the primary use case for Container
418
+
419
+ 4. DOES IT CONTAIN CHILDREN THAT ARE ALL THE SAME TYPE?
420
+ YES → Consider alternatives:
421
+ - All Cards → Container(row) with Card children
422
+ - All Stats → Container(row) with Stats children
423
+ - All items with images → Carousel (if horizontal scroll pattern)
424
+ Rationale: Homogeneous children often indicate a gallery or grid
425
+
426
+ 5. IS THE NESTING DEPTH > 2?
427
+ YES → Flatten intermediate wrappers
428
+ - Keep the outermost Container and the innermost content blocks
429
+ - Collapse intermediate Auto Layout frames by merging their
430
+ layout properties into the nearest preserved Container
431
+ Rationale: PayloadCMS supports Container > NestedContainer (depth 2)
432
+ but not deeper nesting
433
+ ```
434
+
435
+ #### Flattening Strategy
436
+
437
+ When Figma has 3+ levels of nesting:
438
+
439
+ ```
440
+ Figma:
441
+ Frame A (vertical, padding 32px)
442
+ Frame B (horizontal, gap 24px)
443
+ Frame C (vertical, gap 16px)
444
+ Text "Hello"
445
+ Text "World"
446
+ Frame D (vertical, gap 16px)
447
+ Image
448
+ Text "Caption"
449
+
450
+ PayloadCMS (flattened):
451
+ Container (layout: row, gap: gap-6, paddingTop: pt-8, paddingBottom: pb-8)
452
+ Container (layout: col, gap: gap-4)
453
+ RichText ("Hello\nWorld")
454
+ Container (layout: col, gap: gap-4)
455
+ Media (image)
456
+ RichText ("Caption")
457
+ ```
458
+
459
+ Frame A and Frame B are merged: A's padding becomes LayoutMeta on the outer Container, B's row layout and gap become the outer Container's settings. Frame C and Frame D each become NestedContainers.
460
+
461
+ #### When to Flatten vs Preserve
462
+
463
+ | Scenario | Action | Reason |
464
+ |----------|--------|--------|
465
+ | Single child in Auto Layout wrapper | Flatten | No structural purpose |
466
+ | Two levels of nesting | Preserve both | Within PayloadCMS limit |
467
+ | Three+ levels of nesting | Merge intermediate levels | Exceeds Container depth limit |
468
+ | Auto Layout frame with only padding (no layout purpose) | Apply padding as LayoutMeta to child | Wrapper adds only spacing |
469
+ | Frame groups items for alignment only | Preserve if it changes layout direction | Container settings carry alignment |
470
+
471
+ #### Group Detection
472
+
473
+ When multiple sibling elements share a common structural container in Figma but would be top-level blocks in PayloadCMS, detect the grouping pattern:
474
+
475
+ ```
476
+ Figma: Section frame (vertical)
477
+ ├── Heading text
478
+ ├── Description text
479
+ └── Cards row (horizontal)
480
+ ├── Card 1
481
+ ├── Card 2
482
+ └── Card 3
483
+
484
+ PayloadCMS:
485
+ Container (layout: col)
486
+ ├── RichText (heading + description)
487
+ └── Container (layout: row)
488
+ ├── Card 1
489
+ ├── Card 2
490
+ └── Card 3
491
+ ```
492
+
493
+ The "Cards row" frame becomes a nested Container because it changes the layout direction from the parent's column to row.
494
+
495
+ ---
496
+
497
+ ### 5. Design Token Bridge
498
+
499
+ Figma design tokens flow into PayloadCMS blocks through the `tokens.css` file. This section defines the extraction and mapping rules.
500
+
501
+ #### Token Flow Pipeline
502
+
503
+ ```
504
+ Figma Variables / Styles
505
+ |
506
+ v
507
+ [Extract] ──── Figma REST API (GET /v1/files/:key/variables)
508
+ | or Plugin API (figma.variables.getLocalVariables())
509
+ v
510
+ [Name] ─────── Apply --token-{category}-{name} naming
511
+ | See design-tokens.md for naming algorithm
512
+ v
513
+ [Render] ────── Generate tokens.css with :root declarations
514
+ |
515
+ v
516
+ [Consume] ──── Block CSS Modules reference via var(--token-*)
517
+ ```
518
+
519
+ #### Category Mapping
520
+
521
+ | Figma Source | Token Category | CSS Custom Property | Example |
522
+ |-------------|---------------|--------------------|---------|
523
+ | Color styles / color variables | `--token-color-*` | `--token-color-primary` | `#3b82f6` |
524
+ | Spacing variables | `--token-spacing-*` | `--token-spacing-md` | `16px` |
525
+ | Font size variables | `--token-font-size-*` | `--token-font-size-lg` | `20px` |
526
+ | Font family styles | `--token-font-*` | `--token-font-family` | `system-ui, ...` |
527
+ | Line height variables | `--token-line-height-*` | `--token-line-height-base` | `32px` |
528
+ | Border radius variables | `--token-radius-*` | `--token-radius-lg` | `12px` |
529
+ | Effect styles (shadows) | `--token-shadow-*` | `--token-shadow-md` | `0 4px 6px ...` |
530
+ | Transition tokens | `--token-transition-*` | `--token-transition-fast` | `150ms ease` |
531
+
532
+ #### Figma Value to Token Resolution
533
+
534
+ When mapping a Figma design to PayloadCMS blocks, extracted values must be resolved to their token equivalents:
535
+
536
+ 1. **Check for variable binding** -- If a Figma node property is bound to a variable (e.g., `fills[0].color` bound to `primitives/blue-500`), resolve the variable to its token name.
537
+
538
+ 2. **Check for style reference** -- If the node references a Figma style (color style, text style, effect style), map the style name to the token name.
539
+
540
+ 3. **Check for value match** -- If the raw value matches a known token value (e.g., `#3b82f6` matches `--token-color-primary`), use the token reference.
541
+
542
+ 4. **No match** -- If the value does not match any token, use the raw value with a fallback. Flag for potential token promotion.
543
+
544
+ #### Gap Token Mapping
545
+
546
+ The Container block stores gap as Tailwind classes, not token references. The mapping from Figma to Tailwind gap classes is:
547
+
548
+ | Figma itemSpacing | Tailwind Gap Class | Equivalent rem |
549
+ |-------------------|-------------------|----------------|
550
+ | 0px | `gap-0` | 0 |
551
+ | 4px | `gap-1` | 0.25rem |
552
+ | 8px | `gap-2` | 0.5rem |
553
+ | 16px | `gap-4` | 1rem |
554
+ | 24px | `gap-6` | 1.5rem |
555
+ | 32px | `gap-8` | 2rem |
556
+ | 48px | `gap-12` | 3rem |
557
+ | 64px | `gap-16` | 4rem |
558
+
559
+ Non-exact values snap to the nearest option (see Section 3 snapping rules).
560
+
561
+ #### LayoutMeta Token Mapping
562
+
563
+ LayoutMeta spacing values are also Tailwind classes. The mapping from Figma padding/margin:
564
+
565
+ | Figma padding (px) | LayoutMeta value | Tailwind rem |
566
+ |--------------------|--------------------|-------------|
567
+ | 0 | `pt-0` / `pb-0` / `mt-0` / `mb-0` | 0 |
568
+ | 8px | `pt-2` / `pb-2` / etc. | 0.5rem |
569
+ | 16px | `pt-4` / `pb-4` / etc. | 1rem |
570
+ | 24px | `pt-6` / `pb-6` / etc. | 1.5rem |
571
+ | 32px | `pt-8` / `pb-8` / etc. | 2rem |
572
+ | 48px | `pt-12` / `pb-12` / etc. | 3rem |
573
+ | 64px | `pt-16` / `pb-16` / etc. | 4rem |
574
+
575
+ ---
576
+
577
+ ### 6. Variant Mapping (CVA Pattern)
578
+
579
+ Figma component variants map to PayloadCMS block field variants, which are then rendered via CSS Module modifier classes (following the CVA -- Class Variance Authority -- pattern).
580
+
581
+ #### Figma Variants to Block Fields
582
+
583
+ | Figma Variant Property | PayloadCMS Block | Field | Field Type | Options |
584
+ |-----------------------|-----------------|-------|-----------|---------|
585
+ | `impact` (high/medium/low) | Hero | `settings.type` | radio | highImpact, mediumImpact, lowImpact |
586
+ | `size` (fixed/responsive) | Video | `settings.videoSize` | radio | fixed, responsive |
587
+ | `state` (open/closed) | Accordion | `settings.defaultState` | radio | open, closed |
588
+ | `ctaType` (button/link) | Button | `cta.type` | radio | button, link |
589
+ | `appearance` (default/outline) | Link (field factory) | `link.appearance` | select | default, outline |
590
+
591
+ #### Variant Detection Rules
592
+
593
+ 1. **Named variant properties** -- If a Figma component set has variant properties (e.g., `impact=high`, `size=large`), map each property to the corresponding block select/radio field.
594
+
595
+ 2. **Boolean variant properties** -- Figma boolean properties (e.g., `hasImage=true`) map to block checkbox fields or conditional field visibility.
596
+
597
+ 3. **Instance swap properties** -- Figma instance swap slots (e.g., `icon=<IconComponent>`) map to upload or relationship fields.
598
+
599
+ #### CVA Rendering Pattern
600
+
601
+ Block renderers use variant values to select CSS Module classes:
602
+
603
+ ```typescript
604
+ // Hero renderer applies variant-based classes
605
+ const impactClass = {
606
+ highImpact: styles.heroContainerHigh, // min-height: 400px
607
+ mediumImpact: styles.heroContainerMedium, // min-height: 300px
608
+ lowImpact: styles.heroContainerLow, // min-height: 200px
609
+ }[block.settings.type]
610
+
611
+ return <div className={cn(styles.heroContainer, impactClass)}>
612
+ ```
613
+
614
+ The CSS Module defines the modifier classes:
615
+
616
+ ```css
617
+ .heroContainerHigh { min-height: 400px; }
618
+ .heroContainerMedium { min-height: 300px; }
619
+ .heroContainerLow { min-height: 200px; }
620
+ ```
621
+
622
+ This is the CVA pattern: the block field value selects the CSS class variant.
623
+
624
+ #### Mapping Figma Variants When Names Do Not Match
625
+
626
+ When Figma variant property names differ from block field names, use this resolution order:
627
+
628
+ 1. **Exact match** -- Figma variant property name matches block field name (e.g., both called "type")
629
+ 2. **Semantic match** -- Figma "variant" or "style" maps to block "type"
630
+ 3. **Value match** -- Check if Figma variant values match block option values (e.g., Figma "High" matches block "highImpact")
631
+ 4. **Size heuristic** -- If no variant property exists, infer from dimensions (Hero height, Container width)
632
+
633
+ ---
634
+
635
+ ### 7. Instance and Slot Mapping
636
+
637
+ Figma component instances and instance swap properties map to block references and nested block content.
638
+
639
+ #### Component Instance to Nested Block
640
+
641
+ When a Figma component instance appears as a child of another component, it maps to a nested block within the parent's blocks field:
642
+
643
+ ```
644
+ Figma:
645
+ Hero Component
646
+ └── Button Instance (text: "Get Started")
647
+ └── Button Instance (text: "Learn More")
648
+
649
+ PayloadCMS:
650
+ Hero block
651
+ content.buttonGroup[0].link.label = "Get Started"
652
+ content.buttonGroup[1].link.label = "Learn More"
653
+ ```
654
+
655
+ For Container blocks, each child instance becomes a block entry in `layout.content.blocks`.
656
+
657
+ #### Instance Overrides to Field Values
658
+
659
+ Figma instance overrides (text overrides, fill overrides, visibility overrides) map to field values:
660
+
661
+ | Figma Override Type | PayloadCMS Mapping |
662
+ |--------------------|--------------------|
663
+ | Text override | Corresponding text or richText field value |
664
+ | Fill override | Token reference if color matches, otherwise CSS Module customization |
665
+ | Visibility override (hidden) | `settings.layoutMeta.hidden = true` |
666
+ | Component swap | Different block type in the blocks array |
667
+ | Nested instance override | Recursive field value mapping |
668
+
669
+ #### Slot Pattern
670
+
671
+ Figma instance swap properties (where a component exposes a slot for swapping child instances) map to block relationship or blocks fields:
672
+
673
+ ```
674
+ Figma Component: Card
675
+ Instance Swap Property: "icon" → accepts Icon components
676
+ Instance Swap Property: "ctaButton" → accepts Button components
677
+
678
+ PayloadCMS:
679
+ Card block:
680
+ image.image → Upload field (for icon slot)
681
+ cta → CTA group fields (for ctaButton slot)
682
+ ```
683
+
684
+ The key insight is that Figma's instance swap property is a visual composition mechanism, while PayloadCMS uses typed fields. The mapping must translate the swap slot into the appropriate field type.
685
+
686
+ ---
687
+
688
+ ### 8. Rich Text Extraction
689
+
690
+ Figma text nodes are converted to Lexical editor content for PayloadCMS richText fields.
691
+
692
+ #### Text Node to Lexical Mapping
693
+
694
+ ```
695
+ Figma TEXT Node
696
+ |
697
+ ├── Extract styledTextSegments
698
+ | Each segment: { characters, fontSize, fontWeight, italic, textDecoration, hyperlink }
699
+ |
700
+ v
701
+ [Determine Block Type]
702
+ fontSize >= 28px AND fontWeight >= 700 → Lexical HeadingNode (h1/h2)
703
+ fontSize >= 24px AND fontWeight >= 600 → Lexical HeadingNode (h2/h3)
704
+ fontSize >= 20px AND fontWeight >= 600 → Lexical HeadingNode (h3/h4)
705
+ Otherwise → Lexical ParagraphNode
706
+ |
707
+ v
708
+ [Map Text Formatting]
709
+ fontWeight >= 700 → Bold format
710
+ italic: true → Italic format
711
+ textDecoration: UNDERLINE → Underline format
712
+ textDecoration: STRIKETHROUGH → Strikethrough format
713
+ hyperlink defined → LinkNode wrapping text
714
+ |
715
+ v
716
+ [Assemble Lexical JSON]
717
+ ```
718
+
719
+ #### Heading Hierarchy Detection
720
+
721
+ When multiple text nodes exist in a component, determine heading levels from font size relationships:
722
+
723
+ ```
724
+ Text Nodes in Hero:
725
+ "Welcome to Our Platform" (fontSize: 48px, fontWeight: 700) → h1
726
+ "We help teams build faster" (fontSize: 20px, fontWeight: 400) → paragraph
727
+
728
+ Text Nodes in Card:
729
+ "Feature Title" (fontSize: 24px, fontWeight: 600) → h3
730
+ "Description text here" (fontSize: 16px, fontWeight: 400) → paragraph
731
+ ```
732
+
733
+ The rule: within a single block context, the largest font size becomes the highest heading level, and progressively smaller sizes become lower headings or paragraphs.
734
+
735
+ #### Lexical JSON Structure
736
+
737
+ PayloadCMS Lexical content follows this JSON structure:
738
+
739
+ ```json
740
+ {
741
+ "root": {
742
+ "type": "root",
743
+ "children": [
744
+ {
745
+ "type": "heading",
746
+ "tag": "h2",
747
+ "children": [
748
+ {
749
+ "type": "text",
750
+ "text": "Welcome to Our Platform",
751
+ "format": 1
752
+ }
753
+ ]
754
+ },
755
+ {
756
+ "type": "paragraph",
757
+ "children": [
758
+ {
759
+ "type": "text",
760
+ "text": "We help teams build ",
761
+ "format": 0
762
+ },
763
+ {
764
+ "type": "text",
765
+ "text": "faster",
766
+ "format": 3
767
+ }
768
+ ]
769
+ }
770
+ ]
771
+ }
772
+ }
773
+ ```
774
+
775
+ Format values are bitmasks:
776
+ - `0` = no formatting
777
+ - `1` = bold
778
+ - `2` = italic
779
+ - `3` = bold + italic
780
+ - `4` = strikethrough
781
+ - `8` = underline
782
+ - `16` = code
783
+
784
+ #### Multi-Node Aggregation
785
+
786
+ When a Figma component has multiple separate text nodes that should combine into a single richText field, aggregate them in visual order (top to bottom, left to right):
787
+
788
+ ```
789
+ Figma Card:
790
+ Text "Feature Title" (y: 120, fontSize: 24px)
791
+ Text "Description paragraph..." (y: 160, fontSize: 16px)
792
+
793
+ Lexical richText:
794
+ HeadingNode(h3, "Feature Title")
795
+ ParagraphNode("Description paragraph...")
796
+ ```
797
+
798
+ Sort text nodes by their Y position (top to bottom), then by X position for same-line text.
799
+
800
+ #### List Detection
801
+
802
+ Figma does not have native list support. Detect lists heuristically:
803
+
804
+ - **Bullet points** -- Text starting with "- ", "* ", or bullet character (U+2022)
805
+ - **Numbered lists** -- Text starting with "1.", "2.", etc.
806
+ - **Repeated icon + text pattern** -- Row of icon instance + text node, repeated vertically
807
+
808
+ Convert detected lists to Lexical ListNode with ListItemNode children.
809
+
810
+ ---
811
+
812
+ ### 9. Media Asset Pipeline
813
+
814
+ Figma images are extracted, exported, and uploaded to the PayloadCMS Media collection.
815
+
816
+ #### Extraction Steps
817
+
818
+ ```
819
+ Figma Image Fill or Image Node
820
+ |
821
+ v
822
+ [1. Identify] ─── Is it an image fill on a frame? Or a standalone image?
823
+ | Reference: design-to-code-assets.md for detection rules
824
+ v
825
+ [2. Export] ────── Use Figma REST API: GET /v1/images/:file_key
826
+ | Parameters: ids=[nodeId], format=png, scale=2
827
+ | Export at 2x for retina displays
828
+ v
829
+ [3. Upload] ───── POST to PayloadCMS Media collection
830
+ | Include alt text from:
831
+ | - Figma node name (fallback)
832
+ | - Nearest text sibling (better)
833
+ | - Explicit alt text property if set
834
+ v
835
+ [4. Reference] ── Store the Media document ID in the block's
836
+ image.image field (relationship to media collection)
837
+ ```
838
+
839
+ #### Image Export Settings
840
+
841
+ | Setting | Value | Reason |
842
+ |---------|-------|--------|
843
+ | Format | PNG | Lossless, PayloadCMS converts to WebP |
844
+ | Scale | 2 | Retina display support |
845
+ | Fallback format | SVG | For vector-only content |
846
+
847
+ PayloadCMS automatically generates the `og` image size (1200x630 WebP) from uploaded images.
848
+
849
+ #### SVG Handling Decision
850
+
851
+ | Condition | Action |
852
+ |-----------|--------|
853
+ | Vector content (icons, logos, illustrations) | Export as SVG, upload to Media |
854
+ | Photographic content | Export as PNG at 2x |
855
+ | Mixed vector + photo | Export as PNG (vectors rasterize cleanly) |
856
+ | Small icon (< 24px) | Consider inline SVG in CSS Module instead of Media upload |
857
+
858
+ For inline SVG vs upload decisions, reference `design-to-code-assets.md` Section 4 (CSS vs SVG decision tree).
859
+
860
+ #### Alt Text Generation
861
+
862
+ The Media collection requires an `alt` field. Generate alt text using this priority:
863
+
864
+ 1. **Figma node name** if descriptive (e.g., "Team photo", "Logo icon")
865
+ 2. **Parent component context** (e.g., image in Card titled "AI Feature" → "AI Feature illustration")
866
+ 3. **Generic fallback** based on position (e.g., "Hero background image", "Card thumbnail")
867
+ 4. **Empty string** for purely decorative images (with `role="presentation"` in HTML)
868
+
869
+ ---
870
+
871
+ ### 10. Responsive Considerations
872
+
873
+ Figma responsive variants map to PayloadCMS Container width settings and CSS Module media queries.
874
+
875
+ #### Figma Responsive Patterns to PayloadCMS
876
+
877
+ | Figma Pattern | PayloadCMS Mapping |
878
+ |--------------|-------------------|
879
+ | Full-width frame (fills viewport) | Container width = `full` |
880
+ | Max-width ~1200-1400px with auto margins | Container width = `wide` |
881
+ | Max-width ~600-800px with auto margins | Container width = `narrow` |
882
+ | Horizontal layout at desktop, vertical at mobile | Container layout = `row` (CSS Module handles responsive override) |
883
+ | Different gap at mobile vs desktop | Container gap = desktop value (CSS Module responsive rules adjust mobile) |
884
+
885
+ #### Responsive Frame Detection
886
+
887
+ When Figma contains multiple responsive variants of the same frame (e.g., "Hero/Desktop", "Hero/Mobile"):
888
+
889
+ 1. Use the desktop variant as the primary mapping source
890
+ 2. Note layout direction changes between variants
891
+ 3. The CSS Module media queries handle responsive behavior -- PayloadCMS blocks store the desktop configuration
892
+
893
+ #### Container Width to CSS
894
+
895
+ | PayloadCMS Width | CSS Module Class | CSS |
896
+ |-----------------|-----------------|-----|
897
+ | `full` | `.container` | No max-width constraint |
898
+ | `wide` | `.containerWide` | `max-width: 1400px; margin: 0 auto;` |
899
+ | `narrow` | `.containerNarrow` | `max-width: 800px; margin: 0 auto;` |
900
+
901
+ #### Mobile-First CSS Module Pattern
902
+
903
+ Block CSS Modules follow a mobile-first responsive approach:
904
+
905
+ ```css
906
+ /* Base (mobile) */
907
+ .heroContainer {
908
+ padding: var(--token-spacing-lg, 24px);
909
+ min-height: 200px;
910
+ }
911
+
912
+ /* Tablet */
913
+ @media (min-width: 768px) {
914
+ .heroContainer {
915
+ padding: var(--token-spacing-2xl, 48px);
916
+ min-height: 300px;
917
+ }
918
+ }
919
+
920
+ /* Desktop */
921
+ @media (min-width: 1024px) {
922
+ .heroContainer {
923
+ padding: var(--token-spacing-3xl, 64px);
924
+ min-height: 400px;
925
+ }
926
+ }
927
+ ```
928
+
929
+ When Figma Variables use breakpoint modes, the tokens themselves change at breakpoints (see `design-tokens-variables.md`), and blocks automatically adapt without per-block media queries.
930
+
931
+ ---
932
+
933
+ ### 11. Complete Mapping Example
934
+
935
+ This section walks through mapping a complete Figma page to a PayloadCMS block tree.
936
+
937
+ #### Figma Design
938
+
939
+ A landing page with three sections:
940
+
941
+ ```
942
+ Page Frame (1440 x 2400, vertical Auto Layout)
943
+ ├── Hero Frame (1440 x 600)
944
+ │ ├── Background Image (1440 x 600, image fill)
945
+ │ ├── Overlay (1440 x 600, black 40% opacity)
946
+ │ ├── Content Frame (800 x auto, vertical, gap 24px)
947
+ │ │ ├── Text "Build Better Products" (48px, bold, white)
948
+ │ │ ├── Text "Our platform helps teams..." (20px, regular, white)
949
+ │ │ └── Buttons Frame (horizontal, gap 12px)
950
+ │ │ ├── Button Instance "Get Started" (primary fill)
951
+ │ │ └── Button Instance "Watch Demo" (outline)
952
+ │ └── (padding: 64px 32px)
953
+
954
+ ├── Features Section (1440 x auto, vertical, gap 48px, padding 64px 32px)
955
+ │ ├── Section Header Frame (vertical, gap 16px, center-aligned)
956
+ │ │ ├── Text "Features" (32px, bold)
957
+ │ │ └── Text "Everything you need" (18px, regular)
958
+ │ └── Cards Row (horizontal, gap 24px, max-width 1200px)
959
+ │ ├── Card Instance (360 x auto)
960
+ │ │ ├── Image (360 x 200)
961
+ │ │ ├── Text "Fast Builds" (24px, semibold)
962
+ │ │ ├── Text "Deploy in seconds..." (16px, regular)
963
+ │ │ └── Button "Learn More"
964
+ │ ├── Card Instance (360 x auto)
965
+ │ │ ├── Image (360 x 200)
966
+ │ │ ├── Text "Team Collab" (24px, semibold)
967
+ │ │ ├── Text "Work together..." (16px, regular)
968
+ │ │ └── Button "Learn More"
969
+ │ └── Card Instance (360 x auto)
970
+ │ ├── Image (360 x 200)
971
+ │ ├── Text "Analytics" (24px, semibold)
972
+ │ ├── Text "Track metrics..." (16px, regular)
973
+ │ └── Button "Learn More"
974
+
975
+ └── CTA Section (1440 x auto, horizontal, gap 32px, padding 48px)
976
+ ├── Content Frame (vertical, gap 12px)
977
+ │ ├── Text "Ready to get started?" (28px, bold, white)
978
+ │ └── Text "Join thousands of teams..." (16px, regular, white)
979
+ └── Image (400 x 300)
980
+ └── (background: #3b82f6)
981
+ ```
982
+
983
+ #### Mapping Result
984
+
985
+ ```json
986
+ {
987
+ "layout": {
988
+ "blocks": [
989
+ {
990
+ "blockType": "hero",
991
+ "content": {
992
+ "richText": {
993
+ "root": {
994
+ "type": "root",
995
+ "children": [
996
+ {
997
+ "type": "heading",
998
+ "tag": "h1",
999
+ "children": [{ "type": "text", "text": "Build Better Products", "format": 1 }]
1000
+ },
1001
+ {
1002
+ "type": "paragraph",
1003
+ "children": [{ "type": "text", "text": "Our platform helps teams ship faster with confidence.", "format": 0 }]
1004
+ }
1005
+ ]
1006
+ }
1007
+ },
1008
+ "buttonGroup": [
1009
+ {
1010
+ "link": {
1011
+ "type": "custom",
1012
+ "url": "#get-started",
1013
+ "label": "Get Started",
1014
+ "newTab": false
1015
+ }
1016
+ },
1017
+ {
1018
+ "link": {
1019
+ "type": "custom",
1020
+ "url": "#demo",
1021
+ "label": "Watch Demo",
1022
+ "newTab": false
1023
+ }
1024
+ }
1025
+ ]
1026
+ },
1027
+ "image": {
1028
+ "image": "media-id-hero-bg"
1029
+ },
1030
+ "settings": {
1031
+ "type": "highImpact",
1032
+ "className": "",
1033
+ "layoutMeta": {
1034
+ "marginTop": "mt-0",
1035
+ "marginBottom": "mb-0",
1036
+ "paddingTop": "pt-0",
1037
+ "paddingBottom": "pb-0",
1038
+ "hidden": false
1039
+ }
1040
+ }
1041
+ },
1042
+ {
1043
+ "blockType": "container",
1044
+ "layout": {
1045
+ "content": {
1046
+ "blocks": [
1047
+ {
1048
+ "blockType": "richText",
1049
+ "content": {
1050
+ "richText": {
1051
+ "root": {
1052
+ "type": "root",
1053
+ "children": [
1054
+ {
1055
+ "type": "heading",
1056
+ "tag": "h2",
1057
+ "children": [{ "type": "text", "text": "Features", "format": 1 }]
1058
+ },
1059
+ {
1060
+ "type": "paragraph",
1061
+ "children": [{ "type": "text", "text": "Everything you need to build better products", "format": 0 }]
1062
+ }
1063
+ ]
1064
+ }
1065
+ }
1066
+ },
1067
+ "settings": { "className": "", "layoutMeta": { "marginTop": "mt-0", "marginBottom": "mb-0", "paddingTop": "pt-0", "paddingBottom": "pb-0", "hidden": false } }
1068
+ },
1069
+ {
1070
+ "blockType": "container",
1071
+ "layout": {
1072
+ "content": {
1073
+ "blocks": [
1074
+ {
1075
+ "blockType": "card",
1076
+ "content": {
1077
+ "richText": {
1078
+ "root": {
1079
+ "type": "root",
1080
+ "children": [
1081
+ { "type": "heading", "tag": "h3", "children": [{ "type": "text", "text": "Fast Builds", "format": 1 }] },
1082
+ { "type": "paragraph", "children": [{ "type": "text", "text": "Deploy in seconds with our streamlined pipeline.", "format": 0 }] }
1083
+ ]
1084
+ }
1085
+ }
1086
+ },
1087
+ "cta": { "label": "Learn More", "ctaAction": "external", "externalLink": "#", "layout": "wrap-link-button" },
1088
+ "image": { "image": "media-id-card-1" },
1089
+ "settings": { "className": "", "layoutMeta": { "marginTop": "mt-0", "marginBottom": "mb-0", "paddingTop": "pt-0", "paddingBottom": "pb-0", "hidden": false } }
1090
+ },
1091
+ {
1092
+ "blockType": "card",
1093
+ "content": {
1094
+ "richText": {
1095
+ "root": {
1096
+ "type": "root",
1097
+ "children": [
1098
+ { "type": "heading", "tag": "h3", "children": [{ "type": "text", "text": "Team Collab", "format": 1 }] },
1099
+ { "type": "paragraph", "children": [{ "type": "text", "text": "Work together in real-time with live editing.", "format": 0 }] }
1100
+ ]
1101
+ }
1102
+ }
1103
+ },
1104
+ "cta": { "label": "Learn More", "ctaAction": "external", "externalLink": "#", "layout": "wrap-link-button" },
1105
+ "image": { "image": "media-id-card-2" },
1106
+ "settings": { "className": "", "layoutMeta": { "marginTop": "mt-0", "marginBottom": "mb-0", "paddingTop": "pt-0", "paddingBottom": "pb-0", "hidden": false } }
1107
+ },
1108
+ {
1109
+ "blockType": "card",
1110
+ "content": {
1111
+ "richText": {
1112
+ "root": {
1113
+ "type": "root",
1114
+ "children": [
1115
+ { "type": "heading", "tag": "h3", "children": [{ "type": "text", "text": "Analytics", "format": 1 }] },
1116
+ { "type": "paragraph", "children": [{ "type": "text", "text": "Track every metric with real-time dashboards.", "format": 0 }] }
1117
+ ]
1118
+ }
1119
+ }
1120
+ },
1121
+ "cta": { "label": "Learn More", "ctaAction": "external", "externalLink": "#", "layout": "wrap-link-button" },
1122
+ "image": { "image": "media-id-card-3" },
1123
+ "settings": { "className": "", "layoutMeta": { "marginTop": "mt-0", "marginBottom": "mb-0", "paddingTop": "pt-0", "paddingBottom": "pb-0", "hidden": false } }
1124
+ }
1125
+ ]
1126
+ }
1127
+ },
1128
+ "settings": {
1129
+ "htmlTag": "div",
1130
+ "layout": "row",
1131
+ "width": "wide",
1132
+ "alignItems": "items-stretch",
1133
+ "justifyContent": "justify-center",
1134
+ "gap": "gap-6",
1135
+ "layoutMeta": { "marginTop": "mt-0", "marginBottom": "mb-0", "paddingTop": "pt-0", "paddingBottom": "pb-0", "hidden": false }
1136
+ }
1137
+ }
1138
+ ]
1139
+ }
1140
+ },
1141
+ "settings": {
1142
+ "htmlTag": "section",
1143
+ "layout": "col",
1144
+ "width": "full",
1145
+ "alignItems": "items-center",
1146
+ "justifyContent": "justify-start",
1147
+ "gap": "gap-12",
1148
+ "layoutMeta": { "marginTop": "mt-0", "marginBottom": "mb-0", "paddingTop": "pt-16", "paddingBottom": "pb-16", "hidden": false }
1149
+ }
1150
+ },
1151
+ {
1152
+ "blockType": "callToAction",
1153
+ "content": {
1154
+ "title": "Ready to get started?",
1155
+ "richText": {
1156
+ "root": {
1157
+ "type": "root",
1158
+ "children": [
1159
+ { "type": "paragraph", "children": [{ "type": "text", "text": "Join thousands of teams building better products.", "format": 0 }] }
1160
+ ]
1161
+ }
1162
+ }
1163
+ },
1164
+ "image": { "image": "media-id-cta" },
1165
+ "settings": {
1166
+ "className": "",
1167
+ "newTab": false,
1168
+ "layoutMeta": { "marginTop": "mt-0", "marginBottom": "mb-0", "paddingTop": "pt-0", "paddingBottom": "pb-0", "hidden": false }
1169
+ }
1170
+ }
1171
+ ]
1172
+ }
1173
+ }
1174
+ ```
1175
+
1176
+ #### Mapping Decisions Explained
1177
+
1178
+ 1. **Hero Section** -- Matched by Rule 1 (full-width, background image, large heading, buttons). Impact set to `highImpact` because the frame height is 600px (>= 400px threshold).
1179
+
1180
+ 2. **Features Section** -- The outer section frame becomes a Container (Rule 15, structural wrapper with mixed children). `htmlTag` set to `section` based on frame name. The inner "Section Header" is flattened into a RichText block (heading + description, no structural purpose). The "Cards Row" becomes a nested Container (layout: row) because it changes direction from the parent's column layout.
1181
+
1182
+ 3. **Card Instances** -- Each matched by Rule 2 (bounded width, image, text, CTA). The three Card blocks are children of the row Container.
1183
+
1184
+ 4. **CTA Section** -- Matched by Rule 10 (full-width, strong background color, title + description + image). Maps to CallToAction block rather than a Container with RichText, because the structure matches the CTA block's fields exactly.
1185
+
1186
+ 5. **Nesting depth** -- The result has 2 levels: outer Container (section) > inner Container (cards row) > Card blocks. This is within the PayloadCMS depth limit.
1187
+
1188
+ ---
1189
+
1190
+ ## Cross-References
1191
+
1192
+ - **`payload-blocks.md`** -- Complete block catalog, field definitions, Container settings, and Lexical configuration. The mapping tables in this module reference the exact field names and types defined there.
1193
+
1194
+ - **`payload-visual-builder.md`** -- Visual Builder plugin architecture for editing mapped blocks. Defines the editing UX for blocks produced by this mapping pipeline.
1195
+
1196
+ - **`design-to-code-layout.md`** -- Auto Layout to Flexbox mapping rules. The Container mapping in Section 3 directly applies the layout interpretation rules from this module (sizing modes, axis alignment, gap, wrap, constraints).
1197
+
1198
+ - **`design-to-code-visual.md`** -- Visual property extraction (fills, strokes, effects, gradients, opacity). Used during token bridge (Section 5) to determine which visual values map to `--token-*` properties.
1199
+
1200
+ - **`design-to-code-typography.md`** -- Font mapping and text style extraction. Rich text extraction (Section 8) uses the styled segment and heading hierarchy rules from this module.
1201
+
1202
+ - **`design-to-code-assets.md`** -- Image and vector export rules. The media asset pipeline (Section 9) references the SVG vs CSS decision tree and image deduplication patterns.
1203
+
1204
+ - **`design-to-code-semantic.md`** -- Semantic HTML element selection. The Container block's `htmlTag` mapping and the BEM naming patterns used in CSS Modules follow the conventions defined here.
1205
+
1206
+ - **`css-strategy.md`** -- Three-layer CSS architecture. Blocks use Layer 1 (Tailwind) for Container settings, Layer 2 (tokens) for design values, and Layer 3 (CSS Modules) for visual skin -- as defined in this strategy module.
1207
+
1208
+ - **`design-tokens.md`** -- Token extraction pipeline and naming algorithm. The design token bridge (Section 5) follows the extraction, naming, and rendering pipeline from this module.
1209
+
1210
+ - **`design-tokens-variables.md`** -- Figma Variables to CSS custom property resolution. Variable-bound properties are resolved through the chain documented here before mapping to `--token-*` values.