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.
- package/README.md +133 -0
- package/bin/install.js +328 -0
- package/knowledge/README.md +62 -0
- package/knowledge/css-strategy.md +973 -0
- package/knowledge/design-to-code-assets.md +855 -0
- package/knowledge/design-to-code-layout.md +929 -0
- package/knowledge/design-to-code-semantic.md +1085 -0
- package/knowledge/design-to-code-typography.md +1003 -0
- package/knowledge/design-to-code-visual.md +1145 -0
- package/knowledge/design-tokens-variables.md +1261 -0
- package/knowledge/design-tokens.md +960 -0
- package/knowledge/figma-api-devmode.md +894 -0
- package/knowledge/figma-api-plugin.md +920 -0
- package/knowledge/figma-api-rest.md +742 -0
- package/knowledge/figma-api-variables.md +848 -0
- package/knowledge/figma-api-webhooks.md +876 -0
- package/knowledge/payload-blocks.md +1184 -0
- package/knowledge/payload-figma-mapping.md +1210 -0
- package/knowledge/payload-visual-builder.md +1004 -0
- package/knowledge/plugin-architecture.md +1176 -0
- package/knowledge/plugin-best-practices.md +1206 -0
- package/knowledge/plugin-codegen.md +1313 -0
- package/package.json +31 -0
- package/skills/README.md +103 -0
- package/skills/audit-plugin/SKILL.md +244 -0
- package/skills/build-codegen-plugin/SKILL.md +279 -0
- package/skills/build-importer/SKILL.md +320 -0
- package/skills/build-plugin/SKILL.md +199 -0
- package/skills/build-token-pipeline/SKILL.md +363 -0
- package/skills/ref-html/SKILL.md +290 -0
- package/skills/ref-layout/SKILL.md +150 -0
- package/skills/ref-payload-block/SKILL.md +415 -0
- package/skills/ref-react/SKILL.md +222 -0
- package/skills/ref-tokens/SKILL.md +347 -0
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
# Vector Container Detection & Asset Management
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Authoritative reference for detecting which Figma nodes should be exported as image assets (SVG, PNG, JPG) versus rendered as CSS, and for managing the full asset pipeline: vector container detection, CSS-renderable shape identification, image fill handling, asset deduplication, and export format selection. Encodes production-proven heuristics that prevent attempting to render complex vector geometry as CSS divs while avoiding unnecessary asset exports for simple shapes that CSS handles natively.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
Reference this module when you need to:
|
|
10
|
+
|
|
11
|
+
- Determine if a Figma frame/group should be exported as a single SVG (vector container detection)
|
|
12
|
+
- Decide whether a shape node (ELLIPSE, RECTANGLE, VECTOR, etc.) should be CSS or SVG
|
|
13
|
+
- Handle nodes with IMAGE fills (photos, textures, backgrounds)
|
|
14
|
+
- Map Figma `scaleMode` (FILL, FIT, CROP, TILE) to CSS `object-fit` / `background-size`
|
|
15
|
+
- Build an asset map for deduplication based on `imageHash`
|
|
16
|
+
- Select the correct export format (SVG, PNG@2x, JPG) for different asset types
|
|
17
|
+
- Understand the interaction between Auto Layout containers and vector container detection
|
|
18
|
+
- Handle edge cases like BOOLEAN_OPERATION nodes, mixed content containers, and icon detection
|
|
19
|
+
- Generate `<img>` tags with correct `src` and `alt` attributes from asset data
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Content
|
|
24
|
+
|
|
25
|
+
### 1. Vector Container Detection (Critical Heuristic)
|
|
26
|
+
|
|
27
|
+
The most important decision in asset management is determining which containers should be exported as a single SVG unit versus traversed as HTML/CSS. A **vector container** is a frame or group whose visible children are all vector-compatible types and that contains at least one "true" vector child (not just simple CSS shapes).
|
|
28
|
+
|
|
29
|
+
#### Why This Matters
|
|
30
|
+
|
|
31
|
+
Without vector container detection:
|
|
32
|
+
- An icon made of 5 VECTOR paths would produce 5 individual `<div>` elements (broken)
|
|
33
|
+
- A logo with BOOLEAN_OPERATION children would attempt CSS rendering of complex geometry (impossible)
|
|
34
|
+
- An illustration frame would generate dozens of meaningless positioned divs
|
|
35
|
+
|
|
36
|
+
With vector container detection:
|
|
37
|
+
- The entire icon frame is exported as a single SVG file
|
|
38
|
+
- The logo renders as one `<img src="logo.svg">` element
|
|
39
|
+
- The illustration becomes a clean image reference
|
|
40
|
+
|
|
41
|
+
#### Vector-Compatible Types
|
|
42
|
+
|
|
43
|
+
These node types can exist inside a vector container:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const VECTOR_COMPATIBLE_TYPES = new Set([
|
|
47
|
+
'VECTOR', // Path-based vector shape
|
|
48
|
+
'BOOLEAN_OPERATION', // Union, Intersect, Subtract, Exclude of vectors
|
|
49
|
+
'STAR', // Star polygon
|
|
50
|
+
'POLYGON', // Regular polygon
|
|
51
|
+
'LINE', // Line segment
|
|
52
|
+
'ELLIPSE', // Circle or ellipse
|
|
53
|
+
'RECTANGLE', // Rectangle (may have rounded corners)
|
|
54
|
+
])
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Container Types
|
|
58
|
+
|
|
59
|
+
These node types can hold vector children:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const CONTAINER_TYPES = new Set([
|
|
63
|
+
'GROUP', // Figma group (no Auto Layout)
|
|
64
|
+
'FRAME', // Figma frame (may have Auto Layout)
|
|
65
|
+
'COMPONENT', // Component definition
|
|
66
|
+
'INSTANCE', // Component instance
|
|
67
|
+
])
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### Detection Algorithm
|
|
71
|
+
|
|
72
|
+
The detection is a multi-step process with several guard rails:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
isVectorContainer(node):
|
|
76
|
+
1. Must be a container type (GROUP, FRAME, COMPONENT, INSTANCE)
|
|
77
|
+
2. Must have children
|
|
78
|
+
3. Must have at least one visible child
|
|
79
|
+
4. Must have at least one "true" vector child:
|
|
80
|
+
- VECTOR, BOOLEAN_OPERATION, STAR, POLYGON, or LINE
|
|
81
|
+
- (ELLIPSE and RECTANGLE alone do NOT qualify -- they're CSS-renderable)
|
|
82
|
+
5. ALL visible children must be vector-compatible
|
|
83
|
+
6. If ANY child is not vector-compatible → NOT a vector container
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Key insight:** A frame containing only RECTANGLE and ELLIPSE children is NOT a vector container. Those shapes are CSS-renderable. The container must have at least one type that requires SVG (VECTOR, BOOLEAN_OPERATION, STAR, POLYGON, LINE).
|
|
87
|
+
|
|
88
|
+
#### Implementation
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
function isVectorContainer(node: SceneNode): boolean {
|
|
92
|
+
// Must be a container type with children
|
|
93
|
+
if (!CONTAINER_TYPES.has(node.type) || !('children' in node)) return false
|
|
94
|
+
|
|
95
|
+
const visibleChildren = (node as ChildrenMixin & SceneNode)
|
|
96
|
+
.children.filter(child => child.visible)
|
|
97
|
+
|
|
98
|
+
// Must have at least one visible child
|
|
99
|
+
if (visibleChildren.length === 0) return false
|
|
100
|
+
|
|
101
|
+
// Must have at least one TRUE vector child (not just CSS shapes)
|
|
102
|
+
const hasVectorChildren = visibleChildren.some(child =>
|
|
103
|
+
child.type === 'VECTOR' ||
|
|
104
|
+
child.type === 'BOOLEAN_OPERATION' ||
|
|
105
|
+
child.type === 'STAR' ||
|
|
106
|
+
child.type === 'POLYGON' ||
|
|
107
|
+
child.type === 'LINE'
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if (!hasVectorChildren) return false
|
|
111
|
+
|
|
112
|
+
// ALL children must be vector-compatible
|
|
113
|
+
return visibleChildren.every(child => isVectorCompatible(child))
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### Recursive Vector Compatibility
|
|
118
|
+
|
|
119
|
+
A container child (GROUP, FRAME) is vector-compatible if ALL of its own children are recursively vector-compatible:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
function isVectorCompatible(node: SceneNode): boolean {
|
|
123
|
+
// Direct vector types are always compatible
|
|
124
|
+
if (VECTOR_COMPATIBLE_TYPES.has(node.type)) return true
|
|
125
|
+
|
|
126
|
+
// Container types: check all children recursively
|
|
127
|
+
if (CONTAINER_TYPES.has(node.type) && 'children' in node) {
|
|
128
|
+
const container = node as ChildrenMixin & SceneNode
|
|
129
|
+
if (container.children.length === 0) return false // Empty = not vector
|
|
130
|
+
return container.children
|
|
131
|
+
.filter(child => child.visible)
|
|
132
|
+
.every(child => isVectorCompatible(child))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### 2. CSS-Renderable Shapes vs SVG Export
|
|
142
|
+
|
|
143
|
+
Not every shape node needs to be exported as an image. Simple geometric shapes can be rendered more efficiently as CSS.
|
|
144
|
+
|
|
145
|
+
#### Decision Matrix
|
|
146
|
+
|
|
147
|
+
| Node Type | CSS-Renderable? | SVG Export? | CSS Technique |
|
|
148
|
+
|-----------|----------------|-------------|---------------|
|
|
149
|
+
| `RECTANGLE` | Yes | Only if complex fills | `<div>` with `border-radius`, `background-color` |
|
|
150
|
+
| `ELLIPSE` | Yes (if circle) | If non-circular | `border-radius: 50%` on equal width/height |
|
|
151
|
+
| `VECTOR` | No | Always | Complex path geometry |
|
|
152
|
+
| `BOOLEAN_OPERATION` | No | Always | Union/Intersect/Subtract/Exclude results |
|
|
153
|
+
| `STAR` | No | Always | Multi-point star polygon |
|
|
154
|
+
| `POLYGON` | No | Always | Regular polygon (triangle, hexagon, etc.) |
|
|
155
|
+
| `LINE` | Partial | Usually | `border-top` or `<hr>` for simple lines |
|
|
156
|
+
|
|
157
|
+
#### Decision Tree (Pseudocode)
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
decideRendering(node):
|
|
161
|
+
IF node is TEXT:
|
|
162
|
+
→ Render as HTML text (NEVER export as image)
|
|
163
|
+
→ Exception: text with IMAGE fill = text with background image
|
|
164
|
+
|
|
165
|
+
IF node is a vector container (Section 1):
|
|
166
|
+
→ Export entire container as single SVG
|
|
167
|
+
|
|
168
|
+
IF node.type is VECTOR, BOOLEAN_OPERATION, STAR, POLYGON:
|
|
169
|
+
→ Export as SVG (single node)
|
|
170
|
+
|
|
171
|
+
IF node.type is LINE:
|
|
172
|
+
→ IF simple horizontal/vertical line with solid stroke:
|
|
173
|
+
→ CSS border or `<hr>`
|
|
174
|
+
→ ELSE: Export as SVG
|
|
175
|
+
|
|
176
|
+
IF node.type is ELLIPSE:
|
|
177
|
+
→ IF width === height (circle) AND solid fill only:
|
|
178
|
+
→ CSS `border-radius: 50%`
|
|
179
|
+
→ ELSE: Export as SVG (ellipse or complex fills)
|
|
180
|
+
|
|
181
|
+
IF node.type is RECTANGLE:
|
|
182
|
+
→ IF solid/gradient fill only:
|
|
183
|
+
→ CSS `<div>` with background and border-radius
|
|
184
|
+
→ IF has IMAGE fill:
|
|
185
|
+
→ Render as `<img>` or `background-image`
|
|
186
|
+
|
|
187
|
+
IF node is FRAME/GROUP with children:
|
|
188
|
+
→ Traverse children recursively (layout container, not asset)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### Circle Detection
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
if (node.type === 'ELLIPSE') {
|
|
195
|
+
if (Math.abs(node.bounds.width - node.bounds.height) < 1) {
|
|
196
|
+
styles.borderRadius = '50%' // Circle
|
|
197
|
+
}
|
|
198
|
+
// Non-circular ellipses need SVG
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
### 3. SVG Export via Figma API
|
|
205
|
+
|
|
206
|
+
When a node is identified for SVG export, the actual export happens via the Figma REST API image export endpoint or the Plugin API's `exportAsync`.
|
|
207
|
+
|
|
208
|
+
#### REST API Export
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
GET /v1/files/:file_key/images?ids=NODE_ID&format=svg
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Parameters:**
|
|
215
|
+
- `ids` -- Comma-separated node IDs (URL-encoded, colons as `%3A`)
|
|
216
|
+
- `format` -- `svg`, `png`, `jpg`, or `pdf`
|
|
217
|
+
- `svg_include_id` -- Include Figma node IDs in SVG (default: false)
|
|
218
|
+
- `svg_include_node_id` -- Include `data-node-id` attributes (default: false)
|
|
219
|
+
- `svg_simplify_stroke` -- Simplify stroke geometry (default: true)
|
|
220
|
+
|
|
221
|
+
**Response:**
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"err": null,
|
|
226
|
+
"images": {
|
|
227
|
+
"1:23": "https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/..."
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The response contains temporary URLs to the generated SVG files. Download these immediately as URLs expire.
|
|
233
|
+
|
|
234
|
+
#### Plugin API Export
|
|
235
|
+
|
|
236
|
+
Within a Figma plugin, use `exportAsync`:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
const svgBuffer = await node.exportAsync({
|
|
240
|
+
format: 'SVG',
|
|
241
|
+
svgIdAttribute: false,
|
|
242
|
+
})
|
|
243
|
+
const svgString = String.fromCharCode(...new Uint8Array(svgBuffer))
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### SVG Cleanup
|
|
247
|
+
|
|
248
|
+
Exported SVGs from Figma often contain unnecessary metadata. Consider cleaning:
|
|
249
|
+
|
|
250
|
+
- Remove `xmlns:xlink` if no xlink references
|
|
251
|
+
- Remove empty `<defs>` blocks
|
|
252
|
+
- Remove Figma-specific metadata comments
|
|
253
|
+
- Optimize with SVGO (external tool) for production
|
|
254
|
+
- Preserve `viewBox` attribute (critical for correct scaling)
|
|
255
|
+
- Keep `fill` and `stroke` attributes that define the visual appearance
|
|
256
|
+
|
|
257
|
+
#### Inline SVG vs External File
|
|
258
|
+
|
|
259
|
+
| Approach | Use When | Advantages |
|
|
260
|
+
|----------|----------|------------|
|
|
261
|
+
| Inline `<svg>` | Icons, small graphics, need CSS styling | Styleable via CSS, no extra request, can change colors |
|
|
262
|
+
| External `<img src="icon.svg">` | Illustrations, logos, larger graphics | Cacheable, cleaner HTML, simpler code |
|
|
263
|
+
| CSS `background-image: url(icon.svg)` | Decorative, repeated patterns | No HTML impact, easy positioning |
|
|
264
|
+
|
|
265
|
+
**General rule:** Icons < 2KB inline, everything else external.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
### 4. Image Fill Handling
|
|
270
|
+
|
|
271
|
+
Nodes with IMAGE fills contain raster image data referenced by an `imageHash`. These need to be exported and referenced correctly.
|
|
272
|
+
|
|
273
|
+
#### Detection
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
if ('fills' in node) {
|
|
277
|
+
const fills = node.fills
|
|
278
|
+
if (fills !== figma.mixed && Array.isArray(fills)) {
|
|
279
|
+
for (const fill of fills) {
|
|
280
|
+
if (fill.type === 'IMAGE' && fill.imageHash) {
|
|
281
|
+
asset.imageHash = fill.imageHash
|
|
282
|
+
asset.exportAs = 'PNG' // Default for image fills
|
|
283
|
+
break // Take first image fill
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### scaleMode to CSS Mapping
|
|
291
|
+
|
|
292
|
+
Figma's `scaleMode` on IMAGE fills controls how the image is sized within its container. This maps directly to CSS properties.
|
|
293
|
+
|
|
294
|
+
| Figma `scaleMode` | CSS `object-fit` | CSS `background-size` | Behavior |
|
|
295
|
+
|-------------------|------------------|----------------------|----------|
|
|
296
|
+
| `FILL` | `cover` | `cover` | Image covers entire container, may crop |
|
|
297
|
+
| `FIT` | `contain` | `contain` | Image fits inside container, may letterbox |
|
|
298
|
+
| `CROP` | `cover` + `object-position` | `cover` + `background-position` | Cropped to specific region |
|
|
299
|
+
| `TILE` | N/A | `repeat` | Image tiles to fill the container |
|
|
300
|
+
|
|
301
|
+
#### CSS Generation for Image Fills
|
|
302
|
+
|
|
303
|
+
**As `<img>` element** (node without children):
|
|
304
|
+
|
|
305
|
+
```css
|
|
306
|
+
.hero-image {
|
|
307
|
+
width: 400px;
|
|
308
|
+
height: 300px;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.hero-image img {
|
|
312
|
+
width: 100%;
|
|
313
|
+
height: 100%;
|
|
314
|
+
object-fit: cover; /* scaleMode: FILL */
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**As `background-image`** (node with children -- image is behind child content):
|
|
319
|
+
|
|
320
|
+
```css
|
|
321
|
+
.hero-section {
|
|
322
|
+
background-image: url('./assets/hero-bg.png');
|
|
323
|
+
background-size: cover; /* scaleMode: FILL */
|
|
324
|
+
background-position: center;
|
|
325
|
+
background-repeat: no-repeat;
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### CROP Mode with Position
|
|
330
|
+
|
|
331
|
+
When `scaleMode` is `CROP`, the image has specific crop bounds. Map these to `object-position`:
|
|
332
|
+
|
|
333
|
+
```css
|
|
334
|
+
.cropped-image img {
|
|
335
|
+
object-fit: cover;
|
|
336
|
+
object-position: 30% 20%; /* Based on crop offset */
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
#### TILE Mode
|
|
341
|
+
|
|
342
|
+
```css
|
|
343
|
+
.tiled-background {
|
|
344
|
+
background-image: url('./assets/pattern.png');
|
|
345
|
+
background-size: auto; /* Natural image size */
|
|
346
|
+
background-repeat: repeat; /* scaleMode: TILE */
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### Retina Export
|
|
351
|
+
|
|
352
|
+
Always export image fills at **2x scale** for crisp display on high-DPI screens:
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
GET /v1/files/:key/images?ids=NODE_ID&format=png&scale=2
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Or via Plugin API:
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
const pngBuffer = await node.exportAsync({
|
|
362
|
+
format: 'PNG',
|
|
363
|
+
constraint: { type: 'SCALE', value: 2 },
|
|
364
|
+
})
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### Nodes WITH Children vs WITHOUT Children
|
|
368
|
+
|
|
369
|
+
The rendering strategy differs based on whether the node has visible children:
|
|
370
|
+
|
|
371
|
+
| Has Children? | Image Fill Rendering | HTML Output |
|
|
372
|
+
|---------------|---------------------|-------------|
|
|
373
|
+
| No | `<img>` element with `src` attribute | `<img src="./assets/photo.png" alt="Photo">` |
|
|
374
|
+
| Yes | CSS `background-image` on the container | `<div style="background-image: url(...)">...children...</div>` |
|
|
375
|
+
|
|
376
|
+
A frame with an image fill AND text children is a common pattern for hero sections: the image is the background, and the text sits on top.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
### 5. Asset Map & Deduplication
|
|
381
|
+
|
|
382
|
+
When multiple nodes reference the same image (e.g., a profile photo used in several cards), the asset should be downloaded and stored only once.
|
|
383
|
+
|
|
384
|
+
#### Hash-Based Deduplication
|
|
385
|
+
|
|
386
|
+
Figma provides an `imageHash` for each IMAGE fill. This hash uniquely identifies the image content regardless of which node uses it.
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// Build asset map during extraction
|
|
390
|
+
const assetMap = new Map<string, { filename: string; url: string }>()
|
|
391
|
+
|
|
392
|
+
function registerAsset(nodeId: string, imageHash: string, nodeName: string): string {
|
|
393
|
+
// Check if we already have this image
|
|
394
|
+
if (assetMap.has(imageHash)) {
|
|
395
|
+
return assetMap.get(imageHash)!.filename
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// New image - create entry
|
|
399
|
+
const filename = sanitizeFilename(nodeName)
|
|
400
|
+
assetMap.set(imageHash, { filename, url: '' }) // URL filled later
|
|
401
|
+
return filename
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
#### Filename Sanitization
|
|
406
|
+
|
|
407
|
+
Node names from Figma may contain characters invalid for filenames:
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
function sanitizeFilename(name: string): string {
|
|
411
|
+
return name
|
|
412
|
+
.toLowerCase()
|
|
413
|
+
.replace(/\s+/g, '-') // Spaces to hyphens
|
|
414
|
+
.replace(/[^a-z0-9-_]/g, '') // Remove special characters
|
|
415
|
+
.replace(/-+/g, '-') // Collapse multiple hyphens
|
|
416
|
+
.replace(/^-|-$/g, '') // Trim leading/trailing hyphens
|
|
417
|
+
|| 'asset' // Fallback for empty names
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Examples:**
|
|
422
|
+
- `"Hero Image"` -> `hero-image`
|
|
423
|
+
- `"icon/arrow-right"` -> `iconarrow-right`
|
|
424
|
+
- `"Photo (2)"` -> `photo-2`
|
|
425
|
+
- `""` -> `asset`
|
|
426
|
+
|
|
427
|
+
#### Asset Map in HTML Generation
|
|
428
|
+
|
|
429
|
+
During HTML generation, the asset map provides `src` paths for `<img>` elements:
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
if (tag === 'img' && node.asset && node.asset.exportAs && options.assetMap) {
|
|
433
|
+
const filename = options.assetMap.get(node.id)
|
|
434
|
+
if (filename) {
|
|
435
|
+
attributes.src = `./assets/${filename}`
|
|
436
|
+
attributes.alt = node.name || ''
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Generated HTML:
|
|
442
|
+
|
|
443
|
+
```html
|
|
444
|
+
<img class="card__photo" src="./assets/hero-image.png" alt="Hero Image">
|
|
445
|
+
<img class="card__icon" src="./assets/arrow-right.svg" alt="arrow-right">
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
### 6. Multi-Factor Detection Heuristics
|
|
451
|
+
|
|
452
|
+
Beyond the basic vector container check, several nuanced cases require additional heuristics.
|
|
453
|
+
|
|
454
|
+
#### Auto Layout Overrides Vector Detection
|
|
455
|
+
|
|
456
|
+
**Rule:** A frame with Auto Layout (`layoutMode !== 'NONE'`) is **ALWAYS** a layout container, never a vector container, regardless of its children.
|
|
457
|
+
|
|
458
|
+
**Rationale:** Auto Layout implies structural/layout intent. A designer who enables Auto Layout is building a layout, not an illustration.
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
function hasAutoLayout(node: SceneNode): boolean {
|
|
462
|
+
if (node.type === 'FRAME' || node.type === 'COMPONENT' || node.type === 'INSTANCE') {
|
|
463
|
+
return (node as FrameNode).layoutMode !== 'NONE'
|
|
464
|
+
}
|
|
465
|
+
return false
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// In getContainerExportStrategy:
|
|
469
|
+
if (hasAutoLayout(node)) {
|
|
470
|
+
return 'none' // Always layout, never asset
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
#### Text Children Limit
|
|
475
|
+
|
|
476
|
+
A container with more than one TEXT child is a layout container, not a vector illustration. A single TEXT child is allowed (e.g., an icon with a label that gets flattened into the SVG).
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
const textChildren = visibleChildren.filter(child => child.type === 'TEXT')
|
|
480
|
+
if (textChildren.length > 1) {
|
|
481
|
+
return 'none' // Multiple text = layout container
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
When a single TEXT child is present, the text gets flattened before SVG export. The `hasTextToFlatten` flag is set on the AssetData:
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
interface AssetData {
|
|
489
|
+
// ...
|
|
490
|
+
isVectorContainer?: boolean
|
|
491
|
+
hasTextToFlatten?: boolean // True if container has text to flatten pre-export
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
#### Container Export Strategy
|
|
496
|
+
|
|
497
|
+
The full detection algorithm combines all heuristics into a three-way decision:
|
|
498
|
+
|
|
499
|
+
```
|
|
500
|
+
getContainerExportStrategy(node) → 'svg' | 'png' | 'none'
|
|
501
|
+
|
|
502
|
+
1. Not a container type? → 'none'
|
|
503
|
+
2. Has Auto Layout? → 'none' (always layout)
|
|
504
|
+
3. No visible children? → 'none'
|
|
505
|
+
4. No true vector children? → 'none' (just CSS shapes)
|
|
506
|
+
5. Multiple TEXT children? → 'none' (layout, not illustration)
|
|
507
|
+
6. Any non-vector-compatible child? → 'none' (mixed content)
|
|
508
|
+
7. Has IMAGE fills in children? → 'png' (rasterize vectors + images)
|
|
509
|
+
8. All checks pass → 'svg' (pure vector content)
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
#### Simple Shapes: CSS, Not Assets
|
|
513
|
+
|
|
514
|
+
| Scenario | Rendering |
|
|
515
|
+
|----------|-----------|
|
|
516
|
+
| Single RECTANGLE with solid fill | CSS `<div>` with `background-color` and `border-radius` |
|
|
517
|
+
| Single ELLIPSE with equal width/height | CSS `<div>` with `border-radius: 50%` |
|
|
518
|
+
| Frame with only RECTANGLE + ELLIPSE children | CSS (no true vectors present) |
|
|
519
|
+
| RECTANGLE with IMAGE fill | `<img>` element (the image is the content) |
|
|
520
|
+
|
|
521
|
+
#### Complex Shapes: Always SVG
|
|
522
|
+
|
|
523
|
+
| Scenario | Rendering |
|
|
524
|
+
|----------|-----------|
|
|
525
|
+
| Group of VECTOR paths forming an icon | Single SVG asset |
|
|
526
|
+
| BOOLEAN_OPERATION (any sub-type) | SVG (complex geometry) |
|
|
527
|
+
| STAR node | SVG (multi-point polygon) |
|
|
528
|
+
| Frame with VECTOR + ELLIPSE children | SVG (has true vector content) |
|
|
529
|
+
| INSTANCE of a vector component | SVG (export the instance) |
|
|
530
|
+
|
|
531
|
+
#### Icon Detection Heuristic
|
|
532
|
+
|
|
533
|
+
Small vector containers (typically <= 48x48 pixels) are likely icons:
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
function isLikelyIcon(node: SceneNode): boolean {
|
|
537
|
+
if (node.width <= 48 && node.height <= 48) {
|
|
538
|
+
if (node.type === 'VECTOR' || node.type === 'BOOLEAN_OPERATION') {
|
|
539
|
+
return true
|
|
540
|
+
}
|
|
541
|
+
if ('children' in node) {
|
|
542
|
+
return (node as ChildrenMixin & SceneNode).children.every(child =>
|
|
543
|
+
child.type === 'VECTOR' ||
|
|
544
|
+
child.type === 'BOOLEAN_OPERATION' ||
|
|
545
|
+
child.type === 'ELLIPSE' ||
|
|
546
|
+
child.type === 'RECTANGLE' ||
|
|
547
|
+
child.type === 'LINE'
|
|
548
|
+
)
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return false
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Icons are always exported as SVG for crisp scaling at any size.
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
### 7. Export Format Selection
|
|
560
|
+
|
|
561
|
+
Different asset types require different formats for optimal quality and file size.
|
|
562
|
+
|
|
563
|
+
#### Format Decision Matrix
|
|
564
|
+
|
|
565
|
+
| Content Type | Format | Scale | Rationale |
|
|
566
|
+
|-------------|--------|-------|-----------|
|
|
567
|
+
| Vector icons | SVG | N/A (scalable) | Infinitely scalable, small file size, CSS-styleable |
|
|
568
|
+
| Vector illustrations | SVG | N/A | Scalable, preserves sharp edges |
|
|
569
|
+
| Photos | PNG@2x | 2x | Lossless, crisp on retina displays |
|
|
570
|
+
| Large photos | JPG@2x | 2x | Lossy but much smaller file size |
|
|
571
|
+
| Complex gradients | PNG@2x | 2x | Preserves gradient fidelity |
|
|
572
|
+
| Icons with image fills | PNG@2x | 2x | Can't be SVG if contains raster data |
|
|
573
|
+
| Print assets | PDF | N/A | Rare in web context |
|
|
574
|
+
|
|
575
|
+
#### Format Selection Logic
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
function selectExportFormat(node: SceneNode, asset: AssetData): 'SVG' | 'PNG' | 'JPG' {
|
|
579
|
+
// Vectors always export as SVG
|
|
580
|
+
if (asset.isVector && !asset.imageHash) {
|
|
581
|
+
return 'SVG'
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Vector containers with no image content → SVG
|
|
585
|
+
if (asset.isVectorContainer && !hasImageContent(node)) {
|
|
586
|
+
return 'SVG'
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Image fills default to PNG
|
|
590
|
+
if (asset.imageHash) {
|
|
591
|
+
return 'PNG'
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Fallback
|
|
595
|
+
return 'PNG'
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
#### Figma Export Settings Override
|
|
600
|
+
|
|
601
|
+
When a designer has explicitly set export settings on a node in Figma, those settings take priority (for nodes without children):
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
if ('exportSettings' in node && node.exportSettings.length > 0) {
|
|
605
|
+
if (!hasChildren) {
|
|
606
|
+
const format = node.exportSettings[0].format // 'SVG', 'PNG', 'JPG'
|
|
607
|
+
asset.exportAs = format
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
**Exception:** Nodes WITH children ignore export settings for format selection. Their image fills become CSS `background-image` instead of `<img>` exports.
|
|
613
|
+
|
|
614
|
+
#### SVG for Vectors
|
|
615
|
+
|
|
616
|
+
**Always use SVG for:**
|
|
617
|
+
- Icons (typically < 48x48)
|
|
618
|
+
- Logos and wordmarks
|
|
619
|
+
- Geometric illustrations
|
|
620
|
+
- UI decorations (arrows, chevrons, checkmarks)
|
|
621
|
+
- Any VECTOR, BOOLEAN_OPERATION, STAR, POLYGON, LINE node
|
|
622
|
+
|
|
623
|
+
**SVG advantages:**
|
|
624
|
+
- Infinitely scalable without quality loss
|
|
625
|
+
- Typically tiny file size (< 5KB for icons)
|
|
626
|
+
- Can be styled with CSS (fill, stroke colors)
|
|
627
|
+
- Can be inlined for zero-request rendering
|
|
628
|
+
- Accessible (can include `<title>` and `<desc>`)
|
|
629
|
+
|
|
630
|
+
#### PNG for Raster Content
|
|
631
|
+
|
|
632
|
+
**Use PNG@2x for:**
|
|
633
|
+
- Photos and photographic imagery
|
|
634
|
+
- Complex imagery with transparency
|
|
635
|
+
- Screenshots or UI captures
|
|
636
|
+
- Anything with IMAGE fills
|
|
637
|
+
|
|
638
|
+
**2x scale rationale:** Modern displays (Retina, 4K) render at 2x or higher device pixel ratios. Exporting at 2x ensures crisp display:
|
|
639
|
+
|
|
640
|
+
```css
|
|
641
|
+
/* Image is 800x600 actual pixels, displayed at 400x300 CSS pixels */
|
|
642
|
+
.photo {
|
|
643
|
+
width: 400px;
|
|
644
|
+
height: 300px;
|
|
645
|
+
}
|
|
646
|
+
.photo img {
|
|
647
|
+
width: 100%;
|
|
648
|
+
height: 100%;
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
#### JPG for Photos (Size Optimization)
|
|
653
|
+
|
|
654
|
+
When file size matters more than pixel-perfect quality (e.g., large hero images, photo galleries), JPG offers significant compression:
|
|
655
|
+
|
|
656
|
+
```
|
|
657
|
+
GET /v1/files/:key/images?ids=NODE_ID&format=jpg&scale=2
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
JPG should only be used when:
|
|
661
|
+
- The image is photographic (no sharp edges or text)
|
|
662
|
+
- No transparency is needed
|
|
663
|
+
- File size reduction is a priority
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
### 8. Common Pitfalls
|
|
668
|
+
|
|
669
|
+
#### Pitfall: Exporting Every Node as an Image
|
|
670
|
+
|
|
671
|
+
**Problem:** Treating all visual nodes as assets, generating hundreds of unnecessary image files for simple rectangles, circles, and solid-fill elements.
|
|
672
|
+
|
|
673
|
+
**Rule:** Only export nodes that **cannot** be rendered as CSS. The vast majority of UI elements (rectangles, circles, containers with fills, gradients) are CSS-renderable. Only export:
|
|
674
|
+
- VECTOR, BOOLEAN_OPERATION, STAR, POLYGON (complex geometry)
|
|
675
|
+
- Nodes with IMAGE fills (raster content)
|
|
676
|
+
- Vector containers (groups/frames of vectors)
|
|
677
|
+
|
|
678
|
+
#### Pitfall: BOOLEAN_OPERATION Is Always SVG
|
|
679
|
+
|
|
680
|
+
**Problem:** Attempting to render a BOOLEAN_OPERATION as CSS. These nodes represent the union, intersection, subtraction, or exclusion of two or more shapes -- the resulting geometry cannot be expressed in CSS.
|
|
681
|
+
|
|
682
|
+
**Rule:** BOOLEAN_OPERATION nodes have a `booleanOperation` property indicating the operation type (UNION, INTERSECT, SUBTRACT, EXCLUDE). Regardless of the operation, always export as SVG.
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
if (node.type === 'BOOLEAN_OPERATION') {
|
|
686
|
+
asset.isVector = true
|
|
687
|
+
asset.exportAs = 'SVG'
|
|
688
|
+
}
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
#### Pitfall: Image Fills on Text Nodes
|
|
692
|
+
|
|
693
|
+
**Problem:** Treating a text node with an IMAGE fill as an image asset, losing the text content.
|
|
694
|
+
|
|
695
|
+
**Rule:** An IMAGE fill on a TEXT node means the text has a **background image** or **clipping mask**, not that the text should be replaced with an image. The text must remain as HTML text for accessibility and SEO. The image fill can be rendered as a CSS `background-image` with `background-clip: text` for a clipped text effect, or ignored if it's decorative.
|
|
696
|
+
|
|
697
|
+
#### Pitfall: Vector Nodes Have Fills and Strokes
|
|
698
|
+
|
|
699
|
+
**Problem:** Trying to extract CSS `background-color` or `border` from VECTOR node fills/strokes.
|
|
700
|
+
|
|
701
|
+
**Rule:** VECTOR node fills and strokes define the SVG path's appearance. These become SVG `fill` and `stroke` attributes in the exported SVG, not CSS properties. Do not apply CSS visual property extraction to nodes marked for SVG export.
|
|
702
|
+
|
|
703
|
+
```typescript
|
|
704
|
+
// In HTML generation:
|
|
705
|
+
const isExportedAsset = (node.asset && node.asset.exportAs && tag === 'img') ||
|
|
706
|
+
node.asset?.isVectorContainer
|
|
707
|
+
|
|
708
|
+
if (!isExportedAsset) {
|
|
709
|
+
// Only apply visual styles (background, border, etc.) to non-asset nodes
|
|
710
|
+
Object.assign(styles, generateVisualStyles(...))
|
|
711
|
+
}
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
#### Pitfall: Missing Retina Export
|
|
715
|
+
|
|
716
|
+
**Problem:** Exporting images at 1x scale, producing blurry results on Retina/HiDPI displays.
|
|
717
|
+
|
|
718
|
+
**Rule:** Always export raster images at **2x minimum**. This applies to both the REST API (`scale=2`) and Plugin API (`constraint: { type: 'SCALE', value: 2 }`). The CSS should display the image at half the exported pixel dimensions.
|
|
719
|
+
|
|
720
|
+
#### Pitfall: Large SVG File Size
|
|
721
|
+
|
|
722
|
+
**Problem:** A vector container with many children produces an SVG with thousands of path elements, resulting in a large file that's slower to render than a raster image.
|
|
723
|
+
|
|
724
|
+
**Rule:** Consider PNG fallback for vector containers with excessive complexity. Signs that SVG may be too large:
|
|
725
|
+
- Container has > 50 visible children
|
|
726
|
+
- The SVG output exceeds 50KB
|
|
727
|
+
- The content is an illustration with many overlapping gradients
|
|
728
|
+
|
|
729
|
+
In these cases, PNG@2x may be a better choice for web performance.
|
|
730
|
+
|
|
731
|
+
#### Pitfall: Empty Containers as Vector Containers
|
|
732
|
+
|
|
733
|
+
**Problem:** An empty GROUP or FRAME with no children passes the "all children are vector-compatible" check vacuously (all zero of them are compatible).
|
|
734
|
+
|
|
735
|
+
**Rule:** The detection explicitly checks for at least one visible child:
|
|
736
|
+
|
|
737
|
+
```typescript
|
|
738
|
+
if (visibleChildren.length === 0) return false
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
#### Pitfall: Confusing Export Settings with Image Fills
|
|
742
|
+
|
|
743
|
+
**Problem:** A node has export settings (configured in Figma's export panel) AND image fills. The export settings might say "SVG" but the node has a raster image fill.
|
|
744
|
+
|
|
745
|
+
**Rule:** Export settings on nodes WITHOUT children drive the export format. Export settings on nodes WITH children are informational only -- the image fill renders as `background-image` regardless. When a node has both export settings and an image fill:
|
|
746
|
+
|
|
747
|
+
```typescript
|
|
748
|
+
if (asset.imageHash) {
|
|
749
|
+
// Image fill always exports as PNG, overriding SVG export settings
|
|
750
|
+
if (!asset.exportAs) {
|
|
751
|
+
asset.exportAs = 'PNG'
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
#### Edge Case: INSTANCE of a Vector Component
|
|
757
|
+
|
|
758
|
+
When an INSTANCE node references a component that is a vector container, the instance itself should be exported as SVG. The detection checks the instance's own children (which mirror the component's structure), not the component definition.
|
|
759
|
+
|
|
760
|
+
#### Edge Case: Visible vs Hidden Children
|
|
761
|
+
|
|
762
|
+
Only **visible** children are considered in vector container detection. Hidden children (nodes with `visible: false`) are filtered out:
|
|
763
|
+
|
|
764
|
+
```typescript
|
|
765
|
+
const visibleChildren = containerNode.children.filter(child => child.visible)
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
This prevents a hidden TEXT layer inside an icon frame from disqualifying the frame as a vector container.
|
|
769
|
+
|
|
770
|
+
---
|
|
771
|
+
|
|
772
|
+
### Intermediate Type Reference
|
|
773
|
+
|
|
774
|
+
The complete intermediate type used for asset data between extraction and generation:
|
|
775
|
+
|
|
776
|
+
```typescript
|
|
777
|
+
interface AssetData {
|
|
778
|
+
/** Export format: 'SVG', 'PNG', or 'JPG' */
|
|
779
|
+
exportAs?: 'SVG' | 'PNG' | 'JPG'
|
|
780
|
+
/** Image hash for IMAGE fill deduplication */
|
|
781
|
+
imageHash?: string
|
|
782
|
+
/** True if this is a vector type (VECTOR, STAR, POLYGON, BOOLEAN_OPERATION, ELLIPSE, LINE) */
|
|
783
|
+
isVector?: boolean
|
|
784
|
+
/** True if the node has export settings configured in Figma */
|
|
785
|
+
hasExportSettings?: boolean
|
|
786
|
+
/** True if this is a container to export as a single unit (SVG/PNG) */
|
|
787
|
+
isVectorContainer?: boolean
|
|
788
|
+
/** True if container has text that needs flattening before SVG export */
|
|
789
|
+
hasTextToFlatten?: boolean
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
**Note on `isVector` for ELLIPSE and LINE:** These types are marked as `isVector: true` but do NOT trigger the `exportAs` flag by themselves. They are "potential assets" -- the layout engine decides whether to CSS-render them or include them in a parent vector container's SVG export.
|
|
794
|
+
|
|
795
|
+
```typescript
|
|
796
|
+
// ELLIPSE and LINE: marked as vector but not auto-exported
|
|
797
|
+
if (node.type === 'ELLIPSE' || node.type === 'LINE') {
|
|
798
|
+
asset.isVector = true
|
|
799
|
+
// No asset.exportAs set -- decision deferred to layout phase
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// True vectors: marked AND set for SVG export
|
|
803
|
+
if (node.type === 'VECTOR' || node.type === 'STAR' ||
|
|
804
|
+
node.type === 'POLYGON' || node.type === 'BOOLEAN_OPERATION') {
|
|
805
|
+
asset.isVector = true
|
|
806
|
+
asset.exportAs = 'SVG' // Definite SVG export
|
|
807
|
+
}
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
---
|
|
811
|
+
|
|
812
|
+
### Asset Rendering in HTML
|
|
813
|
+
|
|
814
|
+
Vector containers render as self-closing `<img>` tags with no children, because their visual children are baked into the exported SVG/PNG:
|
|
815
|
+
|
|
816
|
+
```typescript
|
|
817
|
+
// In the traversal phase:
|
|
818
|
+
// Vector containers have children=[] set so they don't generate child elements
|
|
819
|
+
if (node.asset?.isVectorContainer) {
|
|
820
|
+
node.children = [] // Children are in the SVG, not in HTML
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// In HTML generation:
|
|
824
|
+
if (node.children && node.children.length > 0 && !node.asset?.isVectorContainer) {
|
|
825
|
+
// Only generate child elements for non-vector-container nodes
|
|
826
|
+
}
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
**Generated HTML:**
|
|
830
|
+
|
|
831
|
+
```html
|
|
832
|
+
<!-- Vector container (icon) -->
|
|
833
|
+
<img class="nav__icon" src="./assets/menu-icon.svg" alt="menu-icon">
|
|
834
|
+
|
|
835
|
+
<!-- Image fill (photo) -->
|
|
836
|
+
<img class="card__photo" src="./assets/team-photo.png" alt="Team Photo">
|
|
837
|
+
|
|
838
|
+
<!-- Frame with image fill AND children (hero section) -->
|
|
839
|
+
<div class="hero" style="background-image: url('./assets/hero-bg.png'); background-size: cover;">
|
|
840
|
+
<h1 class="hero__heading">Welcome</h1>
|
|
841
|
+
<p class="hero__text">Get started today</p>
|
|
842
|
+
</div>
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
---
|
|
846
|
+
|
|
847
|
+
## Cross-References
|
|
848
|
+
|
|
849
|
+
- **`figma-api-rest.md`** -- Image export endpoint (`GET /v1/files/:key/images`), format options (svg, png, jpg, pdf), scale parameter, SVG options (`svg_include_id`, `svg_simplify_stroke`), temporary URL handling
|
|
850
|
+
- **`figma-api-plugin.md`** -- SceneNode types (VECTOR, BOOLEAN_OPERATION, STAR, POLYGON, LINE, ELLIPSE, RECTANGLE), `exportAsync` method, `fills` property, `imageHash`, `scaleMode`, `visible` property, `children` access
|
|
851
|
+
- **`design-to-code-layout.md`** -- Auto Layout detection (`layoutMode !== 'NONE'`) that overrides vector container detection, parent layout context for absolute positioning of asset `<img>` elements
|
|
852
|
+
- **`design-to-code-visual.md`** -- Fill extraction patterns shared with asset detection (SOLID, GRADIENT, IMAGE fill types), visual styles that are skipped for exported assets
|
|
853
|
+
- **`design-to-code-typography.md`** -- Text nodes that should never be exported as images, IMAGE fills on text nodes as background/clip effect, text flattening in vector containers
|
|
854
|
+
- **`design-to-code-semantic.md`** -- `<img>` tag selection for asset nodes (vector containers and image fills), `alt` attribute generation from node names, decorative shapes rendered as `<div>` not `<img>`, semantic context around asset elements
|
|
855
|
+
- **`css-strategy.md`** -- Asset file references in generated CSS (`background-image: url(...)`) and HTML (`<img src="...">`) within the layered CSS architecture. Asset references appear in Layer 3 (CSS Modules).
|