@maccesar/aiskills 1.7.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 (73) hide show
  1. package/README.md +531 -0
  2. package/bin/aiskills.js +76 -0
  3. package/lib/cache.js +49 -0
  4. package/lib/cleanup.js +77 -0
  5. package/lib/commands/auto-update.js +131 -0
  6. package/lib/commands/doctor.js +139 -0
  7. package/lib/commands/list.js +77 -0
  8. package/lib/commands/skills.js +263 -0
  9. package/lib/commands/status.js +94 -0
  10. package/lib/commands/uninstall.js +182 -0
  11. package/lib/commands/update.js +149 -0
  12. package/lib/config.js +90 -0
  13. package/lib/downloader.js +110 -0
  14. package/lib/hooks.js +74 -0
  15. package/lib/installer.js +114 -0
  16. package/lib/platform.js +112 -0
  17. package/lib/prompts/checkboxCancel.js +264 -0
  18. package/lib/prompts/selectCancel.js +204 -0
  19. package/lib/symlink.js +154 -0
  20. package/lib/utils.js +49 -0
  21. package/package.json +61 -0
  22. package/skills/humaniza/SKILL.md +51 -0
  23. package/skills/humaniza/agents/openai.yaml +4 -0
  24. package/skills/humaniza/references/ai-patterns-es.md +51 -0
  25. package/skills/humaniza/references/checklist.md +9 -0
  26. package/skills/humaniza/references/examples.md +17 -0
  27. package/skills/humaniza/references/lexicon-es-mx.md +36 -0
  28. package/skills/humaniza/references/modes-es-mx.md +41 -0
  29. package/skills/humaniza/references/voice-es-mx.md +24 -0
  30. package/skills/refactoring-ui/SKILL.md +59 -0
  31. package/skills/refactoring-ui/references/01-design-process.md +72 -0
  32. package/skills/refactoring-ui/references/02-visual-hierarchy.md +84 -0
  33. package/skills/refactoring-ui/references/03-layout-spacing.md +69 -0
  34. package/skills/refactoring-ui/references/04-typography.md +70 -0
  35. package/skills/refactoring-ui/references/05-color.md +96 -0
  36. package/skills/refactoring-ui/references/06-depth-shadows.md +74 -0
  37. package/skills/refactoring-ui/references/07-images.md +75 -0
  38. package/skills/refactoring-ui/references/08-finishing-touches.md +91 -0
  39. package/skills/stitch-showcase/SKILL.md +411 -0
  40. package/skills/stitch-showcase/references/01-navbar.md +52 -0
  41. package/skills/stitch-showcase/references/02-hero.md +56 -0
  42. package/skills/stitch-showcase/references/03-design-system.md +102 -0
  43. package/skills/stitch-showcase/references/04-screen-gallery.md +102 -0
  44. package/skills/stitch-showcase/references/05-viewer-web.md +105 -0
  45. package/skills/stitch-showcase/references/06-viewer-mobile.md +104 -0
  46. package/skills/stitch-showcase/references/07-theme-system.md +77 -0
  47. package/skills/stitch-showcase/references/08-type-detection.md +81 -0
  48. package/skills/stitch-showcase/references/09-quality-standards.md +126 -0
  49. package/skills/stitch-showcase/references/10-component-standardization.md +40 -0
  50. package/skills/stitch-showcase/references/11-component-catalog.md +70 -0
  51. package/skills/stitch-showcase/references/catalog-template.html +841 -0
  52. package/skills/stitch-showcase/references/index.html +299 -0
  53. package/skills/stitch-showcase/references/viewer.html +412 -0
  54. package/skills/stitch-showcase/scripts/__pycache__/build_showcase.cpython-314.pyc +0 -0
  55. package/skills/stitch-showcase/scripts/__pycache__/component_utils.cpython-313.pyc +0 -0
  56. package/skills/stitch-showcase/scripts/__pycache__/detect_components.cpython-313.pyc +0 -0
  57. package/skills/stitch-showcase/scripts/__pycache__/extract_catalog.cpython-313.pyc +0 -0
  58. package/skills/stitch-showcase/scripts/__pycache__/extract_text.cpython-313.pyc +0 -0
  59. package/skills/stitch-showcase/scripts/__pycache__/extract_zips.cpython-313.pyc +0 -0
  60. package/skills/stitch-showcase/scripts/__pycache__/parse_design_md.cpython-313.pyc +0 -0
  61. package/skills/stitch-showcase/scripts/apply_canonical.py +238 -0
  62. package/skills/stitch-showcase/scripts/build_showcase.py +2103 -0
  63. package/skills/stitch-showcase/scripts/component_utils.py +398 -0
  64. package/skills/stitch-showcase/scripts/detect_components.py +284 -0
  65. package/skills/stitch-showcase/scripts/extract_catalog.py +913 -0
  66. package/skills/stitch-showcase/scripts/extract_text.py +268 -0
  67. package/skills/stitch-showcase/scripts/extract_zips.py +178 -0
  68. package/skills/stitch-showcase/scripts/parse_design_md.py +397 -0
  69. package/skills/vscode-extension-dev/SKILL.md +114 -0
  70. package/skills/vscode-extension-dev/references/api-patterns.md +625 -0
  71. package/skills/vscode-extension-dev/references/architecture.md +287 -0
  72. package/skills/vscode-extension-dev/references/package-json-schema.md +345 -0
  73. package/skills/vscode-extension-dev/references/publishing.md +251 -0
@@ -0,0 +1,841 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="{{DEFAULT_THEME}}">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{PROJECT_NAME}} — Component Catalog</title>
7
+ {{FONT_LINK}}
8
+ <style>
9
+ :root {
10
+ --accent: {{PRIMARY_COLOR}};
11
+ --accent-10: {{PRIMARY_COLOR}}1a;
12
+ --font: '{{FONT_FAMILY}}', system-ui, -apple-system, sans-serif;
13
+ --bg: #f5f5f5;
14
+ --surface: #ffffff;
15
+ --surface-2: #f0f0f0;
16
+ --text: #111;
17
+ --text-2: #555;
18
+ --text-3: #888;
19
+ --border: #e0e0e0;
20
+ --code-bg: #f6f6f6;
21
+ }
22
+ [data-theme="dark"] {
23
+ --bg: #0d0d0d;
24
+ --surface: #1a1a1a;
25
+ --surface-2: #252525;
26
+ --text: #f0f0f0;
27
+ --text-2: #aaa;
28
+ --text-3: #666;
29
+ --border: #2a2a2a;
30
+ --code-bg: #1e1e1e;
31
+ }
32
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
33
+ body {
34
+ font-family: var(--font);
35
+ background: var(--bg);
36
+ color: var(--text);
37
+ line-height: 1.5;
38
+ -webkit-font-smoothing: antialiased;
39
+ }
40
+
41
+ /* ── Header (matches index/viewer exactly) ────── */
42
+ .header {
43
+ position: sticky;
44
+ top: 0;
45
+ z-index: 50;
46
+ border-bottom: 1px solid rgba(0,0,0,0.06);
47
+ background: rgba(255,255,255,0.8);
48
+ backdrop-filter: blur(20px) saturate(180%);
49
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
50
+ }
51
+ [data-theme="dark"] .header {
52
+ background: rgba(13,13,13,0.8);
53
+ border-bottom-color: rgba(255,255,255,0.08);
54
+ }
55
+ .header-inner {
56
+ max-width: 1280px;
57
+ margin: 0 auto;
58
+ height: 56px;
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: space-between;
62
+ gap: 1rem;
63
+ padding: 0 1.5rem;
64
+ }
65
+ .header-left {
66
+ display: flex;
67
+ align-items: center;
68
+ gap: 0.75rem;
69
+ min-width: 0;
70
+ }
71
+ .header-title {
72
+ font-size: 0.875rem;
73
+ font-weight: 600;
74
+ color: var(--text);
75
+ white-space: nowrap;
76
+ }
77
+ .header-badge {
78
+ font-size: 0.6875rem;
79
+ font-weight: 500;
80
+ background: var(--surface-2);
81
+ border: 1px solid var(--border);
82
+ border-radius: 9999px;
83
+ padding: 0.125rem 0.625rem;
84
+ color: var(--text-3);
85
+ white-space: nowrap;
86
+ }
87
+ .header-actions { display: flex; align-items: center; gap: 0.5rem; }
88
+
89
+ /* ── Back button (matches viewer) ──────────────── */
90
+ .back-btn {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 0.375rem;
94
+ padding: 0.375rem 0.625rem;
95
+ border-radius: 8px;
96
+ font-size: 0.8125rem;
97
+ color: var(--text-2);
98
+ text-decoration: none;
99
+ transition: background 0.15s, color 0.15s;
100
+ flex-shrink: 0;
101
+ }
102
+ .back-btn:hover { background: var(--surface-2); color: var(--text); }
103
+ .back-btn svg { width: 14px; height: 14px; }
104
+ .header-divider {
105
+ width: 1px;
106
+ height: 16px;
107
+ background: var(--border);
108
+ flex-shrink: 0;
109
+ }
110
+
111
+ /* ── Theme toggle (SVG moon/sun like index) ───── */
112
+ .theme-btn {
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ width: 32px;
117
+ height: 32px;
118
+ background: transparent;
119
+ border: none;
120
+ border-radius: 8px;
121
+ cursor: pointer;
122
+ color: var(--text-2);
123
+ transition: background 0.15s, color 0.15s;
124
+ }
125
+ .theme-btn:hover { background: var(--surface-2); color: var(--text); }
126
+ .theme-btn svg { width: 16px; height: 16px; }
127
+ /* Show moon in light mode, sun in dark mode */
128
+ .theme-btn .icon-moon { display: block; }
129
+ .theme-btn .icon-sun { display: none; }
130
+ [data-theme="dark"] .theme-btn .icon-moon { display: none; }
131
+ [data-theme="dark"] .theme-btn .icon-sun { display: block; }
132
+
133
+ /* ── Loading overlay ──────────────────────────── */
134
+ .loading-overlay {
135
+ position: fixed;
136
+ inset: 0;
137
+ z-index: 100;
138
+ display: flex;
139
+ flex-direction: column;
140
+ align-items: center;
141
+ justify-content: center;
142
+ gap: 1rem;
143
+ background: var(--bg);
144
+ transition: opacity 0.3s;
145
+ }
146
+ .loading-overlay.hidden {
147
+ opacity: 0;
148
+ pointer-events: none;
149
+ }
150
+ .loading-spinner {
151
+ width: 32px;
152
+ height: 32px;
153
+ border: 3px solid var(--border);
154
+ border-top-color: var(--accent);
155
+ border-radius: 50%;
156
+ animation: spin 0.8s linear infinite;
157
+ }
158
+ @keyframes spin { to { transform: rotate(360deg); } }
159
+ .loading-text {
160
+ font-size: 0.8125rem;
161
+ color: var(--text-3);
162
+ }
163
+
164
+ /* ── Tabs ──────────────────────────────────────── */
165
+ .tabs {
166
+ position: sticky;
167
+ top: 56px;
168
+ z-index: 40;
169
+ display: flex;
170
+ flex-wrap: wrap;
171
+ gap: 0.375rem;
172
+ padding: 0.75rem 1.5rem;
173
+ background: var(--surface);
174
+ border-bottom: 1px solid var(--border);
175
+ }
176
+ .tab {
177
+ padding: 0.375rem 0.875rem;
178
+ border-radius: 99px;
179
+ font-size: 0.75rem;
180
+ font-weight: 500;
181
+ cursor: pointer;
182
+ border: 1px solid transparent;
183
+ background: transparent;
184
+ color: var(--text-2);
185
+ transition: all 0.15s;
186
+ }
187
+ .tab:hover { background: var(--surface-2); }
188
+ .tab.active {
189
+ background: var(--accent);
190
+ color: #fff;
191
+ border-color: var(--accent);
192
+ }
193
+ .tab .count { opacity: 0.6; margin-left: 0.25rem; }
194
+
195
+ /* ── Main content ──────────────────────────────── */
196
+ .main { max-width: 1280px; margin: 0 auto; padding: 1.5rem; }
197
+ .section { margin-bottom: 2.5rem; }
198
+ .section-title {
199
+ font-size: 1rem;
200
+ font-weight: 600;
201
+ margin-bottom: 1rem;
202
+ padding-bottom: 0.5rem;
203
+ border-bottom: 1px solid var(--border);
204
+ color: var(--text);
205
+ }
206
+ .section-title .badge {
207
+ font-size: 0.6875rem;
208
+ font-weight: 500;
209
+ color: var(--text-3);
210
+ margin-left: 0.5rem;
211
+ }
212
+
213
+ /* ── Component cards ───────────────────────────── */
214
+ .grid {
215
+ display: grid;
216
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
217
+ gap: 1rem;
218
+ }
219
+ .comp-card {
220
+ background: var(--surface);
221
+ border: 1px solid var(--border);
222
+ border-radius: 12px;
223
+ overflow: hidden;
224
+ transition: border-color 0.15s, box-shadow 0.15s;
225
+ }
226
+ .comp-card:hover {
227
+ border-color: var(--accent);
228
+ box-shadow: 0 4px 16px rgba(0,0,0,0.08);
229
+ }
230
+ [data-theme="dark"] .comp-card:hover {
231
+ box-shadow: 0 4px 16px rgba(0,0,0,0.3);
232
+ }
233
+
234
+ /* Preview area — contains raw HTML safely via scale wrapper */
235
+ .comp-preview {
236
+ position: relative;
237
+ height: 120px;
238
+ background: var(--surface-2);
239
+ border-bottom: 1px solid var(--border);
240
+ overflow: hidden;
241
+ }
242
+ /* Fade-out at the bottom when content is clipped */
243
+ .comp-preview::after {
244
+ content: '';
245
+ position: absolute;
246
+ bottom: 0; left: 0; right: 0;
247
+ height: 24px;
248
+ background: linear-gradient(transparent, var(--surface-2));
249
+ pointer-events: none;
250
+ z-index: 1;
251
+ }
252
+ /* Scale wrapper: renders HTML at 2x width then scales to 50% */
253
+ .preview-scale {
254
+ width: 200%;
255
+ transform: scale(0.5);
256
+ transform-origin: top left;
257
+ pointer-events: none;
258
+ }
259
+ /* Atomic previews (buttons, icons, badges): center, no scale */
260
+ .comp-preview.atomic-preview {
261
+ display: flex;
262
+ align-items: center;
263
+ justify-content: center;
264
+ height: 80px;
265
+ }
266
+ .comp-preview.atomic-preview .preview-scale {
267
+ width: auto;
268
+ transform: none;
269
+ }
270
+
271
+ /* Info area */
272
+ .comp-info { padding: 0.875rem 1rem; }
273
+ .comp-name {
274
+ font-size: 0.8125rem;
275
+ font-weight: 600;
276
+ color: var(--text);
277
+ margin-bottom: 0.25rem;
278
+ }
279
+ .comp-meta {
280
+ font-size: 0.6875rem;
281
+ color: var(--text-3);
282
+ display: flex;
283
+ flex-wrap: wrap;
284
+ gap: 0.5rem;
285
+ margin-bottom: 0.5rem;
286
+ }
287
+ .comp-meta .pill {
288
+ background: var(--accent-10);
289
+ color: var(--accent);
290
+ padding: 0.125rem 0.5rem;
291
+ border-radius: 99px;
292
+ font-weight: 500;
293
+ }
294
+ .comp-screens {
295
+ font-size: 0.6875rem;
296
+ color: var(--text-3);
297
+ }
298
+ .comp-screens a {
299
+ color: var(--accent);
300
+ text-decoration: underline;
301
+ text-underline-offset: 2px;
302
+ font-weight: 500;
303
+ }
304
+ .comp-screens a:hover { opacity: 0.8; }
305
+
306
+ /* Code snippet */
307
+ .comp-code {
308
+ position: relative;
309
+ margin-top: 0.5rem;
310
+ }
311
+ .comp-code pre {
312
+ background: var(--code-bg);
313
+ border: 1px solid var(--border);
314
+ border-radius: 6px;
315
+ padding: 0.625rem 0.75rem;
316
+ font-size: 0.6875rem;
317
+ font-family: 'SF Mono', 'Menlo', 'Monaco', monospace;
318
+ overflow-x: auto;
319
+ white-space: pre-wrap;
320
+ word-break: break-all;
321
+ max-height: 120px;
322
+ color: var(--text-2);
323
+ line-height: 1.6;
324
+ }
325
+ .copy-btn {
326
+ position: absolute;
327
+ top: 0.375rem;
328
+ right: 0.375rem;
329
+ background: var(--surface);
330
+ border: 1px solid var(--border);
331
+ border-radius: 4px;
332
+ padding: 0.2rem 0.4rem;
333
+ font-size: 0.625rem;
334
+ cursor: pointer;
335
+ color: var(--text-3);
336
+ opacity: 0;
337
+ transition: opacity 0.15s;
338
+ }
339
+ .comp-code:hover .copy-btn { opacity: 1; }
340
+ .copy-btn:hover { color: var(--accent); border-color: var(--accent); }
341
+
342
+ /* ── Design tokens section ─────────────────────── */
343
+ .tokens-grid {
344
+ display: grid;
345
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
346
+ gap: 1rem;
347
+ }
348
+ .token-group h3 {
349
+ font-size: 0.8125rem;
350
+ font-weight: 600;
351
+ margin-bottom: 0.5rem;
352
+ color: var(--text);
353
+ }
354
+ .color-swatch {
355
+ display: flex;
356
+ align-items: center;
357
+ gap: 0.5rem;
358
+ margin-bottom: 0.375rem;
359
+ }
360
+ .swatch-circle {
361
+ width: 24px;
362
+ height: 24px;
363
+ border-radius: 6px;
364
+ border: 1px solid var(--border);
365
+ flex-shrink: 0;
366
+ }
367
+ .swatch-label {
368
+ font-size: 0.75rem;
369
+ font-family: 'SF Mono', 'Menlo', monospace;
370
+ color: var(--text-2);
371
+ }
372
+ .token-list {
373
+ list-style: none;
374
+ font-size: 0.75rem;
375
+ color: var(--text-2);
376
+ }
377
+ .token-list li {
378
+ padding: 0.25rem 0;
379
+ border-bottom: 1px solid var(--border);
380
+ }
381
+ .token-list li:last-child { border-bottom: none; }
382
+
383
+ /* ── Catalog intro ─────────────────────────────── */
384
+ .catalog-intro {
385
+ margin-bottom: 2rem;
386
+ }
387
+ .catalog-intro h2 {
388
+ font-size: 1.125rem;
389
+ font-weight: 600;
390
+ color: var(--text);
391
+ margin-bottom: 0.375rem;
392
+ }
393
+ .catalog-intro p {
394
+ font-size: 0.8125rem;
395
+ color: var(--text-3);
396
+ line-height: 1.6;
397
+ }
398
+
399
+ /* ── Empty state ───────────────────────────────── */
400
+ .empty {
401
+ text-align: center;
402
+ padding: 3rem 1rem;
403
+ color: var(--text-3);
404
+ font-size: 0.875rem;
405
+ }
406
+
407
+ /* ── Canonical badge ───────────────────────────── */
408
+ .comp-card.canonical {
409
+ border-color: var(--accent);
410
+ box-shadow: 0 0 0 1px var(--accent);
411
+ }
412
+ .canonical-badge {
413
+ display: inline-block;
414
+ font-size: 0.625rem;
415
+ font-weight: 600;
416
+ background: var(--accent);
417
+ color: #fff;
418
+ padding: 0.1rem 0.4rem;
419
+ border-radius: 4px;
420
+ margin-left: 0.375rem;
421
+ vertical-align: middle;
422
+ }
423
+ .similarity-bar {
424
+ height: 4px;
425
+ border-radius: 2px;
426
+ background: var(--surface-2);
427
+ overflow: hidden;
428
+ margin-top: 0.375rem;
429
+ }
430
+ .similarity-fill {
431
+ height: 100%;
432
+ background: var(--accent);
433
+ border-radius: 2px;
434
+ }
435
+ .diff-text {
436
+ font-size: 0.6875rem;
437
+ color: var(--text-3);
438
+ margin-top: 0.25rem;
439
+ }
440
+
441
+ /* ── Style properties ──────────────────────────── */
442
+ .style-props {
443
+ display: flex;
444
+ flex-wrap: wrap;
445
+ gap: 0.25rem;
446
+ margin-top: 0.375rem;
447
+ }
448
+ .style-prop {
449
+ font-size: 0.625rem;
450
+ background: var(--surface-2);
451
+ border: 1px solid var(--border);
452
+ border-radius: 4px;
453
+ padding: 0.125rem 0.375rem;
454
+ color: var(--text-3);
455
+ font-family: 'SF Mono', 'Menlo', monospace;
456
+ }
457
+
458
+ /* ── Cluster group ────────────────────────────── */
459
+ .cluster-group {
460
+ margin-bottom: 1.5rem;
461
+ padding: 1rem;
462
+ background: var(--surface);
463
+ border: 1px solid var(--border);
464
+ border-radius: 12px;
465
+ }
466
+ .cluster-header {
467
+ display: flex;
468
+ align-items: center;
469
+ gap: 0.5rem;
470
+ margin-bottom: 0.75rem;
471
+ }
472
+ .cluster-context {
473
+ font-size: 0.6875rem;
474
+ font-weight: 500;
475
+ background: var(--surface-2);
476
+ border: 1px solid var(--border);
477
+ border-radius: 4px;
478
+ padding: 0.125rem 0.5rem;
479
+ color: var(--text-3);
480
+ }
481
+ .cluster-count {
482
+ font-size: 0.6875rem;
483
+ color: var(--text-3);
484
+ }
485
+ .cluster-grid {
486
+ display: grid;
487
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
488
+ gap: 0.75rem;
489
+ }
490
+
491
+ /* ── Already unified ──────────────────────────── */
492
+ .unified-section {
493
+ margin-top: 2rem;
494
+ }
495
+ .unified-toggle {
496
+ display: flex;
497
+ align-items: center;
498
+ gap: 0.5rem;
499
+ background: none;
500
+ border: 1px solid var(--border);
501
+ border-radius: 8px;
502
+ padding: 0.5rem 0.75rem;
503
+ cursor: pointer;
504
+ color: var(--text-2);
505
+ font-size: 0.8125rem;
506
+ font-weight: 500;
507
+ width: 100%;
508
+ text-align: left;
509
+ transition: background 0.15s;
510
+ }
511
+ .unified-toggle:hover { background: var(--surface-2); }
512
+ .unified-toggle .arrow { transition: transform 0.2s; }
513
+ .unified-toggle.open .arrow { transform: rotate(90deg); }
514
+ .unified-content { display: none; padding-top: 0.75rem; }
515
+ .unified-content.open { display: block; }
516
+ .unified-item {
517
+ display: flex;
518
+ align-items: center;
519
+ gap: 0.5rem;
520
+ padding: 0.375rem 0;
521
+ font-size: 0.75rem;
522
+ color: var(--text-2);
523
+ border-bottom: 1px solid var(--border);
524
+ }
525
+ .unified-item:last-child { border-bottom: none; }
526
+ .unified-check {
527
+ color: #22c55e;
528
+ font-weight: 600;
529
+ }
530
+ .screen-count-badge {
531
+ font-size: 0.625rem;
532
+ background: var(--surface-2);
533
+ border: 1px solid var(--border);
534
+ border-radius: 4px;
535
+ padding: 0.1rem 0.375rem;
536
+ color: var(--text-3);
537
+ }
538
+
539
+ /* ── Variant count badge ──────────────────────── */
540
+ .variant-badge {
541
+ font-size: 0.625rem;
542
+ font-weight: 500;
543
+ background: var(--accent-10);
544
+ color: var(--accent);
545
+ padding: 0.125rem 0.5rem;
546
+ border-radius: 99px;
547
+ margin-left: 0.375rem;
548
+ }
549
+
550
+ </style>
551
+ </head>
552
+ <body>
553
+ <!-- ── Loading overlay ──────────────────────────── -->
554
+ <div class="loading-overlay" id="loadingOverlay">
555
+ <div class="loading-spinner"></div>
556
+ <div class="loading-text">Loading component previews…</div>
557
+ </div>
558
+
559
+ <!-- ── Header ──────────────────────────────────── -->
560
+ <header class="header">
561
+ <div class="header-inner">
562
+ <div class="header-left">
563
+ <a href="index.html" class="back-btn" title="Back to gallery">
564
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M10 13L5 8l5-5"/></svg>
565
+ Back
566
+ </a>
567
+ <div class="header-divider"></div>
568
+ <span class="header-title">{{PROJECT_NAME}}</span>
569
+ <span class="header-badge">Component Catalog</span>
570
+ <span class="header-badge">v{{SKILL_VERSION}}</span>
571
+ </div>
572
+ <div class="header-actions">
573
+ <button class="theme-btn" id="themeToggle" title="Toggle light/dark">
574
+ <svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
575
+ <svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
576
+ </button>
577
+ </div>
578
+ </div>
579
+ </header>
580
+
581
+ <!-- ── Tabs ────────────────────────────────────── -->
582
+ <nav class="tabs" id="tabNav">
583
+ {{TABS_HTML}}
584
+ </nav>
585
+
586
+ <!-- ── Main content ────────────────────────────── -->
587
+ <main class="main" id="mainContent">
588
+ <div class="catalog-intro">
589
+ <h2>Component Catalog</h2>
590
+ <p>Components marked <strong>★ Canonical</strong> are the recommended versions.
591
+ Use the <strong>Copy</strong> button on any component's code to grab its HTML,
592
+ then edit your screen files and rebuild the showcase to update this catalog.</p>
593
+ </div>
594
+ {{CATALOG_HTML}}
595
+ </main>
596
+
597
+ <script>
598
+ // ── Theme ──────────────────────────────────────
599
+ const root = document.documentElement;
600
+ const stored = localStorage.getItem('catalog-theme');
601
+ if (stored) root.setAttribute('data-theme', stored);
602
+
603
+ document.getElementById('themeToggle').addEventListener('click', () => {
604
+ const next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
605
+ root.setAttribute('data-theme', next);
606
+ localStorage.setItem('catalog-theme', next);
607
+ });
608
+
609
+ // ── One-time Tailwind compilation ──────────────
610
+ // Instead of 184 iframes each loading + executing the Tailwind CDN,
611
+ // we compile ALL component classes in ONE hidden iframe, extract the
612
+ // generated CSS, and inject it as a <style> in every preview iframe.
613
+ // Result: 1 CDN request + 1 JIT compilation instead of 184.
614
+ const overlay = document.getElementById('loadingOverlay');
615
+ let _compiledHead = null; // { css, fontLinks } after compilation
616
+
617
+ function compileTailwindOnce() {
618
+ const firstIframe = document.querySelector('iframe[data-srcdoc]');
619
+ if (!firstIframe) return Promise.resolve(null);
620
+
621
+ const firstSrc = firstIframe.getAttribute('data-srcdoc');
622
+ if (!firstSrc.includes('tailwindcss')) return Promise.resolve(null);
623
+
624
+ // Extract <head> (CDN + config + fonts) from any iframe — all share the same
625
+ const headMatch = firstSrc.match(/<head>([\s\S]*?)<\/head>/i);
626
+ const head = headMatch ? headMatch[1] : '';
627
+
628
+ // Keep font <link> tags (tiny, cached) — strip CDN scripts
629
+ const fontLinks = (head.match(/<link[^>]*(?:fonts\.googleapis|fonts\.gstatic|preconnect)[^>]*>/gi) || []).join('\n');
630
+
631
+ // Collect ALL body HTML to ensure Tailwind generates CSS for every utility class
632
+ const allBodies = [];
633
+ document.querySelectorAll('iframe[data-srcdoc]').forEach(function(el) {
634
+ const src = el.getAttribute('data-srcdoc');
635
+ const bm = src.match(/<body[^>]*>([\s\S]*)<\/body>/i);
636
+ if (bm) allBodies.push(bm[1]);
637
+ });
638
+
639
+ return new Promise(function(resolve) {
640
+ var compiler = document.createElement('iframe');
641
+ compiler.style.cssText = 'position:fixed;width:0;height:0;border:none;opacity:0;pointer-events:none;';
642
+ document.body.appendChild(compiler);
643
+
644
+ // One big document with ALL component HTML — Tailwind scans it once
645
+ compiler.srcdoc = '<!DOCTYPE html><html><head>' + head +
646
+ '</head><body style="display:none">' + allBodies.join('\n') + '</body></html>';
647
+
648
+ var resolved = false;
649
+
650
+ compiler.addEventListener('load', function() {
651
+ // Poll for Tailwind's generated <style> elements
652
+ var attempts = 0;
653
+ var check = setInterval(function() {
654
+ attempts++;
655
+ try {
656
+ var styles = compiler.contentDocument.querySelectorAll('style');
657
+ if (styles.length > 0 || attempts > 40) {
658
+ clearInterval(check);
659
+ var css = '';
660
+ if (styles.length) styles.forEach(function(s) { css += s.textContent + '\n'; });
661
+ compiler.remove();
662
+ if (!resolved) { resolved = true; resolve({ css: css, fontLinks: fontLinks }); }
663
+ }
664
+ } catch(e) {
665
+ clearInterval(check);
666
+ compiler.remove();
667
+ if (!resolved) { resolved = true; resolve(null); }
668
+ }
669
+ }, 150);
670
+ }, { once: true });
671
+
672
+ // Safety: don't block forever
673
+ setTimeout(function() {
674
+ if (!resolved) { resolved = true; try { compiler.remove(); } catch(e) {} resolve(null); }
675
+ }, 12000);
676
+ });
677
+ }
678
+
679
+ function optimizeSrcdoc(srcdoc) {
680
+ if (!_compiledHead || !_compiledHead.css) return srcdoc;
681
+ // Replace <head> (with CDN scripts) with compiled CSS + font links only
682
+ return srcdoc.replace(
683
+ /<head>[\s\S]*?<\/head>/i,
684
+ '<head>' + _compiledHead.fontLinks + '<style>' + _compiledHead.css + '</style></head>'
685
+ );
686
+ }
687
+
688
+ // ── Lazy iframe loading ────────────────────────
689
+ function loadIframesInElement(container) {
690
+ var iframes = container.querySelectorAll('iframe[data-srcdoc]:not([data-loaded])');
691
+ if (!iframes.length) return Promise.resolve();
692
+
693
+ var loaded = 0;
694
+ var total = iframes.length;
695
+ return new Promise(function(resolve) {
696
+ var done = false;
697
+ function finish() { if (!done) { done = true; resolve(); } }
698
+
699
+ iframes.forEach(function(iframe) {
700
+ iframe.setAttribute('data-loaded', '1');
701
+ iframe.srcdoc = optimizeSrcdoc(iframe.getAttribute('data-srcdoc'));
702
+ iframe.addEventListener('load', function() {
703
+ loaded++;
704
+ if (loaded >= total) finish();
705
+ }, { once: true });
706
+ });
707
+ // Don't block forever — some environments (Live Server) may not fire load for srcdoc iframes
708
+ setTimeout(finish, 5000);
709
+ });
710
+ }
711
+
712
+ function loadIframesProgressively(container) {
713
+ var iframes = Array.from(container.querySelectorAll('iframe[data-srcdoc]:not([data-loaded])'));
714
+ if (!iframes.length) return;
715
+ var BATCH = 16;
716
+ var i = 0;
717
+ function nextBatch() {
718
+ var batch = iframes.slice(i, i + BATCH);
719
+ if (!batch.length) return;
720
+ batch.forEach(function(iframe) {
721
+ iframe.setAttribute('data-loaded', '1');
722
+ iframe.srcdoc = optimizeSrcdoc(iframe.getAttribute('data-srcdoc'));
723
+ });
724
+ i += BATCH;
725
+ if (i < iframes.length) setTimeout(nextBatch, 60);
726
+ }
727
+ nextBatch();
728
+ }
729
+
730
+ // ── Tabs ───────────────────────────────────────
731
+ const tabs = document.querySelectorAll('.tab');
732
+ const sections = document.querySelectorAll('.section[data-category]');
733
+
734
+ tabs.forEach(function(tab) {
735
+ tab.addEventListener('click', function() {
736
+ var cat = tab.dataset.category;
737
+ tabs.forEach(function(t) { t.classList.remove('active'); });
738
+ tab.classList.add('active');
739
+
740
+ sections.forEach(function(s) {
741
+ s.style.display = (cat === 'all' || s.dataset.category === cat) ? '' : 'none';
742
+ });
743
+
744
+ // Lazy-load iframes for newly visible sections
745
+ if (cat === 'all') {
746
+ loadIframesProgressively(document.getElementById('mainContent'));
747
+ } else {
748
+ sections.forEach(function(s) {
749
+ if (s.dataset.category === cat) loadIframesInElement(s);
750
+ });
751
+ }
752
+ });
753
+ });
754
+
755
+ // ── Search (if search box exists) ────────────────
756
+ var searchInput = document.getElementById('searchInput');
757
+ if (searchInput) {
758
+ searchInput.addEventListener('input', function() {
759
+ var q = searchInput.value.toLowerCase().trim();
760
+ document.querySelectorAll('.comp-card').forEach(function(card) {
761
+ var text = (card.dataset.name || '').toLowerCase() + ' ' + (card.dataset.variant || '').toLowerCase();
762
+ card.style.display = (!q || text.includes(q)) ? '' : 'none';
763
+ });
764
+ if (q) {
765
+ sections.forEach(function(s) { s.style.display = ''; });
766
+ sections.forEach(function(s) {
767
+ var visible = s.querySelectorAll('.comp-card:not([style*="display: none"])');
768
+ s.style.display = visible.length ? '' : 'none';
769
+ });
770
+ loadIframesProgressively(document.getElementById('mainContent'));
771
+ }
772
+ });
773
+ }
774
+
775
+ // ── Copy code ──────────────────────────────────
776
+ document.addEventListener('click', function(e) {
777
+ if (e.target.classList.contains('copy-btn')) {
778
+ var pre = e.target.parentElement.querySelector('pre');
779
+ if (pre) {
780
+ navigator.clipboard.writeText(pre.textContent).then(function() {
781
+ var orig = e.target.textContent;
782
+ e.target.textContent = '✓';
783
+ setTimeout(function() { e.target.textContent = orig; }, 1500);
784
+ });
785
+ }
786
+ }
787
+ });
788
+
789
+ // ── Unified toggle ──────────────────────────────
790
+ document.querySelectorAll('.unified-toggle').forEach(function(btn) {
791
+ btn.addEventListener('click', function() {
792
+ btn.classList.toggle('open');
793
+ var content = btn.nextElementSibling;
794
+ if (content) content.classList.toggle('open');
795
+ });
796
+ });
797
+
798
+ // ── Init: compile Tailwind once → activate first tab → hide overlay ──
799
+ function hideOverlay() {
800
+ if (overlay && overlay.parentNode) {
801
+ overlay.classList.add('hidden');
802
+ setTimeout(function() { if (overlay.parentNode) overlay.remove(); }, 400);
803
+ }
804
+ }
805
+
806
+ const firstTab = document.querySelector('.tab');
807
+ if (firstTab) {
808
+ // Safety: never block on loading overlay forever (Live Server / CSP issues)
809
+ var overlayTimeout = setTimeout(hideOverlay, 15000);
810
+
811
+ compileTailwindOnce().then(function(result) {
812
+ _compiledHead = result;
813
+
814
+ // Activate first tab visually
815
+ var cat = firstTab.dataset.category;
816
+ firstTab.classList.add('active');
817
+ sections.forEach(function(s) {
818
+ s.style.display = (cat === 'all' || s.dataset.category === cat) ? '' : 'none';
819
+ });
820
+
821
+ // Load iframes for first tab, then hide overlay
822
+ var target = document.getElementById('mainContent');
823
+ if (cat !== 'all') {
824
+ for (var i = 0; i < sections.length; i++) {
825
+ if (sections[i].dataset.category === cat) { target = sections[i]; break; }
826
+ }
827
+ }
828
+ loadIframesInElement(target).then(function() {
829
+ clearTimeout(overlayTimeout);
830
+ hideOverlay();
831
+ });
832
+ }).catch(function() {
833
+ clearTimeout(overlayTimeout);
834
+ hideOverlay();
835
+ });
836
+ } else {
837
+ hideOverlay();
838
+ }
839
+ </script>
840
+ </body>
841
+ </html>