@rogieking/figui3 2.29.1 → 2.29.3

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/propkit.html ADDED
@@ -0,0 +1,1435 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport"
7
+ content="width=device-width, initial-scale=1.0">
8
+ <title>PropKit — Figma UI3 Property Fields</title>
9
+ <link rel="stylesheet"
10
+ type="text/css"
11
+ href="fig.css">
12
+ <link rel="stylesheet"
13
+ href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css">
14
+ <script src="fig.js"></script>
15
+ <style>
16
+ * {
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ body {
21
+ margin: 0;
22
+ min-height: 100vh;
23
+ font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
24
+ overflow-x: hidden;
25
+ }
26
+
27
+ nav {
28
+ position: fixed;
29
+ width: 180px;
30
+ height: 100vh;
31
+ overflow-y: auto;
32
+ background: var(--figma-color-bg-secondary);
33
+ border-right: 1px solid var(--figma-color-border);
34
+ padding: 16px 0 0 0;
35
+ display: flex;
36
+ flex-direction: column;
37
+ }
38
+
39
+ nav .nav-links {
40
+ flex: 1;
41
+ overflow-y: auto;
42
+ }
43
+
44
+ nav .nav-links h1 {
45
+ font-size: 11px;
46
+ font-weight: 600;
47
+ text-transform: uppercase;
48
+ letter-spacing: 0.05em;
49
+ color: var(--figma-color-text-secondary);
50
+ padding: 8px 20px;
51
+ margin: 0 0 8px 0;
52
+ }
53
+
54
+ nav .nav-links a {
55
+ display: block;
56
+ padding: 6px 12px;
57
+ margin: 0 8px;
58
+ color: var(--figma-color-text-secondary);
59
+ text-decoration: none;
60
+ font-size: 13px;
61
+ border-radius: var(--radius-medium);
62
+ transition: background 0.15s, color 0.15s;
63
+ }
64
+
65
+ nav .nav-links a:hover {
66
+ color: var(--figma-color-text);
67
+ background: var(--figma-color-bg-hover);
68
+ }
69
+
70
+ nav .nav-links a.active {
71
+ color: var(--figma-color-text-brand);
72
+ background: var(--figma-color-bg-selected);
73
+ }
74
+
75
+ nav .theme-switch {
76
+ padding: 12px 16px;
77
+ border-top: 1px solid var(--figma-color-border);
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 8px;
81
+ font-size: 13px;
82
+ color: var(--figma-color-text-secondary);
83
+
84
+ &:has([checked="true"]) {
85
+ #theme-dark-btn {
86
+ color: var(--figma-color-icon-selected);
87
+ }
88
+
89
+ #theme-light-btn {
90
+ color: var(--figma-color-icon-secondary);
91
+ }
92
+ }
93
+
94
+ &:not(:has([checked="true"])) {
95
+ #theme-light-btn {
96
+ color: var(--figma-color-icon-selected);
97
+ }
98
+
99
+ #theme-dark-btn {
100
+ color: var(--figma-color-icon-secondary);
101
+ }
102
+ }
103
+ }
104
+
105
+ main {
106
+ margin-left: 180px;
107
+ padding: 24px 32px;
108
+ max-width: 600px;
109
+ min-height: 100vh;
110
+ display: flex;
111
+ flex-direction: column;
112
+ }
113
+
114
+ section {
115
+ scroll-margin-top: 24px;
116
+ padding-bottom: 32px;
117
+ }
118
+
119
+ main>hr {
120
+ border: none;
121
+ border-top: 1px solid var(--figma-color-border);
122
+ margin: 0 0 32px 0;
123
+ }
124
+
125
+ section h2 {
126
+ font-size: 18px;
127
+ font-weight: 600;
128
+ color: var(--figma-color-text);
129
+ margin: 0 0 8px 0;
130
+ }
131
+
132
+ fig-header.propkit-subheader {
133
+ padding-left: 0;
134
+
135
+ h3 {
136
+ margin-right: var(--spacer-2);
137
+ flex-grow: 0;
138
+ }
139
+ }
140
+
141
+ section>p.description {
142
+ font-size: 13px;
143
+ color: var(--figma-color-text-secondary);
144
+ margin: 0 0 24px 0;
145
+ }
146
+
147
+ .prop-panel {
148
+ width: 240px;
149
+ flex-shrink: 0;
150
+ display: flex;
151
+ flex-direction: column;
152
+ gap: 2px;
153
+ border: 1px solid var(--figma-color-border);
154
+ border-radius: var(--radius-large);
155
+ padding: var(--spacer-2) 0;
156
+ background: var(--figma-color-bg);
157
+ margin-bottom: 0.5rem;
158
+ }
159
+
160
+ .placeholder-field {
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ height: 32px;
165
+ border: 1px dashed var(--figma-color-border);
166
+ border-radius: var(--radius-medium);
167
+ font-size: 11px;
168
+ color: var(--figma-color-text-tertiary);
169
+ }
170
+
171
+ code {
172
+ font-family: "SF Mono", Monaco, Consolas, monospace;
173
+ font-size: 11px;
174
+ background: var(--figma-color-bg-secondary);
175
+ padding: 2px 6px;
176
+ border-radius: 4px;
177
+ color: var(--figma-color-text);
178
+ }
179
+
180
+ /* Prism theme reset: remove glow/shadow from highlighted code */
181
+ pre[class*="language-"],
182
+ code[class*="language-"],
183
+ .token {
184
+ text-shadow: none !important;
185
+ }
186
+
187
+ pre.event-dump {
188
+ margin: 0;
189
+ padding: var(--spacer-2) var(--spacer-3);
190
+ background: var(--figma-color-bg-secondary);
191
+ border: 1px solid var(--figma-color-border);
192
+ border-radius: var(--radius-large);
193
+ margin-top: var(--spacer-2);
194
+ white-space: pre-wrap;
195
+ word-break: break-word;
196
+ width: 240px;
197
+ }
198
+
199
+ pre.event-dump code {
200
+ font-size: 11px;
201
+ }
202
+ </style>
203
+ </head>
204
+
205
+ <body>
206
+ <nav>
207
+ <div class="nav-links">
208
+ <h1>PropKit</h1>
209
+ <a href="#image">Image</a>
210
+ <a href="#color">Color</a>
211
+ <a href="#fill">Fill</a>
212
+ <a href="#slider">Slider</a>
213
+ <a href="#switch">Switch</a>
214
+ <a href="#dropdown">Dropdown</a>
215
+ <a href="#segment">Segment</a>
216
+ <a href="#dial">Dial</a>
217
+ <a href="#easing">Easing Curve</a>
218
+ <a href="#3d-rotate">3D Rotate</a>
219
+ <a href="#angle">Angle</a>
220
+ <hr>
221
+ <a href="#combined">Combined Panel</a>
222
+ </div>
223
+ <div class="theme-switch">
224
+ <fig-button id="theme-light-btn"
225
+ variant="ghost"
226
+ icon>
227
+ <svg width="24"
228
+ height="24"
229
+ viewBox="0 0 24 24"
230
+ fill="none">
231
+ <path fill-rule="evenodd"
232
+ clip-rule="evenodd"
233
+ d="M12 5C12.2761 5 12.5 5.22386 12.5 5.5V6.5C12.5 6.77614 12.2761 7 12 7C11.7239 7 11.5 6.77614 11.5 6.5V5.5C11.5 5.22386 11.7239 5 12 5ZM16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12ZM12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15ZM7.75687 7.05026C7.56161 6.85499 7.24503 6.85499 7.04977 7.05026C6.8545 7.24552 6.8545 7.5621 7.04977 7.75736L7.75687 8.46447C7.95214 8.65973 8.26872 8.65973 8.46398 8.46447C8.65924 8.26921 8.65924 7.95262 8.46398 7.75736L7.75687 7.05026ZM19 12C19 12.2761 18.7761 12.5 18.5 12.5H17.5C17.2239 12.5 17 12.2761 17 12C17 11.7239 17.2239 11.5 17.5 11.5H18.5C18.7761 11.5 19 11.7239 19 12ZM16.9502 7.75736C17.1455 7.5621 17.1455 7.24552 16.9502 7.05026C16.755 6.85499 16.4384 6.85499 16.2431 7.05026L15.536 7.75736C15.3408 7.95262 15.3408 8.26921 15.536 8.46447C15.7313 8.65973 16.0479 8.65973 16.2431 8.46447L16.9502 7.75736ZM12 17C12.2761 17 12.5 17.2239 12.5 17.5V18.5C12.5 18.7761 12.2761 19 12 19C11.7239 19 11.5 18.7761 11.5 18.5V17.5C11.5 17.2239 11.7239 17 12 17ZM16.2422 15.5356C16.047 15.3403 15.7304 15.3403 15.5351 15.5356C15.3399 15.7309 15.3399 16.0475 15.5351 16.2427L16.2422 16.9498C16.4375 17.1451 16.7541 17.1451 16.9493 16.9498C17.1446 16.7546 17.1446 16.438 16.9493 16.2427L16.2422 15.5356ZM7 12C7 12.2761 6.77614 12.5 6.5 12.5H5.5C5.22386 12.5 5 12.2761 5 12C5 11.7239 5.22386 11.5 5.5 11.5H6.5C6.77614 11.5 7 11.7239 7 12ZM8.46488 16.2427C8.66014 16.0475 8.66014 15.7309 8.46488 15.5356C8.26962 15.3403 7.95304 15.3403 7.75777 15.5356L7.05067 16.2427C6.85541 16.438 6.85541 16.7546 7.05067 16.9498C7.24593 17.1451 7.56251 17.1451 7.75777 16.9498L8.46488 16.2427Z"
234
+ fill="currentColor" />
235
+ </svg>
236
+ </fig-button>
237
+ <fig-switch id="theme-toggle"></fig-switch>
238
+ <fig-button id="theme-dark-btn"
239
+ variant="ghost"
240
+ icon>
241
+ <svg width="24"
242
+ height="24"
243
+ viewBox="0 0 24 24"
244
+ fill="none">
245
+ <path fill-rule="evenodd"
246
+ clip-rule="evenodd"
247
+ d="M15 14.9999C15.3647 14.9999 15.7224 14.9672 16.0703 14.9045C15.1624 16.1743 13.677 16.9997 12 16.9997C9.23858 16.9997 7 14.7611 7 11.9997C7 10.3226 7.82546 8.8371 9.09542 7.92923C9.03267 8.27722 9 8.63509 9 8.99986C9 12.3136 11.6863 14.9999 15 14.9999ZM17.3039 14.8075C17.6193 14.2129 16.8933 13.678 16.2412 13.8446C15.8443 13.946 15.4285 13.9999 15 13.9999C12.2386 13.9999 10 11.7613 10 8.99986C10 8.57132 10.0539 8.15537 10.1553 7.75842C10.3219 7.10631 9.78711 6.38032 9.19252 6.6957C7.29348 7.70298 6 9.70029 6 11.9997C6 15.3134 8.68629 17.9997 12 17.9997C14.2993 17.9997 16.2965 16.7064 17.3039 14.8075ZM16 7.49993C16 7.22379 15.7761 6.99993 15.5 6.99993C15.2239 6.99993 15 7.22379 15 7.49993V7.99993H14.5C14.2239 7.99993 14 8.22379 14 8.49993C14 8.77607 14.2239 8.99993 14.5 8.99993H15V9.49993C15 9.77607 15.2239 9.99993 15.5 9.99993C15.7761 9.99993 16 9.77607 16 9.49993V8.99993H16.5C16.7761 8.99993 17 8.77607 17 8.49993C17 8.22379 16.7761 7.99993 16.5 7.99993H16V7.49993Z"
248
+ fill="currentColor" />
249
+ </svg>
250
+ </fig-button>
251
+ </div>
252
+ </nav>
253
+
254
+ <main>
255
+ <!-- Image -->
256
+ <section id="image">
257
+ <h2>Image</h2>
258
+ <p class="description">An image upload field for selecting or previewing image assets.</p>
259
+
260
+ <fig-header borderless
261
+ class="propkit-subheader">
262
+ <h3>Upload</h3>
263
+ <fig-button variant="secondary"
264
+ class="copy-prompt-btn">Copy prompt</fig-button>
265
+ </fig-header>
266
+ <hstack>
267
+ <div class="prop-panel">
268
+ <fig-field direction="horizontal">
269
+ <label>Image</label>
270
+ <fig-image full="true"
271
+ upload="true"
272
+ label="Upload"
273
+ size="auto"></fig-image>
274
+ </fig-field>
275
+ </div>
276
+ <div class="prop-panel">
277
+ <fig-field direction="horizontal">
278
+ <label>Image</label>
279
+ <fig-image full="true"
280
+ upload="true"
281
+ src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=260&h=400&fit=crop"
282
+ size="auto"></fig-image>
283
+ </fig-field>
284
+ </div>
285
+ </hstack>
286
+
287
+ <fig-header borderless
288
+ class="propkit-subheader">
289
+ <h3>Auto Aspect Ratio</h3>
290
+ <fig-button variant="secondary"
291
+ class="copy-prompt-btn">Copy prompt</fig-button>
292
+ </fig-header>
293
+ <div class="prop-panel">
294
+ <fig-field direction="horizontal">
295
+ <label>Image</label>
296
+ <fig-image full="true"
297
+ src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=260&h=400&fit=crop"
298
+ aspect-ratio="auto"></fig-image>
299
+ </fig-field>
300
+ </div>
301
+
302
+ <fig-header borderless
303
+ class="propkit-subheader">
304
+ <h3>16:9</h3>
305
+ <fig-button variant="secondary"
306
+ class="copy-prompt-btn">Copy prompt</fig-button>
307
+ </fig-header>
308
+ <div class="prop-panel">
309
+ <fig-field direction="horizontal">
310
+ <label>Image</label>
311
+ <fig-image full="true"
312
+ src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=480&h=270&fit=crop"
313
+ aspect-ratio="16/9"></fig-image>
314
+ </fig-field>
315
+ </div>
316
+
317
+ <fig-header borderless
318
+ class="propkit-subheader">
319
+ <h3>4:3</h3>
320
+ <fig-button variant="secondary"
321
+ class="copy-prompt-btn">Copy prompt</fig-button>
322
+ </fig-header>
323
+ <div class="prop-panel">
324
+ <fig-field direction="horizontal">
325
+ <label>Image</label>
326
+ <fig-image full="true"
327
+ src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=400&h=300&fit=crop"
328
+ aspect-ratio="4/3"></fig-image>
329
+ </fig-field>
330
+ </div>
331
+
332
+ <fig-header borderless
333
+ class="propkit-subheader">
334
+ <h3>Cover</h3>
335
+ <fig-button variant="secondary"
336
+ class="copy-prompt-btn">Copy prompt</fig-button>
337
+ </fig-header>
338
+ <hstack>
339
+ <div class="prop-panel">
340
+ <fig-field direction="horizontal">
341
+ <label>Portrait</label>
342
+ <fig-image full="true"
343
+ src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=260&h=400&fit=crop"
344
+ fit="cover"
345
+ size="auto"
346
+ aspect-ratio="4/3"></fig-image>
347
+ </fig-field>
348
+ </div>
349
+ <div class="prop-panel">
350
+ <fig-field direction="horizontal">
351
+ <label>Landscape</label>
352
+ <fig-image full="true"
353
+ src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=480&h=270&fit=crop"
354
+ fit="cover"
355
+ size="auto"
356
+ aspect-ratio="4/3"></fig-image>
357
+ </fig-field>
358
+ </div>
359
+ </hstack>
360
+
361
+ <fig-header borderless
362
+ class="propkit-subheader">
363
+ <h3>Contain</h3>
364
+ <fig-button variant="secondary"
365
+ class="copy-prompt-btn">Copy prompt</fig-button>
366
+ </fig-header>
367
+ <hstack>
368
+ <div class="prop-panel">
369
+ <fig-field direction="horizontal">
370
+ <label>Portrait</label>
371
+ <fig-image full="true"
372
+ src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=260&h=400&fit=crop"
373
+ fit="contain"
374
+ size="auto"
375
+ aspect-ratio="4/3"></fig-image>
376
+ </fig-field>
377
+ </div>
378
+ <div class="prop-panel">
379
+ <fig-field direction="horizontal">
380
+ <label>Landscape</label>
381
+ <fig-image full="true"
382
+ src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=480&h=270&fit=crop"
383
+ fit="contain"
384
+ size="auto"
385
+ aspect-ratio="4/3"></fig-image>
386
+ </fig-field>
387
+ </div>
388
+ </hstack>
389
+ </section>
390
+
391
+ <hr>
392
+
393
+ <!-- Color -->
394
+ <section id="color">
395
+ <h2>Color</h2>
396
+ <p class="description">A solid color picker field with hex input and optional alpha.</p>
397
+
398
+ <fig-header borderless
399
+ class="propkit-subheader">
400
+ <h3>Default</h3>
401
+ <fig-button variant="secondary"
402
+ class="copy-prompt-btn">Copy prompt</fig-button>
403
+ </fig-header>
404
+ <hstack>
405
+ <div class="prop-panel">
406
+ <fig-field direction="horizontal">
407
+ <label>Color</label>
408
+ <fig-input-color value="#0D99FF80"
409
+ text="true"
410
+ alpha="true"
411
+ picker="figma"
412
+ picker-anchor="self"
413
+ full></fig-input-color>
414
+ </fig-field>
415
+ </div>
416
+ <div class="prop-panel">
417
+ <fig-field direction="horizontal">
418
+ <label>Color</label>
419
+ <fig-input-color value="#0D99FF"
420
+ text="true"
421
+ picker="figma"
422
+ picker-anchor="self"
423
+ full></fig-input-color>
424
+ </fig-field>
425
+ </div>
426
+ </hstack>
427
+ </section>
428
+
429
+ <hr>
430
+
431
+ <!-- Fill -->
432
+ <section id="fill">
433
+ <h2>Fill</h2>
434
+ <p class="description">A multi-mode fill field supporting solid, gradient, and image fills.</p>
435
+
436
+ <fig-header borderless
437
+ class="propkit-subheader">
438
+ <h3>Solid</h3>
439
+ <fig-button variant="secondary"
440
+ class="copy-prompt-btn">Copy prompt</fig-button>
441
+ </fig-header>
442
+ <hstack>
443
+ <div class="prop-panel">
444
+ <fig-field direction="horizontal">
445
+ <label>Fill</label>
446
+ <fig-input-fill value='{"type":"solid","color":"#667eea"}'
447
+ experimental="modern"></fig-input-fill>
448
+ </fig-field>
449
+ </div>
450
+ <div class="prop-panel">
451
+ <fig-field direction="horizontal">
452
+ <label>Fill</label>
453
+ <fig-input-fill value='{"type":"solid","color":"#667eea"}'
454
+ alpha="false"
455
+ experimental="modern"></fig-input-fill>
456
+ </fig-field>
457
+ </div>
458
+ </hstack>
459
+
460
+ <fig-header borderless
461
+ class="propkit-subheader">
462
+ <h3>Linear Gradient</h3>
463
+ <fig-button variant="secondary"
464
+ class="copy-prompt-btn">Copy prompt</fig-button>
465
+ </fig-header>
466
+ <hstack>
467
+ <div class="prop-panel">
468
+ <fig-field direction="horizontal">
469
+ <label>Fill</label>
470
+ <fig-input-fill value='{"type":"gradient","gradient":{"type":"linear","angle":135,"stops":[{"position":0,"color":"#667eea","opacity":100},{"position":100,"color":"#764ba2","opacity":100}]}}'
471
+ experimental="modern"></fig-input-fill>
472
+ </fig-field>
473
+ </div>
474
+ <div class="prop-panel">
475
+ <fig-field direction="horizontal">
476
+ <label>Fill</label>
477
+ <fig-input-fill value='{"type":"gradient","gradient":{"type":"linear","angle":90,"stops":[{"position":0,"color":"#f093fb","opacity":100},{"position":50,"color":"#f5576c","opacity":100},{"position":100,"color":"#ffd868","opacity":100}]}}'
478
+ experimental="modern"></fig-input-fill>
479
+ </fig-field>
480
+ </div>
481
+ </hstack>
482
+
483
+ <fig-header borderless
484
+ class="propkit-subheader">
485
+ <h3>Radial Gradient</h3>
486
+ <fig-button variant="secondary"
487
+ class="copy-prompt-btn">Copy prompt</fig-button>
488
+ </fig-header>
489
+ <hstack>
490
+ <div class="prop-panel">
491
+ <fig-field direction="horizontal">
492
+ <label>Fill</label>
493
+ <fig-input-fill value='{"type":"gradient","gradient":{"type":"radial","centerX":50,"centerY":50,"stops":[{"position":0,"color":"#ff6b6b","opacity":100},{"position":100,"color":"#4ecdc4","opacity":100}]}}'
494
+ experimental="modern"></fig-input-fill>
495
+ </fig-field>
496
+ </div>
497
+ <div class="prop-panel">
498
+ <fig-field direction="horizontal">
499
+ <label>Fill</label>
500
+ <fig-input-fill value='{"type":"gradient","gradient":{"type":"radial","centerX":50,"centerY":50,"stops":[{"position":0,"color":"#ffffff","opacity":100},{"position":100,"color":"#0D99FF","opacity":100}]}}'
501
+ experimental="modern"></fig-input-fill>
502
+ </fig-field>
503
+ </div>
504
+ </hstack>
505
+
506
+ <fig-header borderless
507
+ class="propkit-subheader">
508
+ <h3>Angular Gradient</h3>
509
+ <fig-button variant="secondary"
510
+ class="copy-prompt-btn">Copy prompt</fig-button>
511
+ </fig-header>
512
+ <hstack>
513
+ <div class="prop-panel">
514
+ <fig-field direction="horizontal">
515
+ <label>Fill</label>
516
+ <fig-input-fill value='{"type":"gradient","gradient":{"type":"angular","stops":[{"position":0,"color":"#ff0000","opacity":100},{"position":33,"color":"#00ff00","opacity":100},{"position":66,"color":"#0000ff","opacity":100},{"position":100,"color":"#ff0000","opacity":100}]}}'
517
+ experimental="modern"></fig-input-fill>
518
+ </fig-field>
519
+ </div>
520
+ <div class="prop-panel">
521
+ <fig-field direction="horizontal">
522
+ <label>Fill</label>
523
+ <fig-input-fill value='{"type":"gradient","gradient":{"type":"angular","stops":[{"position":0,"color":"#f7971e","opacity":100},{"position":100,"color":"#ffd200","opacity":100}]}}'
524
+ experimental="modern"></fig-input-fill>
525
+ </fig-field>
526
+ </div>
527
+ </hstack>
528
+
529
+ <fig-header borderless
530
+ class="propkit-subheader">
531
+ <h3>Image</h3>
532
+ <fig-button variant="secondary"
533
+ class="copy-prompt-btn">Copy prompt</fig-button>
534
+ </fig-header>
535
+ <hstack>
536
+ <div class="prop-panel">
537
+ <fig-field direction="horizontal">
538
+ <label>Fill</label>
539
+ <fig-input-fill value='{"type":"image","image":{"url":"https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=200&h=200&fit=crop","scaleMode":"fill","scale":50,"opacity":1}}'
540
+ experimental="modern"></fig-input-fill>
541
+ </fig-field>
542
+ </div>
543
+ <div class="prop-panel">
544
+ <fig-field direction="horizontal">
545
+ <label>Fill</label>
546
+ <fig-input-fill value='{"type":"image","image":{"url":"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200&h=200&fit=crop","scaleMode":"fill","scale":50,"opacity":0.5}}'
547
+ experimental="modern"></fig-input-fill>
548
+ </fig-field>
549
+ </div>
550
+ </hstack>
551
+ </section>
552
+
553
+ <hr>
554
+
555
+ <!-- Slider -->
556
+ <section id="slider">
557
+ <h2>Slider</h2>
558
+ <p class="description">A range slider with optional text input for precise numeric values.</p>
559
+
560
+ <fig-header borderless
561
+ class="propkit-subheader">
562
+ <h3>Default</h3>
563
+ <fig-button variant="secondary"
564
+ class="copy-prompt-btn">Copy prompt</fig-button>
565
+ </fig-header>
566
+ <hstack>
567
+ <div class="prop-panel">
568
+ <fig-field direction="horizontal">
569
+ <label>Amount</label>
570
+ <fig-slider value="50"
571
+ min="0"
572
+ max="100"
573
+ text="true"
574
+ full></fig-slider>
575
+ </fig-field>
576
+ </div>
577
+ <div class="prop-panel">
578
+ <fig-field direction="horizontal">
579
+ <label>Amount</label>
580
+ <fig-slider value="50"
581
+ min="0"
582
+ max="100"
583
+ full></fig-slider>
584
+ </fig-field>
585
+ </div>
586
+ </hstack>
587
+
588
+ <fig-header borderless
589
+ class="propkit-subheader">
590
+ <h3>Units</h3>
591
+ <fig-button variant="secondary"
592
+ class="copy-prompt-btn">Copy prompt</fig-button>
593
+ </fig-header>
594
+ <hstack>
595
+ <div class="prop-panel">
596
+ <fig-field direction="horizontal">
597
+ <label>Opacity</label>
598
+ <fig-slider value="75"
599
+ min="0"
600
+ max="100"
601
+ text="true"
602
+ units="%"
603
+ full></fig-slider>
604
+ </fig-field>
605
+ </div>
606
+ <div class="prop-panel">
607
+ <fig-field direction="horizontal">
608
+ <label>Opacity</label>
609
+ <fig-slider value="75"
610
+ min="0"
611
+ max="100"
612
+ units="%"
613
+ full></fig-slider>
614
+ </fig-field>
615
+ </div>
616
+ </hstack>
617
+
618
+ <fig-header borderless
619
+ class="propkit-subheader">
620
+ <h3>Opacity</h3>
621
+ <fig-button variant="secondary"
622
+ class="copy-prompt-btn">Copy prompt</fig-button>
623
+ </fig-header>
624
+ <hstack>
625
+ <div class="prop-panel">
626
+ <fig-field direction="horizontal">
627
+ <label>Opacity</label>
628
+ <fig-slider type="opacity"
629
+ value="0.75"
630
+ color="#ff0000"
631
+ units="%"
632
+ text="true"
633
+ full></fig-slider>
634
+ </fig-field>
635
+ </div>
636
+ <div class="prop-panel">
637
+ <fig-field direction="horizontal">
638
+ <label>Opacity</label>
639
+ <fig-slider type="opacity"
640
+ value="0.75"
641
+ color="#ff0000"
642
+ units="%"
643
+ full></fig-slider>
644
+ </fig-field>
645
+ </div>
646
+ </hstack>
647
+
648
+ <fig-header borderless
649
+ class="propkit-subheader">
650
+ <h3>Hue</h3>
651
+ <fig-button variant="secondary"
652
+ class="copy-prompt-btn">Copy prompt</fig-button>
653
+ </fig-header>
654
+ <hstack>
655
+ <div class="prop-panel">
656
+ <fig-field direction="horizontal">
657
+ <label>Hue</label>
658
+ <fig-slider type="hue"
659
+ value="180"
660
+ text="true"
661
+ full></fig-slider>
662
+ </fig-field>
663
+ </div>
664
+ <div class="prop-panel">
665
+ <fig-field direction="horizontal">
666
+ <label>Hue</label>
667
+ <fig-slider type="hue"
668
+ value="180"
669
+ full></fig-slider>
670
+ </fig-field>
671
+ </div>
672
+ </hstack>
673
+
674
+ <fig-header borderless
675
+ class="propkit-subheader">
676
+ <h3>Stepper</h3>
677
+ <fig-button variant="secondary"
678
+ class="copy-prompt-btn">Copy prompt</fig-button>
679
+ </fig-header>
680
+ <hstack>
681
+ <div class="prop-panel">
682
+ <fig-field direction="horizontal">
683
+ <label>Steps</label>
684
+ <fig-slider type="stepper"
685
+ value="50"
686
+ step="25"
687
+ text="true"
688
+ full>
689
+ <datalist>
690
+ <option value="0"></option>
691
+ <option value="25"></option>
692
+ <option value="50"></option>
693
+ <option value="75"></option>
694
+ <option value="100"></option>
695
+ </datalist>
696
+ </fig-slider>
697
+ </fig-field>
698
+ </div>
699
+ <div class="prop-panel">
700
+ <fig-field direction="horizontal">
701
+ <label>Steps</label>
702
+ <fig-slider type="stepper"
703
+ value="50"
704
+ step="25"
705
+ full>
706
+ <datalist>
707
+ <option value="0"></option>
708
+ <option value="25"></option>
709
+ <option value="50"></option>
710
+ <option value="75"></option>
711
+ <option value="100"></option>
712
+ </datalist>
713
+ </fig-slider>
714
+ </fig-field>
715
+ </div>
716
+ </hstack>
717
+
718
+ <fig-header borderless
719
+ class="propkit-subheader">
720
+ <h3>Delta</h3>
721
+ <fig-button variant="secondary"
722
+ class="copy-prompt-btn">Copy prompt</fig-button>
723
+ </fig-header>
724
+ <hstack>
725
+ <div class="prop-panel">
726
+ <fig-field direction="horizontal">
727
+ <label>Offset</label>
728
+ <fig-slider type="delta"
729
+ value="0"
730
+ default="0"
731
+ step="0.25"
732
+ min="-5"
733
+ max="5"
734
+ text="true"
735
+ full>
736
+ <datalist>
737
+ <option value="0"></option>
738
+ </datalist>
739
+ </fig-slider>
740
+ </fig-field>
741
+ </div>
742
+ <div class="prop-panel">
743
+ <fig-field direction="horizontal">
744
+ <label>Offset</label>
745
+ <fig-slider type="delta"
746
+ value="0"
747
+ default="0"
748
+ step="0.25"
749
+ min="-5"
750
+ max="5"
751
+ full>
752
+ <datalist>
753
+ <option value="0"></option>
754
+ </datalist>
755
+ </fig-slider>
756
+ </fig-field>
757
+ </div>
758
+ </hstack>
759
+
760
+ <fig-header borderless
761
+ class="propkit-subheader">
762
+ <h3>Transform</h3>
763
+ <fig-button variant="secondary"
764
+ class="copy-prompt-btn">Copy prompt</fig-button>
765
+ </fig-header>
766
+ <hstack>
767
+ <div class="prop-panel">
768
+ <fig-field direction="horizontal">
769
+ <label>Scale</label>
770
+ <fig-slider min="0"
771
+ max="1"
772
+ value="0.5"
773
+ step="0.01"
774
+ transform="100"
775
+ text="true"
776
+ units="%"
777
+ full></fig-slider>
778
+ </fig-field>
779
+ </div>
780
+ <div class="prop-panel">
781
+ <fig-field direction="horizontal">
782
+ <label>Scale</label>
783
+ <fig-slider min="0"
784
+ max="1"
785
+ value="0.5"
786
+ step="0.01"
787
+ transform="100"
788
+ units="%"
789
+ full></fig-slider>
790
+ </fig-field>
791
+ </div>
792
+ </hstack>
793
+ </section>
794
+
795
+ <hr>
796
+
797
+ <!-- Switch -->
798
+ <section id="switch">
799
+ <h2>Switch</h2>
800
+ <p class="description">A toggle switch for boolean on/off properties.</p>
801
+
802
+ <fig-header borderless
803
+ class="propkit-subheader">
804
+ <h3>Default</h3>
805
+ <fig-button variant="secondary"
806
+ class="copy-prompt-btn">Copy prompt</fig-button>
807
+ </fig-header>
808
+ <div class="prop-panel">
809
+ <fig-field direction="horizontal">
810
+ <label>Visible</label>
811
+ <fig-switch checked="true"></fig-switch>
812
+ </fig-field>
813
+ </div>
814
+ </section>
815
+
816
+ <hr>
817
+
818
+ <!-- Dropdown -->
819
+ <section id="dropdown">
820
+ <h2>Dropdown</h2>
821
+ <p class="description">A dropdown select field for choosing from a set of options.</p>
822
+
823
+ <fig-header borderless
824
+ class="propkit-subheader">
825
+ <h3>Default</h3>
826
+ <fig-button variant="secondary"
827
+ class="copy-prompt-btn">Copy prompt</fig-button>
828
+ </fig-header>
829
+ <div class="prop-panel">
830
+ <fig-field direction="horizontal">
831
+ <label>Blend Mode</label>
832
+ <fig-dropdown full
833
+ experimental="modern">
834
+ <option selected>Normal</option>
835
+ <option>Multiply</option>
836
+ <option>Screen</option>
837
+ <option>Overlay</option>
838
+ <option>Darken</option>
839
+ <option>Lighten</option>
840
+ </fig-dropdown>
841
+ </fig-field>
842
+ </div>
843
+ </section>
844
+
845
+ <hr>
846
+
847
+ <!-- Segment -->
848
+ <section id="segment">
849
+ <h2>Segment</h2>
850
+ <p class="description">A segmented control for mutually exclusive choices.</p>
851
+
852
+ <fig-header borderless
853
+ class="propkit-subheader">
854
+ <h3>Default</h3>
855
+ <fig-button variant="secondary"
856
+ class="copy-prompt-btn">Copy prompt</fig-button>
857
+ </fig-header>
858
+ <div class="prop-panel">
859
+ <fig-field direction="horizontal">
860
+ <label>Align</label>
861
+ <fig-segmented-control full>
862
+ <fig-segment value="left"
863
+ selected>Left</fig-segment>
864
+ <fig-segment value="center">Center</fig-segment>
865
+ <fig-segment value="right">Right</fig-segment>
866
+ </fig-segmented-control>
867
+ </fig-field>
868
+ </div>
869
+ </section>
870
+
871
+ <hr>
872
+
873
+ <!-- Dial -->
874
+ <section id="dial">
875
+ <h2>Dial</h2>
876
+ <p class="description">A rotary dial for continuous value input. <em>Not yet built.</em></p>
877
+
878
+ <fig-header borderless
879
+ class="propkit-subheader">
880
+ <h3>Default</h3>
881
+ <fig-button variant="secondary"
882
+ class="copy-prompt-btn">Copy prompt</fig-button>
883
+ </fig-header>
884
+ <div class="prop-panel">
885
+ <fig-field direction="horizontal">
886
+ <label>Amount</label>
887
+ <div class="placeholder-field">fig-input-dial</div>
888
+ </fig-field>
889
+ </div>
890
+ </section>
891
+
892
+ <hr>
893
+
894
+ <!-- Easing Curve -->
895
+ <section id="easing">
896
+ <h2>Easing Curve</h2>
897
+ <p class="description">A bezier curve editor for animation easing with preset dropdown.</p>
898
+
899
+ <fig-header borderless
900
+ class="propkit-subheader">
901
+ <h3>Default</h3>
902
+ <fig-button variant="secondary"
903
+ class="copy-prompt-btn">Copy prompt</fig-button>
904
+ </fig-header>
905
+ <div class="prop-panel">
906
+ <fig-field direction="horizontal">
907
+ <label>Easing</label>
908
+ <fig-easing-curve value="0.42, 0, 0.58, 1"
909
+ id="easing-curve-observed"
910
+ dropdown="true"></fig-easing-curve>
911
+ </fig-field>
912
+ </div>
913
+ <pre class="event-dump language-json"><code id="easing-curve-dump"
914
+ class="language-json">{
915
+ "event": "ready",
916
+ "value": "0.42, 0, 0.58, 1"
917
+ }</code></pre>
918
+
919
+ <fig-header borderless
920
+ class="propkit-subheader">
921
+ <h3>No Dropdown</h3>
922
+ <fig-button variant="secondary"
923
+ class="copy-prompt-btn">Copy prompt</fig-button>
924
+ </fig-header>
925
+ <div class="prop-panel">
926
+ <fig-field direction="horizontal">
927
+ <label>Easing</label>
928
+ <fig-easing-curve value="0.25, 0.1, 0.25, 1"></fig-easing-curve>
929
+ </fig-field>
930
+ </div>
931
+
932
+ <fig-header borderless
933
+ class="propkit-subheader">
934
+ <h3>16:9 Horizontal</h3>
935
+ <fig-button variant="secondary"
936
+ class="copy-prompt-btn">Copy prompt</fig-button>
937
+ </fig-header>
938
+ <div class="prop-panel">
939
+ <fig-field direction="horizontal">
940
+ <label>Easing</label>
941
+ <fig-easing-curve value="0.25, 0.1, 0.25, 1"
942
+ aspect-ratio="16/9"></fig-easing-curve>
943
+ </fig-field>
944
+ </div>
945
+
946
+ <fig-header borderless
947
+ class="propkit-subheader">
948
+ <h3>No Label</h3>
949
+ <fig-button variant="secondary"
950
+ class="copy-prompt-btn">Copy prompt</fig-button>
951
+ </fig-header>
952
+ <div class="prop-panel">
953
+ <fig-field direction="horizontal">
954
+ <fig-easing-curve value="spring(250, 8, 1)"
955
+ dropdown="true"></fig-easing-curve>
956
+ </fig-field>
957
+ </div>
958
+ </section>
959
+
960
+ <hr>
961
+
962
+ <!-- 3D Rotate -->
963
+ <section id="3d-rotate">
964
+ <h2>3D Rotate</h2>
965
+ <p class="description">An interactive 3D rotation control with a draggable cube preview.</p>
966
+
967
+ <fig-header borderless
968
+ class="propkit-subheader">
969
+ <h3>Default</h3>
970
+ <fig-button variant="secondary"
971
+ class="copy-prompt-btn">Copy prompt</fig-button>
972
+ </fig-header>
973
+ <div class="prop-panel">
974
+ <fig-field direction="horizontal">
975
+ <label>Rotation</label>
976
+ <fig-3d-rotate value="rotateX(0deg) rotateY(0deg) rotateZ(0deg)"
977
+ id="3d-rotate-observed"></fig-3d-rotate>
978
+ </fig-field>
979
+ </div>
980
+ <pre class="event-dump language-json"><code id="3d-rotate-dump"
981
+ class="language-json">{
982
+ "event": "ready",
983
+ "value": "rotateX(0.0deg) rotateY(0.0deg) rotateZ(0.0deg)"
984
+ }</code></pre>
985
+
986
+ <fig-header borderless
987
+ class="propkit-subheader">
988
+ <h3>16:9 Horizontal</h3>
989
+ <fig-button variant="secondary"
990
+ class="copy-prompt-btn">Copy prompt</fig-button>
991
+ </fig-header>
992
+ <div class="prop-panel">
993
+ <fig-field direction="horizontal">
994
+ <label>Rotation</label>
995
+ <fig-3d-rotate value="rotateX(0deg) rotateY(0deg) rotateZ(0deg)"
996
+ aspect-ratio="16/9"></fig-3d-rotate>
997
+ </fig-field>
998
+ </div>
999
+
1000
+ <fig-header borderless
1001
+ class="propkit-subheader">
1002
+ <h3>4:3</h3>
1003
+ <fig-button variant="secondary"
1004
+ class="copy-prompt-btn">Copy prompt</fig-button>
1005
+ </fig-header>
1006
+ <div class="prop-panel">
1007
+ <fig-field direction="horizontal">
1008
+ <label>Rotation</label>
1009
+ <fig-3d-rotate value="rotateX(0deg) rotateY(0deg) rotateZ(0deg)"
1010
+ aspect-ratio="4/3"></fig-3d-rotate>
1011
+ </fig-field>
1012
+ </div>
1013
+
1014
+ <fig-header borderless
1015
+ class="propkit-subheader">
1016
+ <h3>No Label</h3>
1017
+ <fig-button variant="secondary"
1018
+ class="copy-prompt-btn">Copy prompt</fig-button>
1019
+ </fig-header>
1020
+ <div class="prop-panel">
1021
+ <fig-field direction="horizontal">
1022
+ <fig-3d-rotate value="rotateX(0deg) rotateY(0deg) rotateZ(0deg)"
1023
+ aspect-ratio="16/9"></fig-3d-rotate>
1024
+ </fig-field>
1025
+ </div>
1026
+
1027
+ <fig-header borderless
1028
+ class="propkit-subheader">
1029
+ <h3>With Fields (All Axes)</h3>
1030
+ <fig-button variant="secondary"
1031
+ class="copy-prompt-btn">Copy prompt</fig-button>
1032
+ </fig-header>
1033
+ <div class="prop-panel">
1034
+ <fig-field direction="horizontal">
1035
+ <label>Rotation</label>
1036
+ <fig-3d-rotate value="rotateX(0deg) rotateY(0deg) rotateZ(0deg)"
1037
+ fields="rotateX,rotateY,rotateZ"></fig-3d-rotate>
1038
+ </fig-field>
1039
+ </div>
1040
+
1041
+ <fig-header borderless
1042
+ class="propkit-subheader">
1043
+ <h3>With Fields (Y Only)</h3>
1044
+ <fig-button variant="secondary"
1045
+ class="copy-prompt-btn">Copy prompt</fig-button>
1046
+ </fig-header>
1047
+ <div class="prop-panel">
1048
+ <fig-field direction="horizontal">
1049
+ <label>Rotation</label>
1050
+ <fig-3d-rotate value="rotateX(0deg) rotateY(0deg) rotateZ(0deg)"
1051
+ fields="rotateY"></fig-3d-rotate>
1052
+ </fig-field>
1053
+ </div>
1054
+
1055
+ <fig-header borderless
1056
+ class="propkit-subheader">
1057
+ <h3>With Fields (X &amp; Y)</h3>
1058
+ <fig-button variant="secondary"
1059
+ class="copy-prompt-btn">Copy prompt</fig-button>
1060
+ </fig-header>
1061
+ <div class="prop-panel">
1062
+ <fig-field direction="horizontal">
1063
+ <label>Rotation</label>
1064
+ <fig-3d-rotate value="rotateX(0deg) rotateY(0deg) rotateZ(0deg)"
1065
+ fields="rotateX,rotateY"></fig-3d-rotate>
1066
+ </fig-field>
1067
+ </div>
1068
+
1069
+ <fig-header borderless
1070
+ class="propkit-subheader">
1071
+ <h3>Fields + 16:9 + Preset Values</h3>
1072
+ <fig-button variant="secondary"
1073
+ class="copy-prompt-btn">Copy prompt</fig-button>
1074
+ </fig-header>
1075
+ <div class="prop-panel">
1076
+ <fig-field direction="horizontal">
1077
+ <label>Rotation</label>
1078
+ <fig-3d-rotate value="rotateX(25deg) rotateY(-35deg) rotateZ(10deg)"
1079
+ fields="rotateX,rotateY,rotateZ"
1080
+ aspect-ratio="16/9"></fig-3d-rotate>
1081
+ </fig-field>
1082
+ </div>
1083
+
1084
+ <fig-header borderless
1085
+ class="propkit-subheader">
1086
+ <h3>Fields, No Label</h3>
1087
+ <fig-button variant="secondary"
1088
+ class="copy-prompt-btn">Copy prompt</fig-button>
1089
+ </fig-header>
1090
+ <div class="prop-panel">
1091
+ <fig-field direction="horizontal">
1092
+ <fig-3d-rotate value="rotateX(0deg) rotateY(0deg) rotateZ(0deg)"
1093
+ fields="rotateX,rotateY,rotateZ"></fig-3d-rotate>
1094
+ </fig-field>
1095
+ </div>
1096
+ </section>
1097
+
1098
+ <hr>
1099
+
1100
+ <!-- Angle -->
1101
+ <section id="angle">
1102
+ <h2>Angle</h2>
1103
+ <p class="description">An angle input with a visual dial and numeric text field.</p>
1104
+
1105
+ <fig-header borderless
1106
+ class="propkit-subheader">
1107
+ <h3>Default</h3>
1108
+ <fig-button variant="secondary"
1109
+ class="copy-prompt-btn">Copy prompt</fig-button>
1110
+ </fig-header>
1111
+ <div class="prop-panel">
1112
+ <fig-field direction="horizontal">
1113
+ <label>Rotation</label>
1114
+ <fig-input-angle value="45"
1115
+ text="true"
1116
+ full></fig-input-angle>
1117
+ </fig-field>
1118
+ </div>
1119
+ </section>
1120
+
1121
+ <hr>
1122
+
1123
+ <!-- Combined Panel -->
1124
+ <section id="combined">
1125
+ <h2>Combined Panel</h2>
1126
+ <p class="description">A realistic property panel combining multiple field types.</p>
1127
+
1128
+ <fig-header borderless
1129
+ class="propkit-subheader">
1130
+ <h3>Default</h3>
1131
+ <fig-button variant="secondary"
1132
+ class="copy-prompt-btn">Copy prompt</fig-button>
1133
+ </fig-header>
1134
+ <div class="prop-panel">
1135
+ <fig-field direction="horizontal">
1136
+ <label>Fill</label>
1137
+ <fig-input-fill value='{"type":"solid","color":"#667eea"}'
1138
+ experimental="modern"></fig-input-fill>
1139
+ </fig-field>
1140
+ <fig-field direction="horizontal">
1141
+ <label>Opacity</label>
1142
+ <fig-slider value="100"
1143
+ min="0"
1144
+ max="100"
1145
+ text="true"
1146
+ full></fig-slider>
1147
+ </fig-field>
1148
+ <fig-field direction="horizontal">
1149
+ <label>Blend</label>
1150
+ <fig-dropdown full
1151
+ experimental="modern">
1152
+ <option selected>Normal</option>
1153
+ <option>Multiply</option>
1154
+ <option>Screen</option>
1155
+ <option>Overlay</option>
1156
+ </fig-dropdown>
1157
+ </fig-field>
1158
+ <fig-field direction="horizontal">
1159
+ <label>Visible</label>
1160
+ <fig-switch checked="true"></fig-switch>
1161
+ </fig-field>
1162
+
1163
+ <fig-field direction="horizontal">
1164
+ <label>Rotation</label>
1165
+ <fig-input-angle value="0"
1166
+ text="true"
1167
+ full></fig-input-angle>
1168
+ </fig-field>
1169
+ <fig-field direction="horizontal">
1170
+ <label>Align</label>
1171
+ <fig-segmented-control full>
1172
+ <fig-segment value="left">Left</fig-segment>
1173
+ <fig-segment value="center"
1174
+ selected>Center</fig-segment>
1175
+ <fig-segment value="right">Right</fig-segment>
1176
+ </fig-segmented-control>
1177
+ </fig-field>
1178
+
1179
+ <fig-field direction="horizontal">
1180
+ <label>Image</label>
1181
+ <fig-image full="true"
1182
+ upload="true"
1183
+ label="Upload"
1184
+ size="medium"></fig-image>
1185
+ </fig-field>
1186
+ <fig-field direction="horizontal">
1187
+ <label>Tint</label>
1188
+ <fig-input-color value="#FFFFFF"
1189
+ text="true"
1190
+ picker="figma"
1191
+ picker-anchor="self"
1192
+ full></fig-input-color>
1193
+ </fig-field>
1194
+ </div>
1195
+ </section>
1196
+ </main>
1197
+
1198
+ <dialog is="fig-toast"
1199
+ id="prompt-copy-toast"
1200
+ duration="1500"
1201
+ theme="dark">Prompt copied</dialog>
1202
+
1203
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-core.min.js"></script>
1204
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-clike.min.js"></script>
1205
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-javascript.min.js"></script>
1206
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-json.min.js"></script>
1207
+ <script>
1208
+ // Nav highlighting
1209
+ function updateActiveNav() {
1210
+ const hash = location.hash || '#image';
1211
+ document.querySelectorAll('nav .nav-links a').forEach(a => {
1212
+ a.classList.toggle('active', a.getAttribute('href') === hash);
1213
+ });
1214
+ }
1215
+
1216
+ let isClickScrolling = false;
1217
+
1218
+ const observer = new IntersectionObserver(entries => {
1219
+ if (isClickScrolling) return;
1220
+ entries.forEach(entry => {
1221
+ if (entry.isIntersecting) {
1222
+ history.replaceState(null, '', '#' + entry.target.id);
1223
+ updateActiveNav();
1224
+ }
1225
+ });
1226
+ }, { threshold: 0.3 });
1227
+
1228
+ document.querySelectorAll('section[id]').forEach(s => observer.observe(s));
1229
+
1230
+ document.querySelectorAll('nav .nav-links a').forEach(a => {
1231
+ a.addEventListener('click', (e) => {
1232
+ e.preventDefault();
1233
+ const targetId = a.getAttribute('href');
1234
+ const target = document.getElementById(targetId.slice(1));
1235
+ if (target) {
1236
+ isClickScrolling = true;
1237
+ history.pushState(null, '', targetId);
1238
+ updateActiveNav();
1239
+ target.scrollIntoView({ behavior: 'smooth' });
1240
+ setTimeout(() => { isClickScrolling = false; }, 500);
1241
+ }
1242
+ });
1243
+ });
1244
+
1245
+ window.addEventListener('load', () => {
1246
+ updateActiveNav();
1247
+ if (location.hash) {
1248
+ document.getElementById(location.hash.slice(1))?.scrollIntoView();
1249
+ }
1250
+ });
1251
+
1252
+ window.addEventListener('hashchange', updateActiveNav);
1253
+
1254
+ // Theme toggle
1255
+ const themeToggle = document.getElementById('theme-toggle');
1256
+ const themeLightBtn = document.getElementById('theme-light-btn');
1257
+ const themeDarkBtn = document.getElementById('theme-dark-btn');
1258
+ document.querySelectorAll('fig-slider').forEach((slider) => {
1259
+ slider.setAttribute('variant', 'neue');
1260
+ });
1261
+
1262
+ function setTheme(isDark) {
1263
+ document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';
1264
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
1265
+ if (isDark) {
1266
+ themeToggle.setAttribute('checked', 'true');
1267
+ } else {
1268
+ themeToggle.removeAttribute('checked');
1269
+ }
1270
+ }
1271
+
1272
+ const savedTheme = localStorage.getItem('theme');
1273
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
1274
+ setTheme(savedTheme ? savedTheme === 'dark' : prefersDark);
1275
+
1276
+ themeToggle.addEventListener('change', (e) => setTheme(e.target.checked));
1277
+ themeLightBtn.addEventListener('click', () => setTheme(false));
1278
+ themeDarkBtn.addEventListener('click', () => setTheme(true));
1279
+
1280
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
1281
+ if (!localStorage.getItem('theme')) setTheme(e.matches);
1282
+ });
1283
+
1284
+ // Easing curve event observer dump
1285
+ const easingCurveObserved = document.getElementById('easing-curve-observed');
1286
+ const easingCurveDump = document.getElementById('easing-curve-dump');
1287
+ const promptCopyToast = document.getElementById('prompt-copy-toast');
1288
+
1289
+ function updateEasingDump(eventName, detail = null) {
1290
+ if (!easingCurveDump || !easingCurveObserved) return;
1291
+ const payload = {
1292
+ event: eventName,
1293
+ mode: detail?.mode ?? null,
1294
+ preset: detail?.preset ?? null,
1295
+ value: detail?.value ?? easingCurveObserved.value,
1296
+ cssValue: detail?.cssValue ?? easingCurveObserved.cssValue,
1297
+ };
1298
+ easingCurveDump.textContent = JSON.stringify(payload, null, 2);
1299
+ if (window.Prism) {
1300
+ window.Prism.highlightElement(easingCurveDump);
1301
+ }
1302
+ }
1303
+
1304
+ if (easingCurveObserved) {
1305
+ updateEasingDump('ready');
1306
+ easingCurveObserved.addEventListener('input', (e) => updateEasingDump('input', e.detail));
1307
+ easingCurveObserved.addEventListener('change', (e) => updateEasingDump('change', e.detail));
1308
+ }
1309
+
1310
+ // 3D rotate event observer dump
1311
+ const rotate3dObserved = document.getElementById('3d-rotate-observed');
1312
+ const rotate3dDump = document.getElementById('3d-rotate-dump');
1313
+
1314
+ function updateRotate3dDump(eventName, detail = null) {
1315
+ if (!rotate3dDump || !rotate3dObserved) return;
1316
+ const payload = {
1317
+ event: eventName,
1318
+ value: detail?.value ?? rotate3dObserved.value,
1319
+ rotateX: detail?.rotateX ?? rotate3dObserved.rotateX,
1320
+ rotateY: detail?.rotateY ?? rotate3dObserved.rotateY,
1321
+ rotateZ: detail?.rotateZ ?? rotate3dObserved.rotateZ,
1322
+ };
1323
+ rotate3dDump.textContent = JSON.stringify(payload, null, 2);
1324
+ if (window.Prism) {
1325
+ window.Prism.highlightElement(rotate3dDump);
1326
+ }
1327
+ }
1328
+
1329
+ if (rotate3dObserved) {
1330
+ updateRotate3dDump('ready');
1331
+ rotate3dObserved.addEventListener('input', (e) => updateRotate3dDump('input', e.detail));
1332
+ rotate3dObserved.addEventListener('change', (e) => updateRotate3dDump('change', e.detail));
1333
+ }
1334
+
1335
+ function collectComponentTargets(subheaderEl, maxTargets = 1) {
1336
+ const targets = [];
1337
+ let node = subheaderEl.nextElementSibling;
1338
+ while (node) {
1339
+ if (node.matches('fig-header.propkit-subheader') || node.tagName === 'H2' || node.tagName === 'HR') break;
1340
+ node.querySelectorAll('fig-field').forEach((field) => {
1341
+ const component = Array.from(field.children).find((child) => child.tagName !== 'LABEL');
1342
+ const label = field.querySelector('label')?.textContent?.trim() || '';
1343
+ if (component && targets.length < maxTargets) targets.push({ component, label });
1344
+ });
1345
+ if (targets.length >= maxTargets) break;
1346
+ node = node.nextElementSibling;
1347
+ }
1348
+ return targets;
1349
+ }
1350
+
1351
+ function formatAttrs(el) {
1352
+ return Array.from(el.attributes)
1353
+ .filter((a) => !['id', 'class', 'src', 'style', 'label', 'value'].includes(a.name))
1354
+ .map((a) => `${a.name}="${a.value}"`)
1355
+ .join(' ');
1356
+ }
1357
+
1358
+ function buildPropkitPrompt(subheaderEl) {
1359
+ const targets = collectComponentTargets(subheaderEl);
1360
+ if (!targets.length) {
1361
+ return 'Use a horizontal fig-field. With a label of [Label name].';
1362
+ }
1363
+ const { component: el, label } = targets[0];
1364
+ const labelText = label || '[Label name]';
1365
+ const tag = el.tagName.toLowerCase();
1366
+ if (tag === 'fig-image') {
1367
+ const parts = [];
1368
+ if (el.getAttribute('full') === 'true' || el.hasAttribute('full')) parts.push('full fig-image');
1369
+ else parts.push('fig-image');
1370
+ if (el.getAttribute('upload') === 'true' || el.hasAttribute('upload')) parts.push('upload');
1371
+ if (el.getAttribute('size') === 'auto') parts.push('auto size');
1372
+ if (el.hasAttribute('fit')) parts.push(`fit ${el.getAttribute('fit')}`);
1373
+ if (el.hasAttribute('aspect-ratio')) parts.push(`aspect ratio ${el.getAttribute('aspect-ratio')}`);
1374
+ return `Use a horizontal fig-field, with a ${parts.join(', ')}. With a label of ${labelText}.`;
1375
+ }
1376
+
1377
+ if (tag === 'fig-input-fill') {
1378
+ try {
1379
+ const val = JSON.parse(el.getAttribute('value') || '{}');
1380
+ const parts = ['fig-input-fill'];
1381
+ if (val.type === 'solid') {
1382
+ parts.push(`solid fill, color=${val.color}`);
1383
+ } else if (val.type === 'gradient' && val.gradient) {
1384
+ const g = val.gradient;
1385
+ parts.push(`${g.type} gradient`);
1386
+ if (g.angle !== undefined) parts.push(`angle=${g.angle}`);
1387
+ const colors = (g.stops || []).map(s => s.color).join(' → ');
1388
+ if (colors) parts.push(`stops=${colors}`);
1389
+ } else if (val.type === 'image') {
1390
+ parts.push('image fill');
1391
+ if (val.image?.scaleMode) parts.push(`scaleMode=${val.image.scaleMode}`);
1392
+ if (val.image?.opacity !== undefined && val.image.opacity < 1) parts.push(`opacity=${val.image.opacity}`);
1393
+ }
1394
+ if (el.hasAttribute('alpha') && el.getAttribute('alpha') === 'false') parts.push('alpha=false');
1395
+ return `Use a horizontal fig-field, with a ${parts.join(', ')}. With a label of ${labelText}.`;
1396
+ } catch (e) { /* fall through */ }
1397
+ }
1398
+
1399
+ const attrs = formatAttrs(el);
1400
+ if (!attrs) return `Use a horizontal fig-field, with a ${tag}. With a label of ${labelText}.`;
1401
+ return `Use a horizontal fig-field, with a ${tag}, ${attrs.replaceAll('"', '')}. With a label of ${labelText}.`;
1402
+ }
1403
+
1404
+ async function copyText(text) {
1405
+ if (navigator.clipboard?.writeText) {
1406
+ await navigator.clipboard.writeText(text);
1407
+ return;
1408
+ }
1409
+ const textarea = document.createElement('textarea');
1410
+ textarea.value = text;
1411
+ textarea.setAttribute('readonly', '');
1412
+ textarea.style.position = 'fixed';
1413
+ textarea.style.opacity = '0';
1414
+ document.body.appendChild(textarea);
1415
+ textarea.select();
1416
+ document.execCommand('copy');
1417
+ textarea.remove();
1418
+ }
1419
+
1420
+ document.addEventListener('click', async (e) => {
1421
+ const btn = e.target.closest('.copy-prompt-btn');
1422
+ if (!btn) return;
1423
+ try {
1424
+ const subheaderEl = btn.closest('fig-header.propkit-subheader');
1425
+ const prompt = buildPropkitPrompt(subheaderEl);
1426
+ await copyText(prompt);
1427
+ promptCopyToast?.showToast();
1428
+ } catch (err) {
1429
+ console.error('Failed to copy prompt', err);
1430
+ }
1431
+ });
1432
+ </script>
1433
+ </body>
1434
+
1435
+ </html>