@sarjallab09/figma-intelligence 1.0.1 → 1.2.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 (297) hide show
  1. package/README.md +67 -36
  2. package/dist/bin/cli.js +2 -0
  3. package/dist/design-bridge/bridge.js +2 -0
  4. package/dist/figma-bridge-plugin/bridge-relay.js +2 -0
  5. package/dist/figma-bridge-plugin/code.js +1 -0
  6. package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package-lock.json +0 -3
  7. package/dist/figma-bridge-plugin/ui.html +4970 -0
  8. package/dist/figma-intelligence-layer/dist/index.js +2 -0
  9. package/dist/scripts/clean-existing-chunks.js +2 -0
  10. package/dist/scripts/connect-ai-tool.js +2 -0
  11. package/dist/scripts/convert-hub-pdfs.js +2 -0
  12. package/dist/scripts/figma-mcp-status.js +2 -0
  13. package/dist/scripts/register-codex-mcp.js +2 -0
  14. package/dist/scripts/test-copilot-chat.js +2 -0
  15. package/package.json +11 -8
  16. package/bin/cli.js +0 -859
  17. package/design-bridge/bridge.js +0 -196
  18. package/design-bridge/lib/assets.js +0 -367
  19. package/design-bridge/lib/prompt.js +0 -85
  20. package/design-bridge/lib/server.js +0 -66
  21. package/design-bridge/lib/stitch.js +0 -37
  22. package/design-bridge/lib/tokens.js +0 -82
  23. package/design-bridge/package-lock.json +0 -579
  24. package/figma-bridge-plugin/README.md +0 -97
  25. package/figma-bridge-plugin/anthropic-chat-runner.js +0 -192
  26. package/figma-bridge-plugin/bridge-relay.js +0 -2363
  27. package/figma-bridge-plugin/chat-runner.js +0 -459
  28. package/figma-bridge-plugin/code.js +0 -1528
  29. package/figma-bridge-plugin/codex-runner.js +0 -505
  30. package/figma-bridge-plugin/component-schemas.js +0 -110
  31. package/figma-bridge-plugin/content-context.js +0 -869
  32. package/figma-bridge-plugin/create-button.js +0 -216
  33. package/figma-bridge-plugin/gemini-cli-runner.js +0 -291
  34. package/figma-bridge-plugin/gemini-runner.js +0 -187
  35. package/figma-bridge-plugin/html-to-figma.js +0 -927
  36. package/figma-bridge-plugin/knowledge-hub/.gitkeep +0 -0
  37. package/figma-bridge-plugin/knowledge-hub/uspec-references/anatomy-spec.md +0 -159
  38. package/figma-bridge-plugin/knowledge-hub/uspec-references/api-spec.md +0 -162
  39. package/figma-bridge-plugin/knowledge-hub/uspec-references/color-spec.md +0 -148
  40. package/figma-bridge-plugin/knowledge-hub/uspec-references/full-spec-template.md +0 -314
  41. package/figma-bridge-plugin/knowledge-hub/uspec-references/property-spec.md +0 -175
  42. package/figma-bridge-plugin/knowledge-hub/uspec-references/screen-reader-spec.md +0 -180
  43. package/figma-bridge-plugin/knowledge-hub/uspec-references/structure-spec.md +0 -165
  44. package/figma-bridge-plugin/perplexity-runner.js +0 -188
  45. package/figma-bridge-plugin/references/SKILL.md +0 -178
  46. package/figma-bridge-plugin/references/anatomy-spec.md +0 -159
  47. package/figma-bridge-plugin/references/api-spec.md +0 -162
  48. package/figma-bridge-plugin/references/color-spec.md +0 -148
  49. package/figma-bridge-plugin/references/full-spec-template.md +0 -314
  50. package/figma-bridge-plugin/references/property-spec.md +0 -175
  51. package/figma-bridge-plugin/references/screen-reader-spec.md +0 -180
  52. package/figma-bridge-plugin/references/structure-spec.md +0 -165
  53. package/figma-bridge-plugin/shared-prompt-config.js +0 -604
  54. package/figma-bridge-plugin/spec-helpers/build-table.js +0 -269
  55. package/figma-bridge-plugin/spec-helpers/classify-elements.js +0 -189
  56. package/figma-bridge-plugin/spec-helpers/index.js +0 -35
  57. package/figma-bridge-plugin/spec-helpers/parse-figma-link.js +0 -49
  58. package/figma-bridge-plugin/spec-helpers/position-markers.js +0 -158
  59. package/figma-bridge-plugin/stitch-auth.js +0 -322
  60. package/figma-bridge-plugin/stitch-runner.js +0 -1427
  61. package/figma-bridge-plugin/token-resolver.js +0 -107
  62. package/figma-bridge-plugin/ui.html +0 -4467
  63. package/figma-intelligence-layer/.env.example +0 -39
  64. package/figma-intelligence-layer/docs/local-image-generation.md +0 -60
  65. package/figma-intelligence-layer/examples/comfyui-workflow-template.example.json +0 -101
  66. package/figma-intelligence-layer/jest.config.js +0 -14
  67. package/figma-intelligence-layer/mcp-config.json +0 -19
  68. package/figma-intelligence-layer/package-lock.json +0 -5892
  69. package/figma-intelligence-layer/scripts/setup-comfyui-local.sh +0 -67
  70. package/figma-intelligence-layer/scripts/start-comfyui.sh +0 -33
  71. package/figma-intelligence-layer/src/index.ts +0 -2233
  72. package/figma-intelligence-layer/src/shared/auto-layout-validator.ts +0 -404
  73. package/figma-intelligence-layer/src/shared/cache.ts +0 -187
  74. package/figma-intelligence-layer/src/shared/color-operations.ts +0 -533
  75. package/figma-intelligence-layer/src/shared/color-utils.ts +0 -138
  76. package/figma-intelligence-layer/src/shared/component-script-builder.ts +0 -413
  77. package/figma-intelligence-layer/src/shared/component-templates.ts +0 -2767
  78. package/figma-intelligence-layer/src/shared/concept-taxonomy.ts +0 -694
  79. package/figma-intelligence-layer/src/shared/decision-log.ts +0 -128
  80. package/figma-intelligence-layer/src/shared/design-system-context.ts +0 -568
  81. package/figma-intelligence-layer/src/shared/design-system-intelligence.ts +0 -131
  82. package/figma-intelligence-layer/src/shared/design-system-matcher.ts +0 -184
  83. package/figma-intelligence-layer/src/shared/design-system-normalizers.ts +0 -196
  84. package/figma-intelligence-layer/src/shared/design-system-tokens.ts +0 -295
  85. package/figma-intelligence-layer/src/shared/dtcg-validator.ts +0 -530
  86. package/figma-intelligence-layer/src/shared/enrichment-pipeline.ts +0 -671
  87. package/figma-intelligence-layer/src/shared/figma-bridge.ts +0 -1408
  88. package/figma-intelligence-layer/src/shared/font-config.ts +0 -126
  89. package/figma-intelligence-layer/src/shared/icon-catalog.ts +0 -360
  90. package/figma-intelligence-layer/src/shared/icon-fetch.ts +0 -80
  91. package/figma-intelligence-layer/src/shared/prototype-script-builder.ts +0 -162
  92. package/figma-intelligence-layer/src/shared/response-compression.ts +0 -440
  93. package/figma-intelligence-layer/src/shared/semantic-token-catalog.ts +0 -324
  94. package/figma-intelligence-layer/src/shared/token-binder.ts +0 -505
  95. package/figma-intelligence-layer/src/shared/token-math.ts +0 -427
  96. package/figma-intelligence-layer/src/shared/token-naming.ts +0 -468
  97. package/figma-intelligence-layer/src/shared/token-utils.ts +0 -420
  98. package/figma-intelligence-layer/src/shared/types.ts +0 -346
  99. package/figma-intelligence-layer/src/shared/typography-presets.ts +0 -94
  100. package/figma-intelligence-layer/src/shared/unsplash.ts +0 -165
  101. package/figma-intelligence-layer/src/shared/vision-client.ts +0 -607
  102. package/figma-intelligence-layer/src/shared/vision-provider-anthropic.ts +0 -334
  103. package/figma-intelligence-layer/src/shared/vision-provider-openai.ts +0 -446
  104. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-handler.ts +0 -782
  105. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-renderer.ts +0 -496
  106. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotation-kit.ts +0 -230
  107. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/colorblind-sim.ts +0 -66
  108. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/index.ts +0 -810
  109. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-analyzer.ts +0 -1191
  110. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-figma-page.ts +0 -1346
  111. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-handler.ts +0 -148
  112. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-figma-page.ts +0 -499
  113. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-report.ts +0 -910
  114. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-checker.ts +0 -989
  115. package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-criteria.ts +0 -1160
  116. package/figma-intelligence-layer/src/tools/phase1-vision/design-from-ref/index.ts +0 -424
  117. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/component-recognizer.ts +0 -38
  118. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/ds-matcher.ts +0 -111
  119. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/font-matcher.ts +0 -114
  120. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/icon-resolver.ts +0 -103
  121. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/index.ts +0 -1060
  122. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/layout-segmenter.ts +0 -18
  123. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/token-inferencer.ts +0 -39
  124. package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/vision-pipeline.ts +0 -58
  125. package/figma-intelligence-layer/src/tools/phase1-vision/sketch-to-design/index.ts +0 -298
  126. package/figma-intelligence-layer/src/tools/phase1-vision/visual-audit/index.ts +0 -197
  127. package/figma-intelligence-layer/src/tools/phase2-accuracy/component-audit/index.ts +0 -494
  128. package/figma-intelligence-layer/src/tools/phase2-accuracy/intent-translator/index.ts +0 -356
  129. package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/container-patterns.ts +0 -123
  130. package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/index.ts +0 -663
  131. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/built-in-rules.yaml +0 -56
  132. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/index.ts +0 -614
  133. package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/rule-engine.ts +0 -113
  134. package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/color-theory.ts +0 -178
  135. package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/index.ts +0 -470
  136. package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/index.ts +0 -429
  137. package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/token-override-maps.ts +0 -226
  138. package/figma-intelligence-layer/src/tools/phase3-generation/ai-image-insert/index.ts +0 -535
  139. package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/index.ts +0 -660
  140. package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/pattern-fingerprints.ts +0 -209
  141. package/figma-intelligence-layer/src/tools/phase3-generation/composition-builder/index.ts +0 -540
  142. package/figma-intelligence-layer/src/tools/phase3-generation/figma-animated-build.ts +0 -391
  143. package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/index.ts +0 -2019
  144. package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/screen-templates.ts +0 -131
  145. package/figma-intelligence-layer/src/tools/phase3-generation/prototype-map/index.ts +0 -381
  146. package/figma-intelligence-layer/src/tools/phase3-generation/prototype-wire/index.ts +0 -565
  147. package/figma-intelligence-layer/src/tools/phase3-generation/swarm-build/index.ts +0 -764
  148. package/figma-intelligence-layer/src/tools/phase3-generation/system-drift/index.ts +0 -535
  149. package/figma-intelligence-layer/src/tools/phase3-generation/unsplash-search/index.ts +0 -84
  150. package/figma-intelligence-layer/src/tools/phase3-generation/url-to-frame/index.ts +0 -401
  151. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/css-animations.ts +0 -68
  152. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/framer-motion.ts +0 -78
  153. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/swift-animations.ts +0 -93
  154. package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/index.ts +0 -596
  155. package/figma-intelligence-layer/src/tools/phase4-sync/ci-check/index.ts +0 -462
  156. package/figma-intelligence-layer/src/tools/phase4-sync/export-tokens/index.ts +0 -1470
  157. package/figma-intelligence-layer/src/tools/phase4-sync/generate-component-code/index.ts +0 -829
  158. package/figma-intelligence-layer/src/tools/phase4-sync/handoff-spec/index.ts +0 -702
  159. package/figma-intelligence-layer/src/tools/phase4-sync/icon-library-sync/index.ts +0 -483
  160. package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/index.ts +0 -501
  161. package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/storybook-parser.ts +0 -106
  162. package/figma-intelligence-layer/src/tools/phase4-sync/watch-docs/index.ts +0 -676
  163. package/figma-intelligence-layer/src/tools/phase4-sync/webhook-listener/index.ts +0 -560
  164. package/figma-intelligence-layer/src/tools/phase5-governance/apg-doc/index.ts +0 -1043
  165. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/component-detection.ts +0 -620
  166. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/anatomy.ts +0 -331
  167. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/color-tokens.ts +0 -77
  168. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/properties.ts +0 -54
  169. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/snapshot.ts +0 -287
  170. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/spacing.ts +0 -71
  171. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/states.ts +0 -43
  172. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/typography.ts +0 -71
  173. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/index.ts +0 -221
  174. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/_default.ts +0 -166
  175. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/accordion.ts +0 -232
  176. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/alert.ts +0 -234
  177. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar-group.ts +0 -270
  178. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar.ts +0 -249
  179. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/badge.ts +0 -231
  180. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/banner.ts +0 -293
  181. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/breadcrumb.ts +0 -240
  182. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/button.ts +0 -243
  183. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/calendar.ts +0 -307
  184. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/card.ts +0 -143
  185. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/checkbox.ts +0 -227
  186. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/chip.ts +0 -233
  187. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/combobox.ts +0 -282
  188. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/datepicker.ts +0 -276
  189. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/divider.ts +0 -223
  190. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/drawer.ts +0 -255
  191. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/dropdown-menu.ts +0 -289
  192. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/empty-state.ts +0 -261
  193. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/file-uploader.ts +0 -290
  194. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/form.ts +0 -265
  195. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/grid.ts +0 -238
  196. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/icon.ts +0 -255
  197. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/index.ts +0 -128
  198. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-edit.ts +0 -286
  199. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-message.ts +0 -255
  200. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/input.ts +0 -330
  201. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/link.ts +0 -247
  202. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/list.ts +0 -250
  203. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/menu.ts +0 -247
  204. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/modal.ts +0 -144
  205. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navbar.ts +0 -264
  206. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navigation.ts +0 -251
  207. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/number-input.ts +0 -261
  208. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/pagination.ts +0 -248
  209. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/popover.ts +0 -270
  210. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/progress.ts +0 -251
  211. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/radio.ts +0 -142
  212. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/range-slider.ts +0 -282
  213. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/rating.ts +0 -250
  214. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/search.ts +0 -258
  215. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/segmented-control.ts +0 -265
  216. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/select.ts +0 -319
  217. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/skeleton.ts +0 -256
  218. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/slider.ts +0 -232
  219. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/spinner.ts +0 -239
  220. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/status-dot.ts +0 -252
  221. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/stepper.ts +0 -270
  222. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/table.ts +0 -244
  223. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tabs.ts +0 -143
  224. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tag.ts +0 -243
  225. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/textarea.ts +0 -259
  226. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/time-picker.ts +0 -293
  227. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toast.ts +0 -144
  228. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toggle.ts +0 -289
  229. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toolbar.ts +0 -267
  230. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tooltip.ts +0 -232
  231. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/treeview.ts +0 -257
  232. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/typography.ts +0 -319
  233. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/legacy-compat.ts +0 -121
  234. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/anatomy-diagram.ts +0 -430
  235. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/figma-page.ts +0 -312
  236. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/json.ts +0 -129
  237. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/markdown.ts +0 -78
  238. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/visual-doc.ts +0 -2333
  239. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/accessibility.ts +0 -100
  240. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/anatomy.ts +0 -32
  241. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/color-tokens.ts +0 -59
  242. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/content-guidance.ts +0 -18
  243. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/design-tokens.ts +0 -53
  244. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/interaction-rules.ts +0 -19
  245. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/overview.ts +0 -91
  246. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/properties-api.ts +0 -71
  247. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/qa-criteria.ts +0 -19
  248. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/related-components.ts +0 -110
  249. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/responsive.ts +0 -19
  250. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/size-specs.ts +0 -67
  251. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/spacing-structure.ts +0 -58
  252. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/state-specs.ts +0 -79
  253. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/states.ts +0 -50
  254. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/type-hierarchy.ts +0 -33
  255. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/typography.ts +0 -55
  256. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/usage-guidelines.ts +0 -73
  257. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/variants.ts +0 -81
  258. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/types.ts +0 -409
  259. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/index.ts +0 -198
  260. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/renderer.ts +0 -701
  261. package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/types.ts +0 -88
  262. package/figma-intelligence-layer/src/tools/phase5-governance/decision-log/index.ts +0 -135
  263. package/figma-intelligence-layer/src/tools/phase5-governance/design-decision-log/index.ts +0 -491
  264. package/figma-intelligence-layer/src/tools/phase5-governance/ds-primitives/index.ts +0 -416
  265. package/figma-intelligence-layer/src/tools/phase5-governance/ds-scaffolder/index.ts +0 -722
  266. package/figma-intelligence-layer/src/tools/phase5-governance/ds-variables/index.ts +0 -449
  267. package/figma-intelligence-layer/src/tools/phase5-governance/health-report/index.ts +0 -393
  268. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/index.ts +0 -406
  269. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/figma-page.ts +0 -292
  270. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/json.ts +0 -24
  271. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/markdown.ts +0 -172
  272. package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/naming-guide.ts +0 -409
  273. package/figma-intelligence-layer/src/tools/phase5-governance/token-analytics/index.ts +0 -594
  274. package/figma-intelligence-layer/src/tools/phase5-governance/token-docs/index.ts +0 -710
  275. package/figma-intelligence-layer/src/tools/phase5-governance/token-migrate/index.ts +0 -458
  276. package/figma-intelligence-layer/src/tools/phase5-governance/token-naming/index.ts +0 -134
  277. package/figma-intelligence-layer/tests/apg-doc.test.ts +0 -101
  278. package/figma-intelligence-layer/tests/design-system-context.test.ts +0 -152
  279. package/figma-intelligence-layer/tests/design-system-matcher.test.ts +0 -144
  280. package/figma-intelligence-layer/tests/figma-bridge.test.ts +0 -83
  281. package/figma-intelligence-layer/tests/generate-image-and-insert.test.ts +0 -56
  282. package/figma-intelligence-layer/tests/screen-cloner-regression.test.ts +0 -69
  283. package/figma-intelligence-layer/tests/smoke.test.ts +0 -174
  284. package/figma-intelligence-layer/tests/spec-generator.test.ts +0 -127
  285. package/figma-intelligence-layer/tests/token-migrate.test.ts +0 -21
  286. package/figma-intelligence-layer/tests/token-naming.test.ts +0 -30
  287. package/figma-intelligence-layer/tsconfig.json +0 -19
  288. package/scripts/clean-existing-chunks.js +0 -179
  289. package/scripts/connect-ai-tool.js +0 -490
  290. package/scripts/convert-hub-pdfs.js +0 -425
  291. package/scripts/figma-mcp-status.js +0 -349
  292. package/scripts/register-codex-mcp.js +0 -96
  293. /package/{design-bridge → dist/design-bridge}/.env.example +0 -0
  294. /package/{design-bridge → dist/design-bridge}/package.json +0 -0
  295. /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/manifest.json +0 -0
  296. /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package.json +0 -0
  297. /package/{figma-intelligence-layer → dist/figma-intelligence-layer}/package.json +0 -0
@@ -1,2333 +0,0 @@
1
- /**
2
- * Visual Doc renderer — Enterprise exhibit-based component spec as a Figma page.
3
- *
4
- * Produces Zeroheight/Specify-level spec pages with:
5
- * - 1343px wide white card sections with 48px gaps
6
- * - Exhibit pattern: gray artwork (350×248) + content metadata
7
- * - Orange (#C54600) anatomy markers with type icons
8
- * - Green/orange/blue spacing overlays
9
- * - Per-variant live instance exhibits grouped by axis
10
- * - DS-aware font + palette integration via font-config / token-binder
11
- *
12
- * IMPORTANT Figma Plugin API pattern:
13
- * layoutSizingHorizontal = "FILL" only works AFTER the node is appended
14
- * to an auto-layout parent. Always: appendChild() first, then set sizing.
15
- */
16
- import { getBridge } from "../../../../shared/figma-bridge.js";
17
- import type { ComponentSpec, SpecSectionContent, ClassifiedElement } from "../types.js";
18
- import { computeMarkerPositions } from "./anatomy-diagram.js";
19
-
20
- // ── Design Tokens ─────────────────────────────────────────────────────────
21
-
22
- const PAGE_W = 1343;
23
- const PAD = 64;
24
- const SECTION_GAP = 48;
25
- const CARD_PAD = 64;
26
- const CARD_ITEM_GAP = 64;
27
- const CONTENT_W = PAGE_W - PAD * 2; // 1215
28
-
29
- // Exhibit artwork dimensions
30
- const ARTWORK_W = 350;
31
- const ARTWORK_H = 160;
32
- const EXHIBIT_GAP = 64;
33
-
34
- const C = {
35
- textMain: "{r:0,g:0,b:0}", // #000000
36
- textMuted: "{r:0.42,g:0.42,b:0.42}", // #6B6B6B
37
- bgCard: "{r:0.949,g:0.949,b:0.949}", // #F2F2F2
38
- bgPage: "{r:1,g:1,b:1}", // #FFFFFF
39
- border: "{r:0.898,g:0.906,b:0.922}", // #E5E7EB
40
- success: "{r:0.086,g:0.639,b:0.290}", // #16A34A
41
- error: "{r:0.863,g:0.149,b:0.149}", // #DC2626
42
- white: "{r:1,g:1,b:1}",
43
- // Spec annotation colors
44
- markerOrange: "{r:0.773,g:0.275,b:0}", // #C54600
45
- padGreen: "{r:0,g:0.490,b:0}", // #007D00
46
- gapOrange: "{r:0.773,g:0.275,b:0}", // #C54600
47
- sizeBlue: "{r:0.051,g:0.412,b:0.831}", // #0D69D4
48
- };
49
-
50
- type Bridge = {
51
- execute(code: string): Promise<{ success: boolean; result?: unknown; error?: string }>;
52
- };
53
-
54
- function esc(s: string): string { return JSON.stringify(s); }
55
-
56
- // ── Font loader (Inter with fallbacks) ────────────────────────────────────
57
-
58
- const FL = `
59
- var F={};
60
- async function lf(k,a){for(var i=0;i<a.length;i++){try{await figma.loadFontAsync(a[i]);F[k]=a[i];return}catch(e){}}F[k]={family:"Arial",style:"Regular"};try{await figma.loadFontAsync(F[k])}catch(e){}}
61
- await lf("b",[{family:"Inter",style:"Bold"},{family:"Roboto",style:"Bold"}]);
62
- await lf("sb",[{family:"Inter",style:"SemiBold"},{family:"Inter",style:"Medium"},{family:"Roboto",style:"Medium"}]);
63
- await lf("r",[{family:"Inter",style:"Regular"},{family:"Roboto",style:"Regular"}]);
64
- await lf("mono",[{family:"SF Mono",style:"Regular"},{family:"Roboto Mono",style:"Regular"},{family:"Courier New",style:"Regular"}]);
65
- `;
66
-
67
- // ── Helpers ───────────────────────────────────────────────────────────────
68
-
69
- function spacerJS(h: number, parent: string = "m"): string {
70
- return `
71
- (function(){
72
- var sp=figma.createFrame();
73
- sp.resize(4,${h});sp.fills=[];
74
- ${parent}.appendChild(sp);
75
- sp.layoutSizingHorizontal="FILL";
76
- })();`;
77
- }
78
-
79
- // Helper: create a text node, append to parent, then set FILL sizing
80
- function textFillJS(
81
- varName: string,
82
- parent: string,
83
- fontKey: string,
84
- fontSize: number,
85
- lineH: number,
86
- charsExpr: string,
87
- colorExpr: string,
88
- extra: string = "",
89
- ): string {
90
- return `
91
- var ${varName}=figma.createText();
92
- ${varName}.fontName=F.${fontKey};
93
- ${varName}.fontSize=${fontSize};
94
- ${varName}.lineHeight={unit:"PIXELS",value:${lineH}};
95
- ${varName}.characters=${charsExpr};
96
- ${varName}.fills=[{type:"SOLID",color:${colorExpr}}];
97
- ${extra}
98
- ${varName}.textAutoResize="HEIGHT";
99
- ${parent}.appendChild(${varName});
100
- ${varName}.layoutSizingHorizontal="FILL";`;
101
- }
102
-
103
- // ── Deep style extractor (injected into Figma script for variants) ────────
104
-
105
- const EXTRACT_STYLE_FN = `
106
- function extractStyle(node){
107
- var info={fills:[],texts:[],padding:null,radius:null,w:Math.round(node.width),h:Math.round(node.height)};
108
- if(Array.isArray(node.fills)){
109
- for(var fi=0;fi<node.fills.length;fi++){
110
- var p=node.fills[fi];
111
- if(p.type==="SOLID"&&p.visible!==false&&p.color){
112
- var r=Math.round(p.color.r*255),g=Math.round(p.color.g*255),b=Math.round(p.color.b*255);
113
- info.fills.push("#"+[r,g,b].map(function(v){return v.toString(16).padStart(2,"0")}).join(""));
114
- }
115
- }
116
- }
117
- if(typeof node.cornerRadius==="number"&&node.cornerRadius>0)info.radius=node.cornerRadius;
118
- if("paddingTop" in node){
119
- var pt=node.paddingTop||0,pr=node.paddingRight||0,pb=node.paddingBottom||0,pl=node.paddingLeft||0;
120
- if(pt+pr+pb+pl>0)info.padding=pt+", "+pr+", "+pb+", "+pl;
121
- }
122
- var q=("children" in node)?node.children.slice(0,12):[];
123
- var depth=0;
124
- while(q.length>0&&depth<3){
125
- var next=[];depth++;
126
- for(var qi=0;qi<q.length;qi++){
127
- var c=q[qi];
128
- if(c.type==="TEXT"){
129
- var fn=c.fontName;
130
- var family=(fn&&fn!==figma.mixed)?fn.family:"Mixed";
131
- var style=(fn&&fn!==figma.mixed)?fn.style:"";
132
- var fs=(typeof c.fontSize==="number")?c.fontSize:0;
133
- info.texts.push(family+" "+style+" "+fs);
134
- }
135
- if(Array.isArray(c.fills)){
136
- for(var cfi=0;cfi<c.fills.length;cfi++){
137
- var cp=c.fills[cfi];
138
- if(cp.type==="SOLID"&&cp.visible!==false&&cp.color){
139
- var cr=Math.round(cp.color.r*255),cg=Math.round(cp.color.g*255),cb=Math.round(cp.color.b*255);
140
- var hex="#"+[cr,cg,cb].map(function(v){return v.toString(16).padStart(2,"0")}).join("");
141
- if(info.fills.indexOf(hex)===-1)info.fills.push(hex);
142
- }
143
- }
144
- }
145
- if("children" in c&&c.children)for(var sci=0;sci<Math.min(c.children.length,8);sci++)next.push(c.children[sci]);
146
- }
147
- q=next;
148
- }
149
- var seen={};info.texts=info.texts.filter(function(t){if(seen[t])return false;seen[t]=true;return true;});
150
- return info;
151
- }
152
- `;
153
-
154
- // ══════════════════════════════════════════════════════════════════════════
155
- // PHASE 1: LAYOUT FOUNDATION — Card-based sections
156
- // ══════════════════════════════════════════════════════════════════════════
157
-
158
- async function initPage(bridge: Bridge, pageName: string): Promise<{ pageId: string; mId: string }> {
159
- const r = await bridge.execute(`
160
- (async () => {
161
- await figma.loadAllPagesAsync();
162
- var nm=${esc(pageName)};
163
- var mm=figma.root.children.filter(function(p){return p.name===nm;});
164
- var pg;
165
- if(mm.length>0){pg=mm[0];for(var d=1;d<mm.length;d++)mm[d].remove();}
166
- else{pg=figma.createPage();}
167
- pg.name=nm;
168
- await figma.setCurrentPageAsync(pg);
169
- var ch=pg.children.slice();
170
- for(var i=0;i<ch.length;i++)ch[i].remove();
171
-
172
- var m=figma.createFrame();
173
- m.name="Component Spec";
174
- m.layoutMode="VERTICAL";
175
- m.primaryAxisSizingMode="AUTO";
176
- m.counterAxisSizingMode="FIXED";
177
- m.resize(${PAGE_W},100);
178
- m.paddingTop=${PAD};
179
- m.paddingBottom=${PAD};
180
- m.paddingLeft=${PAD};
181
- m.paddingRight=${PAD};
182
- m.itemSpacing=${SECTION_GAP};
183
- m.fills=[{type:"SOLID",color:${C.bgPage}}];
184
- m.clipsContent=false;
185
- pg.appendChild(m);
186
- return {pageId:pg.id,mId:m.id};
187
- })();
188
- `);
189
- if (!r.success) throw new Error(`Init: ${r.error}`);
190
- return r.result as { pageId: string; mId: string };
191
- }
192
-
193
- // ── renderDocHeader — Large title + minimal description (no pill badges) ──
194
-
195
- async function renderDocHeaderAndHero(
196
- bridge: Bridge, mId: string, name: string, description: string, spec: ComponentSpec,
197
- sectionLabels?: string[],
198
- ): Promise<void> {
199
- // Build subtitle from available sections
200
- const subtitle = sectionLabels && sectionLabels.length > 0
201
- ? "Interactive " + sectionLabels.join(", ") + " \u00B7 Component Specification"
202
- : "Component Specification";
203
- const compW = spec.extraction.snapshot.width || 200;
204
- const compH = spec.extraction.snapshot.height || 60;
205
- const heroScale = Math.min(3, Math.max(1.5, 400 / Math.max(compW, compH)));
206
- const canvasW = Math.max(600, Math.round(compW * heroScale) + 120);
207
- const canvasH = Math.round(compH * heroScale) + 80;
208
-
209
- const r = await bridge.execute(`
210
- (async () => {
211
- ${FL}
212
- var m=await figma.getNodeByIdAsync(${esc(mId)});
213
- if(!m)return{error:"no master"};
214
-
215
- // Header card
216
- var hCard=figma.createFrame();
217
- hCard.name="Header";
218
- hCard.layoutMode="VERTICAL";
219
- hCard.primaryAxisSizingMode="AUTO";
220
- hCard.counterAxisSizingMode="AUTO";
221
- hCard.paddingTop=${CARD_PAD};hCard.paddingBottom=${CARD_PAD};
222
- hCard.paddingLeft=${CARD_PAD};hCard.paddingRight=${CARD_PAD};
223
- hCard.itemSpacing=16;
224
- hCard.fills=[{type:"SOLID",color:${C.white}}];
225
- hCard.cornerRadius=12;
226
- m.appendChild(hCard);
227
- hCard.layoutSizingHorizontal="FILL";
228
-
229
- // 64px title
230
- var t=figma.createText();
231
- t.fontName=F.b;t.fontSize=64;
232
- t.lineHeight={unit:"PIXELS",value:72};
233
- t.characters=${esc(name)};
234
- t.fills=[{type:"SOLID",color:${C.textMain}}];
235
- t.letterSpacing={unit:"PIXELS",value:-2};
236
- t.textAutoResize="HEIGHT";
237
- hCard.appendChild(t);
238
- t.layoutSizingHorizontal="FILL";
239
-
240
- // Subtitle (section summary)
241
- var sub=figma.createText();
242
- sub.fontName=F.r;sub.fontSize=16;
243
- sub.lineHeight={unit:"PIXELS",value:24};
244
- sub.characters=${esc(subtitle)};
245
- sub.fills=[{type:"SOLID",color:${C.textMuted}}];
246
- sub.letterSpacing={unit:"PIXELS",value:0.3};
247
- sub.textAutoResize="HEIGHT";
248
- hCard.appendChild(sub);
249
- sub.layoutSizingHorizontal="FILL";
250
-
251
- // Description
252
- var desc=figma.createText();
253
- desc.fontName=F.r;desc.fontSize=18;
254
- desc.lineHeight={unit:"PIXELS",value:28};
255
- desc.characters=${esc(description)};
256
- desc.fills=[{type:"SOLID",color:${C.textMuted}}];
257
- desc.textAutoResize="HEIGHT";
258
- hCard.appendChild(desc);
259
- desc.layoutSizingHorizontal="FILL";
260
-
261
- // Hero section (merged into same call)
262
- var sourceNode=await figma.getNodeByIdAsync(${esc(spec.nodeId)});
263
- if(sourceNode){
264
- var heroCard=figma.createFrame();heroCard.name="Hero";
265
- heroCard.layoutMode="VERTICAL";
266
- heroCard.primaryAxisSizingMode="AUTO";heroCard.counterAxisSizingMode="AUTO";
267
- heroCard.itemSpacing=0;heroCard.fills=[];
268
- m.appendChild(heroCard);heroCard.layoutSizingHorizontal="FILL";
269
-
270
- var canvas=figma.createFrame();canvas.name="Hero Canvas";
271
- canvas.layoutMode="HORIZONTAL";
272
- canvas.primaryAxisSizingMode="FIXED";canvas.counterAxisSizingMode="FIXED";
273
- canvas.resize(${canvasW},${canvasH});
274
- canvas.fills=[{type:"SOLID",color:${C.bgCard}}];canvas.cornerRadius=12;
275
- canvas.primaryAxisAlignItems="CENTER";canvas.counterAxisAlignItems="CENTER";
276
- canvas.clipsContent=true;
277
- heroCard.appendChild(canvas);canvas.layoutSizingHorizontal="FILL";
278
-
279
- var src=sourceNode;
280
- if(src.type==="COMPONENT_SET"&&src.children.length>0)src=src.children[0];
281
- var clone=src.clone();
282
- ${heroScale > 1 ? `clone.rescale(${heroScale});` : ""}
283
- canvas.appendChild(clone);
284
- }
285
-
286
- return{ok:true};
287
- })();
288
- `);
289
- if (!r.success) console.error("Header+Hero failed:", r.error);
290
- }
291
-
292
- // ── Section card wrapper — white card with 64px padding ──────────────────
293
-
294
- function sectionCardOpenJS(varName: string, parent: string, sectionName: string): string {
295
- return `
296
- var ${varName}=figma.createFrame();
297
- ${varName}.name=${esc(sectionName)};
298
- ${varName}.layoutMode="VERTICAL";
299
- ${varName}.primaryAxisSizingMode="AUTO";
300
- ${varName}.counterAxisSizingMode="AUTO";
301
- ${varName}.paddingTop=0;${varName}.paddingBottom=${CARD_PAD};
302
- ${varName}.paddingLeft=${CARD_PAD};${varName}.paddingRight=${CARD_PAD};
303
- ${varName}.itemSpacing=${CARD_ITEM_GAP};
304
- ${varName}.fills=[{type:"SOLID",color:${C.white}}];
305
- ${varName}.cornerRadius=12;
306
- ${varName}.clipsContent=true;
307
- ${parent}.appendChild(${varName});
308
- ${varName}.layoutSizingHorizontal="FILL";
309
-
310
- // Accent bar at top of card
311
- var ${varName}Accent=figma.createFrame();
312
- ${varName}Accent.name="Accent";
313
- ${varName}Accent.resize(4,3);
314
- ${varName}Accent.fills=[{type:"SOLID",color:${C.markerOrange}}];
315
- ${varName}.appendChild(${varName}Accent);
316
- ${varName}Accent.layoutSizingHorizontal="FILL";
317
-
318
- // Top padding spacer (replaces paddingTop since accent bar is first child)
319
- var ${varName}TopPad=figma.createFrame();
320
- ${varName}TopPad.name="TopPad";
321
- ${varName}TopPad.resize(4,${CARD_PAD - 3});
322
- ${varName}TopPad.fills=[];
323
- ${varName}.appendChild(${varName}TopPad);
324
- ${varName}TopPad.layoutSizingHorizontal="FILL";`;
325
- }
326
-
327
- // ── Section title — 48px Inter Bold ──────────────────────────────────────
328
-
329
- function sectionTitleJS(varName: string, parent: string, title: string, sectionNum?: number): string {
330
- const displayTitle = sectionNum != null
331
- ? String(sectionNum).padStart(2, "0") + " " + title
332
- : title;
333
- return `
334
- var ${varName}=figma.createText();
335
- ${varName}.fontName=F.b;${varName}.fontSize=28;
336
- ${varName}.lineHeight={unit:"PIXELS",value:36};
337
- ${varName}.characters=${esc(displayTitle)};
338
- ${varName}.fills=[{type:"SOLID",color:${C.textMain}}];
339
- ${varName}.letterSpacing={unit:"PIXELS",value:-0.5};
340
- ${varName}.textAutoResize="HEIGHT";
341
- ${parent}.appendChild(${varName});
342
- ${varName}.layoutSizingHorizontal="FILL";`;
343
- }
344
-
345
- // ── Subsection title — 36px Inter Bold ───────────────────────────────────
346
-
347
- function subsectionTitleJS(varName: string, parent: string, title: string): string {
348
- return `
349
- var ${varName}=figma.createText();
350
- ${varName}.fontName=F.b;${varName}.fontSize=20;
351
- ${varName}.lineHeight={unit:"PIXELS",value:28};
352
- ${varName}.characters=${esc(title)};
353
- ${varName}.fills=[{type:"SOLID",color:${C.textMain}}];
354
- ${varName}.letterSpacing={unit:"PIXELS",value:0};
355
- ${varName}.textAutoResize="HEIGHT";
356
- ${parent}.appendChild(${varName});
357
- ${varName}.layoutSizingHorizontal="FILL";`;
358
- }
359
-
360
- // ══════════════════════════════════════════════════════════════════════════
361
- // TEXT-BASED SUB-RENDERERS (for knowledge sections)
362
- // ══════════════════════════════════════════════════════════════════════════
363
-
364
- async function renderTextSection(
365
- bridge: Bridge, mId: string, title: string, content: SpecSectionContent, sectionNum?: number,
366
- ): Promise<void> {
367
- // Create a section card, add title, then render content inside it
368
- const contentJS = buildContentJS("sc", content);
369
-
370
- const r = await bridge.execute(`
371
- (async () => {
372
- ${FL}
373
- var m=await figma.getNodeByIdAsync(${esc(mId)});
374
- if(!m)return{error:"no master"};
375
-
376
- ${sectionCardOpenJS("sc", "m", title)}
377
- ${sectionTitleJS("stitle", "sc", title, sectionNum)}
378
- ${contentJS}
379
-
380
- return{ok:true};
381
- })();
382
- `);
383
- if (!r.success) console.error(`Text section "${title}" failed:`, r.error);
384
- }
385
-
386
- function buildContentJS(parent: string, content: SpecSectionContent): string {
387
- switch (content.kind) {
388
- case "paragraph":
389
- return textFillJS("_p", parent, "r", 16, 26, esc(content.text), C.textMuted);
390
-
391
- case "key-value": {
392
- const capped = content.entries.slice(0, 40);
393
- return `
394
- var _kvOuter=figma.createFrame();_kvOuter.name="Key-Value";
395
- _kvOuter.layoutMode="VERTICAL";
396
- _kvOuter.primaryAxisSizingMode="AUTO";_kvOuter.counterAxisSizingMode="AUTO";
397
- _kvOuter.itemSpacing=0;_kvOuter.fills=[];
398
- ${parent}.appendChild(_kvOuter);_kvOuter.layoutSizingHorizontal="FILL";
399
- var _kvEntries=${JSON.stringify(capped)};
400
- for(var _kvi=0;_kvi<_kvEntries.length;_kvi++){
401
- var _kvRow=figma.createFrame();_kvRow.name=_kvEntries[_kvi].label;
402
- _kvRow.layoutMode="HORIZONTAL";
403
- _kvRow.primaryAxisSizingMode="AUTO";_kvRow.counterAxisSizingMode="AUTO";
404
- _kvRow.itemSpacing=16;_kvRow.paddingTop=10;_kvRow.paddingBottom=10;
405
- _kvRow.fills=[];_kvRow.counterAxisAlignItems="MIN";
406
- _kvRow.strokes=[{type:"SOLID",color:${C.border}}];
407
- _kvRow.strokeWeight=1;_kvRow.strokeTopWeight=0;_kvRow.strokeRightWeight=0;
408
- _kvRow.strokeBottomWeight=1;_kvRow.strokeLeftWeight=0;_kvRow.strokeAlign="INSIDE";
409
- _kvOuter.appendChild(_kvRow);_kvRow.layoutSizingHorizontal="FILL";
410
- var _kvLbl=figma.createText();_kvLbl.fontName=F.sb;_kvLbl.fontSize=14;
411
- _kvLbl.lineHeight={unit:"PIXELS",value:20};
412
- _kvLbl.characters=_kvEntries[_kvi].label;
413
- _kvLbl.fills=[{type:"SOLID",color:${C.textMuted}}];
414
- _kvLbl.textAutoResize="HEIGHT";_kvLbl.resize(200,20);
415
- _kvRow.appendChild(_kvLbl);_kvLbl.layoutSizingHorizontal="FIXED";
416
- var _kvVal=figma.createText();_kvVal.fontName=F.r;_kvVal.fontSize=16;
417
- _kvVal.lineHeight={unit:"PIXELS",value:24};
418
- _kvVal.characters=_kvEntries[_kvi].value||"\\u2014";
419
- _kvVal.fills=[{type:"SOLID",color:${C.textMain}}];
420
- _kvVal.textAutoResize="HEIGHT";
421
- _kvRow.appendChild(_kvVal);_kvVal.layoutSizingHorizontal="FILL";
422
- }`;
423
- }
424
-
425
- case "table":
426
- case "structured-data": {
427
- const headers = content.kind === "table" ? content.headers : content.columns;
428
- const rows = content.kind === "table"
429
- ? content.rows.slice(0, 50)
430
- : content.rows.slice(0, 50).map(r => headers.map(c => r[c] ?? "\u2014"));
431
- const colCount = headers.length;
432
- const cellW = Math.floor((CONTENT_W - CARD_PAD * 2 - 32) / colCount);
433
- return `
434
- var _tHeaders=${JSON.stringify(headers)};
435
- var _tRows=${JSON.stringify(rows)};
436
- var _tCellW=${cellW};
437
- var _tbl=figma.createFrame();_tbl.name="Table";
438
- _tbl.layoutMode="VERTICAL";
439
- _tbl.primaryAxisSizingMode="AUTO";_tbl.counterAxisSizingMode="AUTO";
440
- _tbl.itemSpacing=0;_tbl.fills=[{type:"SOLID",color:${C.white}}];
441
- _tbl.cornerRadius=8;
442
- _tbl.strokes=[{type:"SOLID",color:${C.border}}];_tbl.strokeWeight=1;_tbl.strokeAlign="INSIDE";
443
- _tbl.clipsContent=true;
444
- ${parent}.appendChild(_tbl);_tbl.layoutSizingHorizontal="FILL";
445
- var _tHdr=figma.createFrame();_tHdr.name="Header";
446
- _tHdr.layoutMode="HORIZONTAL";
447
- _tHdr.primaryAxisSizingMode="AUTO";_tHdr.counterAxisSizingMode="AUTO";
448
- _tHdr.itemSpacing=0;_tHdr.paddingTop=12;_tHdr.paddingBottom=12;
449
- _tHdr.paddingLeft=16;_tHdr.paddingRight=16;
450
- _tHdr.fills=[{type:"SOLID",color:${C.bgCard}}];_tHdr.counterAxisAlignItems="MIN";
451
- _tHdr.strokes=[{type:"SOLID",color:${C.border}}];_tHdr.strokeWeight=1;
452
- _tHdr.strokeTopWeight=0;_tHdr.strokeRightWeight=0;_tHdr.strokeBottomWeight=1;_tHdr.strokeLeftWeight=0;
453
- _tHdr.strokeAlign="INSIDE";
454
- _tbl.appendChild(_tHdr);_tHdr.layoutSizingHorizontal="FILL";
455
- for(var _hc=0;_hc<_tHeaders.length;_hc++){
456
- var _ht=figma.createText();_ht.fontName=F.sb;_ht.fontSize=14;
457
- _ht.lineHeight={unit:"PIXELS",value:20};_ht.characters=_tHeaders[_hc]||"";
458
- _ht.fills=[{type:"SOLID",color:${C.textMain}}];_ht.textAutoResize="HEIGHT";
459
- _ht.resize(_tCellW,20);_tHdr.appendChild(_ht);_ht.layoutSizingHorizontal="FIXED";
460
- }
461
- for(var _ri=0;_ri<_tRows.length;_ri++){
462
- var _tRow=figma.createFrame();_tRow.name="Row "+(_ri+1);
463
- _tRow.layoutMode="HORIZONTAL";
464
- _tRow.primaryAxisSizingMode="AUTO";_tRow.counterAxisSizingMode="AUTO";
465
- _tRow.itemSpacing=0;_tRow.paddingTop=10;_tRow.paddingBottom=10;
466
- _tRow.paddingLeft=16;_tRow.paddingRight=16;_tRow.fills=[];_tRow.counterAxisAlignItems="MIN";
467
- if(_ri<_tRows.length-1){
468
- _tRow.strokes=[{type:"SOLID",color:${C.border}}];_tRow.strokeWeight=1;
469
- _tRow.strokeTopWeight=0;_tRow.strokeRightWeight=0;_tRow.strokeBottomWeight=1;_tRow.strokeLeftWeight=0;
470
- _tRow.strokeAlign="INSIDE";
471
- }
472
- _tbl.appendChild(_tRow);_tRow.layoutSizingHorizontal="FILL";
473
- for(var _rc=0;_rc<_tHeaders.length;_rc++){
474
- var _ct=figma.createText();_ct.fontName=F.r;_ct.fontSize=14;
475
- _ct.lineHeight={unit:"PIXELS",value:20};
476
- _ct.characters=(_tRows[_ri][_rc]!=null?_tRows[_ri][_rc]:"")||"\\u2014";
477
- _ct.fills=[{type:"SOLID",color:${C.textMuted}}];_ct.textAutoResize="HEIGHT";
478
- _ct.resize(_tCellW,20);_tRow.appendChild(_ct);_ct.layoutSizingHorizontal="FIXED";
479
- }
480
- }`;
481
- }
482
-
483
- case "list":
484
- case "rules": {
485
- const items = content.items.slice(0, 60);
486
- return `
487
- var _lItems=${JSON.stringify(items)};
488
- var _lOuter=figma.createFrame();_lOuter.name="List";
489
- _lOuter.layoutMode="VERTICAL";
490
- _lOuter.primaryAxisSizingMode="AUTO";_lOuter.counterAxisSizingMode="AUTO";
491
- _lOuter.itemSpacing=6;_lOuter.fills=[];_lOuter.paddingLeft=8;
492
- ${parent}.appendChild(_lOuter);_lOuter.layoutSizingHorizontal="FILL";
493
- for(var _li=0;_li<_lItems.length;_li++){
494
- var _lRow=figma.createFrame();_lRow.name="Item "+(_li+1);
495
- _lRow.layoutMode="HORIZONTAL";
496
- _lRow.primaryAxisSizingMode="AUTO";_lRow.counterAxisSizingMode="AUTO";
497
- _lRow.itemSpacing=8;_lRow.fills=[];_lRow.counterAxisAlignItems="MIN";
498
- _lOuter.appendChild(_lRow);_lRow.layoutSizingHorizontal="FILL";
499
- var _lBul=figma.createText();_lBul.fontName=F.r;_lBul.fontSize=16;
500
- _lBul.lineHeight={unit:"PIXELS",value:26};_lBul.characters="\\u2022";
501
- _lBul.fills=[{type:"SOLID",color:${C.textMuted}}];_lBul.textAutoResize="WIDTH_AND_HEIGHT";
502
- _lRow.appendChild(_lBul);
503
- var _lTxt=figma.createText();_lTxt.fontName=F.r;_lTxt.fontSize=16;
504
- _lTxt.lineHeight={unit:"PIXELS",value:26};_lTxt.characters=_lItems[_li]||"\\u2014";
505
- _lTxt.fills=[{type:"SOLID",color:${C.textMuted}}];_lTxt.textAutoResize="HEIGHT";
506
- _lRow.appendChild(_lTxt);_lTxt.layoutSizingHorizontal="FILL";
507
- }`;
508
- }
509
-
510
- case "do-dont": {
511
- const cappedDos = content.dos.slice(0, 30);
512
- const cappedDonts = content.donts.slice(0, 30);
513
- return `
514
- var _ddDos=${JSON.stringify(cappedDos)};
515
- var _ddDonts=${JSON.stringify(cappedDonts)};
516
- var _ddOuter=figma.createFrame();_ddOuter.name="Do-Dont";
517
- _ddOuter.layoutMode="HORIZONTAL";
518
- _ddOuter.primaryAxisSizingMode="AUTO";_ddOuter.counterAxisSizingMode="AUTO";
519
- _ddOuter.itemSpacing=24;_ddOuter.fills=[];_ddOuter.counterAxisAlignItems="MIN";
520
- ${parent}.appendChild(_ddOuter);_ddOuter.layoutSizingHorizontal="FILL";
521
-
522
- function _ddBuildCol(parent,title,items,iconChar,iconColor,titleColor){
523
- var col=figma.createFrame();col.name=title;
524
- col.layoutMode="VERTICAL";
525
- col.primaryAxisSizingMode="AUTO";col.counterAxisSizingMode="AUTO";
526
- col.itemSpacing=8;col.paddingTop=16;col.paddingBottom=16;
527
- col.paddingLeft=16;col.paddingRight=16;
528
- col.fills=[{type:"SOLID",color:${C.bgCard}}];col.cornerRadius=8;
529
- parent.appendChild(col);col.layoutSizingHorizontal="FILL";
530
- var ct=figma.createText();ct.fontName=F.sb;ct.fontSize=16;
531
- ct.lineHeight={unit:"PIXELS",value:22};ct.characters=iconChar+" "+title;
532
- ct.fills=[{type:"SOLID",color:titleColor}];ct.textAutoResize="WIDTH_AND_HEIGHT";
533
- col.appendChild(ct);
534
- var sp=figma.createFrame();sp.resize(4,4);sp.fills=[];col.appendChild(sp);sp.layoutSizingHorizontal="FILL";
535
- for(var i=0;i<items.length;i++){
536
- var row=figma.createFrame();row.name=title+" "+(i+1);
537
- row.layoutMode="HORIZONTAL";
538
- row.primaryAxisSizingMode="AUTO";row.counterAxisSizingMode="AUTO";
539
- row.itemSpacing=8;row.fills=[];row.counterAxisAlignItems="MIN";
540
- col.appendChild(row);row.layoutSizingHorizontal="FILL";
541
- var icon=figma.createText();icon.fontName=F.sb;icon.fontSize=14;
542
- icon.lineHeight={unit:"PIXELS",value:22};icon.characters=iconChar;
543
- icon.fills=[{type:"SOLID",color:iconColor}];icon.textAutoResize="WIDTH_AND_HEIGHT";
544
- row.appendChild(icon);
545
- var txt=figma.createText();txt.fontName=F.r;txt.fontSize=14;
546
- txt.lineHeight={unit:"PIXELS",value:22};txt.characters=items[i]||"\\u2014";
547
- txt.fills=[{type:"SOLID",color:${C.textMuted}}];txt.textAutoResize="HEIGHT";
548
- row.appendChild(txt);txt.layoutSizingHorizontal="FILL";
549
- }
550
- }
551
- if(_ddDos.length>0)_ddBuildCol(_ddOuter,"Do",_ddDos,"\\u2713",${C.success},${C.success});
552
- if(_ddDonts.length>0)_ddBuildCol(_ddOuter,"Don\\u2019t",_ddDonts,"\\u2717",${C.error},${C.error});`;
553
- }
554
-
555
- case "mixed":
556
- return content.blocks.map(b => buildContentJS(parent, b)).join("\n");
557
-
558
- default:
559
- return "";
560
- }
561
- }
562
-
563
- // ══════════════════════════════════════════════════════════════════════════
564
- // PHASE 2: EXHIBIT RENDERER — Reusable artwork + content pattern
565
- // ══════════════════════════════════════════════════════════════════════════
566
-
567
- /**
568
- * Generate JS for a single exhibit: gray artwork left + content metadata right.
569
- * The artwork frame is absolute-positioned for component placement;
570
- * content is an auto-layout column with label, element info, and attributes.
571
- */
572
- function exhibitOpenJS(
573
- varName: string,
574
- parent: string,
575
- label: string,
576
- artworkVar: string,
577
- contentVar: string,
578
- ): string {
579
- return `
580
- var ${varName}=figma.createFrame();${varName}.name=${esc(label)};
581
- ${varName}.layoutMode="HORIZONTAL";
582
- ${varName}.primaryAxisSizingMode="AUTO";${varName}.counterAxisSizingMode="AUTO";
583
- ${varName}.itemSpacing=${EXHIBIT_GAP};
584
- ${varName}.fills=[];
585
- ${varName}.counterAxisAlignItems="MIN";
586
- ${parent}.appendChild(${varName});${varName}.layoutSizingHorizontal="FILL";
587
-
588
- var ${artworkVar}=figma.createFrame();${artworkVar}.name="Artwork";
589
- ${artworkVar}.layoutMode="HORIZONTAL";
590
- ${artworkVar}.primaryAxisSizingMode="FIXED";${artworkVar}.counterAxisSizingMode="FIXED";
591
- ${artworkVar}.resize(${ARTWORK_W},${ARTWORK_H});
592
- ${artworkVar}.fills=[{type:"SOLID",color:${C.bgCard}}];
593
- ${artworkVar}.cornerRadius=8;
594
- ${artworkVar}.primaryAxisAlignItems="CENTER";${artworkVar}.counterAxisAlignItems="CENTER";
595
- ${artworkVar}.clipsContent=true;
596
- ${varName}.appendChild(${artworkVar});
597
-
598
- var ${contentVar}=figma.createFrame();${contentVar}.name="Content";
599
- ${contentVar}.layoutMode="VERTICAL";
600
- ${contentVar}.primaryAxisSizingMode="AUTO";${contentVar}.counterAxisSizingMode="AUTO";
601
- ${contentVar}.itemSpacing=16;${contentVar}.fills=[];
602
- ${varName}.appendChild(${contentVar});${contentVar}.layoutSizingHorizontal="FILL";`;
603
- }
604
-
605
- /**
606
- * Generate JS for a type icon (20x20 frame with node type indicator)
607
- */
608
- function typeIconJS(varName: string, parent: string, nodeType: string): string {
609
- let glyph = "\u25A1"; // default: square for FRAME
610
- if (nodeType === "INSTANCE") glyph = "\u25C7"; // diamond
611
- else if (nodeType === "TEXT") glyph = "T";
612
-
613
- return `
614
- var ${varName}=figma.createFrame();${varName}.name="TypeIcon";
615
- ${varName}.layoutMode="HORIZONTAL";
616
- ${varName}.primaryAxisSizingMode="FIXED";${varName}.counterAxisSizingMode="FIXED";
617
- ${varName}.resize(20,20);
618
- ${varName}.fills=[];
619
- ${varName}.strokes=[{type:"SOLID",color:${C.textMuted}}];
620
- ${varName}.strokeWeight=1;${varName}.strokeAlign="INSIDE";
621
- ${varName}.cornerRadius=3;
622
- ${varName}.primaryAxisAlignItems="CENTER";${varName}.counterAxisAlignItems="CENTER";
623
- ${parent}.appendChild(${varName});
624
- var ${varName}Lbl=figma.createText();${varName}Lbl.fontName=F.r;${varName}Lbl.fontSize=10;
625
- ${varName}Lbl.characters=${esc(glyph)};
626
- ${varName}Lbl.fills=[{type:"SOLID",color:${C.textMuted}}];
627
- ${varName}Lbl.textAutoResize="WIDTH_AND_HEIGHT";
628
- ${varName}.appendChild(${varName}Lbl);`;
629
- }
630
-
631
- /**
632
- * Generate JS for an attribute row (name: value)
633
- */
634
- function attrRowJS(varName: string, parent: string, name: string, value: string): string {
635
- return `
636
- var ${varName}=figma.createFrame();${varName}.name=${esc(name)};
637
- ${varName}.layoutMode="HORIZONTAL";
638
- ${varName}.primaryAxisSizingMode="AUTO";${varName}.counterAxisSizingMode="AUTO";
639
- ${varName}.itemSpacing=4;${varName}.fills=[];
640
- ${parent}.appendChild(${varName});${varName}.layoutSizingHorizontal="FILL";
641
- var ${varName}N=figma.createText();${varName}N.fontName=F.r;${varName}N.fontSize=11;
642
- ${varName}N.lineHeight={unit:"PIXELS",value:16};
643
- ${varName}N.characters=${esc(name + ":")};
644
- ${varName}N.fills=[{type:"SOLID",color:${C.textMuted}}];
645
- ${varName}N.textAutoResize="WIDTH_AND_HEIGHT";
646
- ${varName}N.resize(90,16);
647
- ${varName}.appendChild(${varName}N);
648
- ${varName}N.layoutSizingHorizontal="FIXED";
649
- var ${varName}V=figma.createText();${varName}V.fontName=F.sb;${varName}V.fontSize=11;
650
- ${varName}V.lineHeight={unit:"PIXELS",value:16};
651
- ${varName}V.characters=${esc(value)};
652
- ${varName}V.fills=[{type:"SOLID",color:${C.textMain}}];
653
- ${varName}V.textAutoResize="WIDTH_AND_HEIGHT";
654
- ${varName}.appendChild(${varName}V);`;
655
- }
656
-
657
- // ══════════════════════════════════════════════════════════════════════════
658
- // PHASE 4: ANATOMY EXHIBIT — Orange markers, type icons, attributes
659
- // ══════════════════════════════════════════════════════════════════════════
660
-
661
- async function renderAnatomyExhibit(
662
- bridge: Bridge, mId: string, spec: ComponentSpec, sectionNum?: number,
663
- ): Promise<void> {
664
- const anatomy = spec.extraction.anatomy;
665
- const elements = anatomy.elements.filter(
666
- el => el.visible && el.position.w > 0 && el.position.h > 0,
667
- );
668
- if (elements.length === 0) return;
669
-
670
- const compW = anatomy.componentBounds?.w || spec.extraction.snapshot.width || 200;
671
- const compH = anatomy.componentBounds?.h || spec.extraction.snapshot.height || 60;
672
-
673
- // Scale up small components
674
- const scale = Math.min(3, Math.max(1, 140 / Math.min(compW, compH)));
675
- const displayW = Math.round(compW * scale);
676
- const displayH = Math.round(compH * scale);
677
-
678
- const scaledElements = elements.map(el => ({
679
- ...el,
680
- position: {
681
- x: Math.round(el.position.x * scale),
682
- y: Math.round(el.position.y * scale),
683
- w: Math.round(el.position.w * scale),
684
- h: Math.round(el.position.h * scale),
685
- },
686
- }));
687
-
688
- const MARGIN = 60;
689
- const markers = computeMarkerPositions(displayW, displayH, MARGIN, MARGIN, scaledElements);
690
- if (markers.length === 0) return;
691
-
692
- // Build anatomy content items JS
693
- const anatomyItemsJS = elements.slice(0, markers.length).map((el, idx) => {
694
- const num = idx + 1;
695
- const vPrefix = `_ai${num}`;
696
- let attributeLines = "";
697
-
698
- if (el.nodeType === "TEXT") {
699
- if (el.fontFamily) attributeLines += attrRowJS(`${vPrefix}a1`, `${vPrefix}col`, "Font family", el.fontFamily);
700
- if (el.fontStyle) attributeLines += attrRowJS(`${vPrefix}a2`, `${vPrefix}col`, "Font weight", el.fontStyle);
701
- if (el.fontSize) attributeLines += attrRowJS(`${vPrefix}a3`, `${vPrefix}col`, "Font size", String(el.fontSize) + "px");
702
- if (el.lineHeightPx) attributeLines += attrRowJS(`${vPrefix}a4`, `${vPrefix}col`, "Line height", String(el.lineHeightPx) + "px");
703
- if (el.tokenName) attributeLines += attrRowJS(`${vPrefix}a5`, `${vPrefix}col`, "Text style", el.tokenName);
704
- if (el.fills?.[0]) attributeLines += attrRowJS(`${vPrefix}a6`, `${vPrefix}col`, "Fill", el.fills[0]);
705
- } else if (el.nodeType === "INSTANCE") {
706
- if (el.componentName) attributeLines += attrRowJS(`${vPrefix}a1`, `${vPrefix}col`, "Depends on", el.componentName);
707
- if (el.instanceOf && el.instanceOf !== el.componentName) attributeLines += attrRowJS(`${vPrefix}a2`, `${vPrefix}col`, "Variant", el.instanceOf);
708
- if (el.position?.w) attributeLines += attrRowJS(`${vPrefix}a3`, `${vPrefix}col`, "Size", `${el.position.w}\u00D7${el.position.h}`);
709
- if (el.variantProperties) {
710
- const vpEntries = Object.entries(el.variantProperties).slice(0, 3);
711
- vpEntries.forEach(([k, v], i) => {
712
- attributeLines += attrRowJS(`${vPrefix}vp${i}`, `${vPrefix}col`, k, v);
713
- });
714
- }
715
- if (el.fills?.[0]) attributeLines += attrRowJS(`${vPrefix}a4`, `${vPrefix}col`, "Fill", el.fills[0]);
716
- } else if (el.nodeType === "FRAME" || el.nodeType === "COMPONENT") {
717
- if (el.layoutMode && el.layoutMode !== "NONE") attributeLines += attrRowJS(`${vPrefix}a1`, `${vPrefix}col`, "Direction", el.layoutMode === "HORIZONTAL" ? "Horizontal" : "Vertical");
718
- if (el.itemSpacing != null && el.itemSpacing > 0) attributeLines += attrRowJS(`${vPrefix}a2`, `${vPrefix}col`, "Item spacing", String(el.itemSpacing) + "px");
719
- if (el.cornerRadius != null && el.cornerRadius > 0) attributeLines += attrRowJS(`${vPrefix}a3`, `${vPrefix}col`, "Radius", String(el.cornerRadius) + "px");
720
- if (el.fills?.[0]) attributeLines += attrRowJS(`${vPrefix}a4`, `${vPrefix}col`, "Fill", el.fills[0]);
721
- if (el.strokes?.[0]) attributeLines += attrRowJS(`${vPrefix}a5`, `${vPrefix}col`, "Stroke", el.strokes[0]);
722
- } else {
723
- if (el.fills?.[0]) attributeLines += attrRowJS(`${vPrefix}a1`, `${vPrefix}col`, "Fill", el.fills[0]);
724
- if (el.strokes?.[0]) attributeLines += attrRowJS(`${vPrefix}a2`, `${vPrefix}col`, "Stroke", el.strokes[0]);
725
- if (el.cornerRadius != null && el.cornerRadius > 0) attributeLines += attrRowJS(`${vPrefix}a3`, `${vPrefix}col`, "Radius", String(el.cornerRadius) + "px");
726
- }
727
- // Size for all non-instance elements
728
- if (el.nodeType !== "INSTANCE" && el.position?.w > 0) {
729
- attributeLines += attrRowJS(`${vPrefix}sz`, `${vPrefix}col`, "Size", `${el.position.w}\u00D7${el.position.h}`);
730
- }
731
-
732
- return `
733
- // Anatomy item ${num}
734
- var ${vPrefix}=figma.createFrame();${vPrefix}.name="Item ${num}";
735
- ${vPrefix}.layoutMode="HORIZONTAL";
736
- ${vPrefix}.primaryAxisSizingMode="AUTO";${vPrefix}.counterAxisSizingMode="AUTO";
737
- ${vPrefix}.itemSpacing=8;${vPrefix}.fills=[];${vPrefix}.counterAxisAlignItems="MIN";
738
- ${vPrefix}.paddingLeft=28;
739
- anatContent.appendChild(${vPrefix});${vPrefix}.layoutSizingHorizontal="FILL";
740
-
741
- // Orange numbered dot (overlapping left edge)
742
- var ${vPrefix}dot=figma.createEllipse();${vPrefix}dot.resize(20,20);
743
- ${vPrefix}dot.fills=[{type:"SOLID",color:${C.markerOrange}}];
744
- ${vPrefix}dot.name="Dot ${num}";
745
-
746
- var ${vPrefix}dotWrap=figma.createFrame();${vPrefix}dotWrap.name="DotWrap";
747
- ${vPrefix}dotWrap.layoutMode="NONE";${vPrefix}dotWrap.resize(20,20);${vPrefix}dotWrap.fills=[];
748
- ${vPrefix}dotWrap.appendChild(${vPrefix}dot);${vPrefix}dot.x=0;${vPrefix}dot.y=0;
749
- var ${vPrefix}dotNum=figma.createText();${vPrefix}dotNum.fontName=F.b;${vPrefix}dotNum.fontSize=12;
750
- ${vPrefix}dotNum.characters="${num}";
751
- ${vPrefix}dotNum.fills=[{type:"SOLID",color:{r:1,g:1,b:1}}];
752
- ${vPrefix}dotNum.textAutoResize="WIDTH_AND_HEIGHT";
753
- ${vPrefix}dotWrap.appendChild(${vPrefix}dotNum);
754
- ${vPrefix}dotNum.x=10-${vPrefix}dotNum.width/2;
755
- ${vPrefix}dotNum.y=10-${vPrefix}dotNum.height/2;
756
- ${vPrefix}.appendChild(${vPrefix}dotWrap);
757
-
758
- // Element info column
759
- var ${vPrefix}col=figma.createFrame();${vPrefix}col.name="Info";
760
- ${vPrefix}col.layoutMode="VERTICAL";
761
- ${vPrefix}col.primaryAxisSizingMode="AUTO";${vPrefix}col.counterAxisSizingMode="AUTO";
762
- ${vPrefix}col.itemSpacing=4;${vPrefix}col.fills=[];
763
- ${vPrefix}.appendChild(${vPrefix}col);${vPrefix}col.layoutSizingHorizontal="FILL";
764
-
765
- // Element row: type icon + name
766
- var ${vPrefix}elRow=figma.createFrame();${vPrefix}elRow.name="ElRow";
767
- ${vPrefix}elRow.layoutMode="HORIZONTAL";
768
- ${vPrefix}elRow.primaryAxisSizingMode="AUTO";${vPrefix}elRow.counterAxisSizingMode="AUTO";
769
- ${vPrefix}elRow.itemSpacing=6;${vPrefix}elRow.fills=[];${vPrefix}elRow.counterAxisAlignItems="CENTER";
770
- ${vPrefix}col.appendChild(${vPrefix}elRow);${vPrefix}elRow.layoutSizingHorizontal="FILL";
771
-
772
- ${typeIconJS(`${vPrefix}icon`, `${vPrefix}elRow`, el.nodeType)}
773
-
774
- var ${vPrefix}name=figma.createText();${vPrefix}name.fontName=F.b;${vPrefix}name.fontSize=16;
775
- ${vPrefix}name.lineHeight={unit:"PIXELS",value:22};
776
- ${vPrefix}name.characters=${esc(el.name)};
777
- ${vPrefix}name.fills=[{type:"SOLID",color:${C.textMain}}];
778
- ${vPrefix}name.textAutoResize="WIDTH_AND_HEIGHT";
779
- ${vPrefix}elRow.appendChild(${vPrefix}name);
780
-
781
- ${attributeLines}
782
- `;
783
- }).join("\n");
784
-
785
- const markersJSON = JSON.stringify(markers);
786
- const MD = 24; // marker diameter
787
-
788
- const r = await bridge.execute(`
789
- (async () => {
790
- ${FL}
791
- var m=await figma.getNodeByIdAsync(${esc(mId)});
792
- if(!m)return{error:"no master"};
793
- var sourceNode=await figma.getNodeByIdAsync(${esc(spec.nodeId)});
794
- if(!sourceNode)return{error:"no source"};
795
-
796
- // Section card
797
- ${sectionCardOpenJS("anatCard", "m", "Anatomy")}
798
- ${sectionTitleJS("anatTitle", "anatCard", "Anatomy", sectionNum)}
799
-
800
- // Exhibit: artwork left | content right
801
- var anatExhibit=figma.createFrame();anatExhibit.name="Anatomy Exhibit";
802
- anatExhibit.layoutMode="HORIZONTAL";
803
- anatExhibit.primaryAxisSizingMode="AUTO";anatExhibit.counterAxisSizingMode="AUTO";
804
- anatExhibit.itemSpacing=${EXHIBIT_GAP};anatExhibit.fills=[];
805
- anatExhibit.counterAxisAlignItems="MIN";
806
- anatCard.appendChild(anatExhibit);anatExhibit.layoutSizingHorizontal="FILL";
807
-
808
- // Left: artwork with component clone + markers (absolute positioning)
809
- var artWrap=figma.createFrame();artWrap.name="Artwork";
810
- artWrap.layoutMode="NONE";
811
- artWrap.fills=[{type:"SOLID",color:${C.bgCard}}];artWrap.cornerRadius=8;
812
- anatExhibit.appendChild(artWrap);
813
-
814
- var src=sourceNode;
815
- if(src.type==="COMPONENT_SET"&&src.children.length>0)src=src.children[0];
816
- var clone=src.clone();
817
- clone.x=${MARGIN};clone.y=${MARGIN};
818
- ${scale > 1 ? `clone.rescale(${scale});` : ""}
819
- artWrap.appendChild(clone);
820
- var artW=clone.width+${MARGIN * 2}+40;
821
- artWrap.resize(artW,clone.height+${MARGIN * 2});
822
-
823
- var markers=${markersJSON};
824
- var MD=${MD},MR=${MD / 2};
825
-
826
- for(var i=0;i<markers.length;i++){
827
- var mk=markers[i];
828
-
829
- // Orange marker dot ON the element
830
- var dot=figma.createEllipse();dot.resize(MD,MD);
831
- dot.fills=[{type:"SOLID",color:${C.markerOrange}}];
832
- dot.x=mk.markerX-MR;dot.y=mk.markerY-MR;dot.name="Marker "+(i+1);
833
- artWrap.appendChild(dot);
834
-
835
- // Number label centered on dot
836
- var num=figma.createText();num.fontName=F.b;num.fontSize=11;
837
- num.characters=String(i+1);
838
- num.fills=[{type:"SOLID",color:{r:1,g:1,b:1}}];
839
- num.textAutoResize="WIDTH_AND_HEIGHT";
840
- artWrap.appendChild(num);
841
- num.x=mk.markerX-num.width/2;num.y=mk.markerY-num.height/2;
842
-
843
- // Dashed leader line from marker to right edge
844
- var leaderStartX=mk.markerX+MR+2;
845
- var leaderEndX=artW-8;
846
- var leaderY=mk.markerY;
847
- if(leaderEndX>leaderStartX+10){
848
- var leader=figma.createVector();
849
- leader.vectorPaths=[{windingRule:"NONZERO",data:"M "+leaderStartX+" "+leaderY+" L "+leaderEndX+" "+leaderY}];
850
- leader.strokes=[{type:"SOLID",color:${C.markerOrange}}];
851
- leader.strokeWeight=0.75;leader.fills=[];
852
- leader.dashPattern=[3,3];leader.opacity=0.5;
853
- leader.name="Leader "+(i+1);
854
- artWrap.appendChild(leader);
855
- }
856
- }
857
-
858
- // Right: anatomy content
859
- var anatContent=figma.createFrame();anatContent.name="Anatomy Items";
860
- anatContent.layoutMode="VERTICAL";
861
- anatContent.primaryAxisSizingMode="AUTO";anatContent.counterAxisSizingMode="AUTO";
862
- anatContent.itemSpacing=24;anatContent.fills=[];
863
- anatExhibit.appendChild(anatContent);anatContent.layoutSizingHorizontal="FILL";
864
-
865
- ${anatomyItemsJS}
866
-
867
- return{ok:true};
868
- })();
869
- `);
870
- if (!r.success) console.error("Anatomy exhibit failed:", r.error);
871
- }
872
-
873
- // ══════════════════════════════════════════════════════════════════════════
874
- // PHASE 3: PROPERTY SECTIONS WITH GROUPED EXHIBITS
875
- // ══════════════════════════════════════════════════════════════════════════
876
-
877
- async function renderVariantsExhibit(
878
- bridge: Bridge, mId: string, spec: ComponentSpec,
879
- ): Promise<void> {
880
- const snapshot = spec.extraction.snapshot;
881
- const variantGroupProps = snapshot.variantGroupProperties || {};
882
- const axisNames = Object.keys(variantGroupProps);
883
- if (axisNames.length === 0) return;
884
-
885
- const defaultProps = snapshot.variants?.[0]?.properties || {};
886
- const MAX_PER_AXIS = 6;
887
-
888
- // Create the section card first
889
- const r0 = await bridge.execute(`
890
- (async () => {
891
- ${FL}
892
- var m=await figma.getNodeByIdAsync(${esc(mId)});
893
- if(!m)return{error:"no master"};
894
- ${sectionCardOpenJS("varCard", "m", "Properties")}
895
- ${sectionTitleJS("varTitle", "varCard", "Properties")}
896
- return{ok:true,cardId:varCard.id};
897
- })();
898
- `);
899
- if (!r0.success) { console.error("Variants card failed:", r0.error); return; }
900
- const cardId = (r0.result as { cardId: string }).cardId;
901
-
902
- // Render each axis as a subsection with exhibits
903
- for (const axisName of axisNames) {
904
- const values = variantGroupProps[axisName].slice(0, MAX_PER_AXIS);
905
- const samples = values.map(val => ({
906
- label: val,
907
- props: { ...defaultProps, [axisName]: val },
908
- }));
909
-
910
- const r = await bridge.execute(`
911
- (async () => {
912
- ${FL}
913
- ${EXTRACT_STYLE_FN}
914
- var card=await figma.getNodeByIdAsync(${esc(cardId)});
915
- if(!card)return{error:"no card"};
916
- var sourceNode=await figma.getNodeByIdAsync(${esc(spec.nodeId)});
917
- if(!sourceNode)return{error:"no source"};
918
-
919
- // Axis group
920
- var axGrp=figma.createFrame();axGrp.name=${esc(axisName + " Axis")};
921
- axGrp.layoutMode="VERTICAL";
922
- axGrp.primaryAxisSizingMode="AUTO";axGrp.counterAxisSizingMode="AUTO";
923
- axGrp.itemSpacing=40;axGrp.fills=[];
924
- card.appendChild(axGrp);axGrp.layoutSizingHorizontal="FILL";
925
-
926
- // Axis title (36px bold)
927
- ${subsectionTitleJS("axTitle", "axGrp", axisName)}
928
-
929
- var compSet=sourceNode.type==="COMPONENT_SET"?sourceNode:(sourceNode.parent&&sourceNode.parent.type==="COMPONENT_SET"?sourceNode.parent:null);
930
- if(!compSet)return{ok:true,skip:"not a component set"};
931
-
932
- var samples=${JSON.stringify(samples)};
933
-
934
- for(var si=0;si<samples.length;si++){
935
- var s=samples[si];
936
-
937
- // Find matching variant
938
- var target=null;
939
- for(var ci=0;ci<compSet.children.length;ci++){
940
- var ch=compSet.children[ci];
941
- if(!ch.variantProperties)continue;
942
- var match=true;
943
- for(var pk in s.props){if(ch.variantProperties[pk]!==s.props[pk]){match=false;break;}}
944
- if(match){target=ch;break;}
945
- }
946
- if(!target&&compSet.children.length>0)target=compSet.children[0];
947
- if(!target)continue;
948
- var inst=target.createInstance();
949
- var style=extractStyle(inst);
950
-
951
- // Exhibit: artwork + content
952
- var exh=figma.createFrame();exh.name=s.label;
953
- exh.layoutMode="HORIZONTAL";
954
- exh.primaryAxisSizingMode="AUTO";exh.counterAxisSizingMode="AUTO";
955
- exh.itemSpacing=${EXHIBIT_GAP};exh.fills=[];
956
- exh.counterAxisAlignItems="MIN";
957
- axGrp.appendChild(exh);exh.layoutSizingHorizontal="FILL";
958
-
959
- // Artwork (350x248 gray)
960
- var art=figma.createFrame();art.name="Artwork";
961
- art.layoutMode="HORIZONTAL";
962
- art.primaryAxisSizingMode="FIXED";art.counterAxisSizingMode="FIXED";
963
- art.resize(${ARTWORK_W},${ARTWORK_H});
964
- art.fills=[{type:"SOLID",color:${C.bgCard}}];art.cornerRadius=8;
965
- art.primaryAxisAlignItems="CENTER";art.counterAxisAlignItems="CENTER";
966
- art.clipsContent=true;
967
- exh.appendChild(art);
968
- art.appendChild(inst);
969
-
970
- // Content metadata
971
- var cnt=figma.createFrame();cnt.name="Content";
972
- cnt.layoutMode="VERTICAL";
973
- cnt.primaryAxisSizingMode="AUTO";cnt.counterAxisSizingMode="AUTO";
974
- cnt.itemSpacing=12;cnt.fills=[];
975
- exh.appendChild(cnt);cnt.layoutSizingHorizontal="FILL";
976
-
977
- // Option label (16px bold)
978
- var optLbl=figma.createText();optLbl.fontName=F.b;optLbl.fontSize=16;
979
- optLbl.lineHeight={unit:"PIXELS",value:22};
980
- optLbl.characters=s.label;
981
- optLbl.fills=[{type:"SOLID",color:${C.textMain}}];
982
- optLbl.textAutoResize="HEIGHT";
983
- cnt.appendChild(optLbl);optLbl.layoutSizingHorizontal="FILL";
984
-
985
- // Style attributes
986
- var attrCol=figma.createFrame();attrCol.name="Attributes";
987
- attrCol.layoutMode="VERTICAL";
988
- attrCol.primaryAxisSizingMode="AUTO";attrCol.counterAxisSizingMode="AUTO";
989
- attrCol.itemSpacing=4;attrCol.fills=[];
990
- cnt.appendChild(attrCol);attrCol.layoutSizingHorizontal="FILL";
991
-
992
- // EightShapes-style: circle icon + element name, then indented attrs
993
- function addElement(parent,name){
994
- var elRow=figma.createFrame();elRow.name=name;
995
- elRow.layoutMode="HORIZONTAL";
996
- elRow.primaryAxisSizingMode="AUTO";elRow.counterAxisSizingMode="AUTO";
997
- elRow.itemSpacing=6;elRow.fills=[];elRow.counterAxisAlignItems="CENTER";
998
- parent.appendChild(elRow);elRow.layoutSizingHorizontal="FILL";
999
- var ic=figma.createEllipse();ic.resize(10,10);
1000
- ic.fills=[];ic.strokes=[{type:"SOLID",color:${C.textMuted}}];ic.strokeWeight=1;
1001
- elRow.appendChild(ic);
1002
- var nm=figma.createText();nm.fontName=F.sb;nm.fontSize=12;
1003
- nm.lineHeight={unit:"PIXELS",value:18};nm.characters=name;
1004
- nm.fills=[{type:"SOLID",color:${C.textMain}}];nm.textAutoResize="WIDTH_AND_HEIGHT";
1005
- elRow.appendChild(nm);
1006
- }
1007
- function addAttr(parent,name,value){
1008
- var row=figma.createFrame();row.name=name;
1009
- row.layoutMode="HORIZONTAL";
1010
- row.primaryAxisSizingMode="AUTO";row.counterAxisSizingMode="AUTO";
1011
- row.itemSpacing=4;row.fills=[];row.paddingLeft=16;
1012
- parent.appendChild(row);row.layoutSizingHorizontal="FILL";
1013
- var n=figma.createText();n.fontName=F.r;n.fontSize=11;
1014
- n.lineHeight={unit:"PIXELS",value:16};n.characters=name+":";
1015
- n.fills=[{type:"SOLID",color:${C.textMuted}}];n.textAutoResize="WIDTH_AND_HEIGHT";
1016
- n.resize(80,16);row.appendChild(n);n.layoutSizingHorizontal="FIXED";
1017
- if(value&&value.charAt(0)==="#"&&value.length===7){
1018
- var hex=value.replace("#","");
1019
- var sw=figma.createRectangle();sw.resize(12,12);sw.cornerRadius=2;
1020
- sw.fills=[{type:"SOLID",color:{
1021
- r:parseInt(hex.substring(0,2),16)/255,
1022
- g:parseInt(hex.substring(2,4),16)/255,
1023
- b:parseInt(hex.substring(4,6),16)/255
1024
- }}];sw.strokes=[{type:"SOLID",color:${C.border}}];sw.strokeWeight=0.5;
1025
- row.appendChild(sw);
1026
- }
1027
- var v=figma.createText();v.fontName=F.sb;v.fontSize=11;
1028
- v.lineHeight={unit:"PIXELS",value:16};v.characters=value;
1029
- v.fills=[{type:"SOLID",color:${C.textMain}}];v.textAutoResize="WIDTH_AND_HEIGHT";
1030
- row.appendChild(v);
1031
- }
1032
-
1033
- // Root element
1034
- addElement(attrCol,inst.name||s.label);
1035
- for(var fi=0;fi<Math.min(style.fills.length,3);fi++){
1036
- addAttr(attrCol,"Fill",style.fills[fi]);
1037
- }
1038
- if(style.radius)addAttr(attrCol,"Radius",style.radius+"px");
1039
- if(style.padding)addAttr(attrCol,"Padding",style.padding);
1040
- addAttr(attrCol,"Size",style.w+" \\u00D7 "+style.h);
1041
- // Child text elements as separate entries
1042
- for(var ti=0;ti<Math.min(style.texts.length,4);ti++){
1043
- var parts=style.texts[ti].split(" ");
1044
- var tName=parts[0]||"Text";
1045
- addElement(attrCol,tName);
1046
- addAttr(attrCol,"Font",style.texts[ti]);
1047
- }
1048
- }
1049
-
1050
- return{ok:true};
1051
- })();
1052
- `);
1053
- if (!r.success) console.error(`Variants exhibit "${axisName}" failed:`, r.error);
1054
- }
1055
-
1056
- // Phase 6: Boolean toggles
1057
- const boolToggles = (snapshot.componentProperties || []).filter(cp => cp.type === "BOOLEAN");
1058
- for (const toggle of boolToggles) {
1059
- const cleanName = toggle.name.replace(/#.*$/, "").trim();
1060
- const r = await bridge.execute(`
1061
- (async () => {
1062
- ${FL}
1063
- var card=await figma.getNodeByIdAsync(${esc(cardId)});
1064
- if(!card)return{error:"no card"};
1065
- var sourceNode=await figma.getNodeByIdAsync(${esc(spec.nodeId)});
1066
- if(!sourceNode)return{error:"no source"};
1067
-
1068
- // Bool group
1069
- var boolGrp=figma.createFrame();boolGrp.name=${esc(cleanName + " (Boolean)")};
1070
- boolGrp.layoutMode="VERTICAL";
1071
- boolGrp.primaryAxisSizingMode="AUTO";boolGrp.counterAxisSizingMode="AUTO";
1072
- boolGrp.itemSpacing=40;boolGrp.fills=[];
1073
- card.appendChild(boolGrp);boolGrp.layoutSizingHorizontal="FILL";
1074
-
1075
- ${subsectionTitleJS("boolTitle", "boolGrp", cleanName + " (Boolean)")}
1076
-
1077
- var compSet=sourceNode.type==="COMPONENT_SET"?sourceNode:null;
1078
- if(!compSet||compSet.children.length===0)return{ok:true};
1079
- var base=compSet.children[0];
1080
-
1081
- var propKey=null;
1082
- if("componentProperties" in compSet&&compSet.componentProperties){
1083
- for(var k in compSet.componentProperties){
1084
- if(k.replace(/#.*$/,"").trim()===${esc(cleanName)}){propKey=k;break;}
1085
- }
1086
- }
1087
-
1088
- // True/false exhibits side by side
1089
- var boolRow=figma.createFrame();boolRow.name="Toggle Comparison";
1090
- boolRow.layoutMode="HORIZONTAL";
1091
- boolRow.primaryAxisSizingMode="AUTO";boolRow.counterAxisSizingMode="AUTO";
1092
- boolRow.itemSpacing=24;boolRow.fills=[];
1093
- boolGrp.appendChild(boolRow);boolRow.layoutSizingHorizontal="FILL";
1094
-
1095
- var vals=[{v:true,label:"Visible (true)"},{v:false,label:"Hidden (false)"}];
1096
- for(var vi=0;vi<vals.length;vi++){
1097
- var inst=base.createInstance();
1098
- if(propKey){try{var pp={};pp[propKey]=vals[vi].v;inst.setProperties(pp);}catch(e){}}
1099
-
1100
- var bCard=figma.createFrame();bCard.name=${esc(cleanName)}+"="+vals[vi].v;
1101
- bCard.layoutMode="VERTICAL";
1102
- bCard.primaryAxisSizingMode="AUTO";bCard.counterAxisSizingMode="AUTO";
1103
- bCard.itemSpacing=16;
1104
- bCard.paddingTop=24;bCard.paddingBottom=24;bCard.paddingLeft=24;bCard.paddingRight=24;
1105
- bCard.fills=[{type:"SOLID",color:${C.bgCard}}];bCard.cornerRadius=8;
1106
- bCard.primaryAxisAlignItems="CENTER";
1107
- boolRow.appendChild(bCard);bCard.layoutSizingHorizontal="FILL";
1108
-
1109
- bCard.appendChild(inst);
1110
-
1111
- var bLbl=figma.createText();bLbl.fontName=F.b;bLbl.fontSize=16;
1112
- bLbl.characters=vals[vi].label;
1113
- bLbl.fills=[{type:"SOLID",color:${C.textMain}}];
1114
- bLbl.textAutoResize="WIDTH_AND_HEIGHT";
1115
- bCard.appendChild(bLbl);
1116
-
1117
- var bSub=figma.createText();bSub.fontName=F.r;bSub.fontSize=12;
1118
- bSub.characters="Boolean layer";
1119
- bSub.fills=[{type:"SOLID",color:${C.textMuted}}];
1120
- bSub.textAutoResize="WIDTH_AND_HEIGHT";
1121
- bCard.appendChild(bSub);
1122
- }
1123
-
1124
- return{ok:true};
1125
- })();
1126
- `);
1127
- if (!r.success) console.error(`Boolean toggle "${cleanName}" failed:`, r.error);
1128
- }
1129
-
1130
- // Phase 6: Instance swaps
1131
- const instSwaps = (snapshot.componentProperties || []).filter(cp => cp.type === "INSTANCE_SWAP");
1132
- for (const swap of instSwaps) {
1133
- const cleanName = swap.name.replace(/#.*$/, "").trim();
1134
- const r = await bridge.execute(`
1135
- (async () => {
1136
- ${FL}
1137
- var card=await figma.getNodeByIdAsync(${esc(cardId)});
1138
- if(!card)return{error:"no card"};
1139
-
1140
- var swapGrp=figma.createFrame();swapGrp.name=${esc(cleanName + " (Instance Swap)")};
1141
- swapGrp.layoutMode="VERTICAL";
1142
- swapGrp.primaryAxisSizingMode="AUTO";swapGrp.counterAxisSizingMode="AUTO";
1143
- swapGrp.itemSpacing=16;swapGrp.fills=[];
1144
- card.appendChild(swapGrp);swapGrp.layoutSizingHorizontal="FILL";
1145
-
1146
- ${subsectionTitleJS("swapTitle", "swapGrp", cleanName + " (Instance Swap)")}
1147
-
1148
- // Info card
1149
- var swapInfo=figma.createFrame();swapInfo.name="Swap Info";
1150
- swapInfo.layoutMode="HORIZONTAL";
1151
- swapInfo.primaryAxisSizingMode="AUTO";swapInfo.counterAxisSizingMode="AUTO";
1152
- swapInfo.itemSpacing=${EXHIBIT_GAP};swapInfo.fills=[];
1153
- swapInfo.counterAxisAlignItems="MIN";
1154
- swapGrp.appendChild(swapInfo);swapInfo.layoutSizingHorizontal="FILL";
1155
-
1156
- var swapArt=figma.createFrame();swapArt.name="Artwork";
1157
- swapArt.layoutMode="HORIZONTAL";
1158
- swapArt.primaryAxisSizingMode="FIXED";swapArt.counterAxisSizingMode="FIXED";
1159
- swapArt.resize(${ARTWORK_W},${ARTWORK_H / 2});
1160
- swapArt.fills=[{type:"SOLID",color:${C.bgCard}}];swapArt.cornerRadius=8;
1161
- swapArt.primaryAxisAlignItems="CENTER";swapArt.counterAxisAlignItems="CENTER";
1162
- swapInfo.appendChild(swapArt);
1163
-
1164
- // Placeholder text in artwork
1165
- var phTxt=figma.createText();phTxt.fontName=F.r;phTxt.fontSize=14;
1166
- phTxt.characters="Instance Swap: "+${esc(cleanName)};
1167
- phTxt.fills=[{type:"SOLID",color:${C.textMuted}}];
1168
- phTxt.textAutoResize="WIDTH_AND_HEIGHT";
1169
- swapArt.appendChild(phTxt);
1170
-
1171
- var swapCnt=figma.createFrame();swapCnt.name="Content";
1172
- swapCnt.layoutMode="VERTICAL";
1173
- swapCnt.primaryAxisSizingMode="AUTO";swapCnt.counterAxisSizingMode="AUTO";
1174
- swapCnt.itemSpacing=8;swapCnt.fills=[];
1175
- swapInfo.appendChild(swapCnt);swapCnt.layoutSizingHorizontal="FILL";
1176
-
1177
- var swapLbl=figma.createText();swapLbl.fontName=F.b;swapLbl.fontSize=20;
1178
- swapLbl.characters=${esc(cleanName)};
1179
- swapLbl.fills=[{type:"SOLID",color:${C.textMain}}];
1180
- swapLbl.textAutoResize="WIDTH_AND_HEIGHT";
1181
- swapCnt.appendChild(swapLbl);
1182
-
1183
- var swapVal=figma.createText();swapVal.fontName=F.r;swapVal.fontSize=14;
1184
- swapVal.characters="Current: "+${esc(swap.value || "Default")};
1185
- swapVal.fills=[{type:"SOLID",color:${C.textMuted}}];
1186
- swapVal.textAutoResize="WIDTH_AND_HEIGHT";
1187
- swapCnt.appendChild(swapVal);
1188
-
1189
- var swapType=figma.createText();swapType.fontName=F.r;swapType.fontSize=12;
1190
- swapType.characters="Type: Instance Swap";
1191
- swapType.fills=[{type:"SOLID",color:${C.textMuted}}];
1192
- swapType.textAutoResize="WIDTH_AND_HEIGHT";
1193
- swapCnt.appendChild(swapType);
1194
-
1195
- return{ok:true};
1196
- })();
1197
- `);
1198
- if (!r.success) console.error(`Instance swap "${cleanName}" failed:`, r.error);
1199
- }
1200
- }
1201
-
1202
- // ══════════════════════════════════════════════════════════════════════════
1203
- // PHASE 3b: COMPACT PROPERTY TABLE — Summary table + variant strips
1204
- // ══════════════════════════════════════════════════════════════════════════
1205
-
1206
- async function renderPropertyTable(
1207
- bridge: Bridge, mId: string, spec: ComponentSpec, sectionNum?: number,
1208
- ): Promise<void> {
1209
- const snapshot = spec.extraction.snapshot;
1210
- const variantGroupProps = snapshot.variantGroupProperties || {};
1211
- const axisNames = Object.keys(variantGroupProps);
1212
- const boolToggles = (snapshot.componentProperties || []).filter(cp => cp.type === "BOOLEAN");
1213
- const instSwaps = (snapshot.componentProperties || []).filter(cp => cp.type === "INSTANCE_SWAP");
1214
-
1215
- if (axisNames.length === 0 && boolToggles.length === 0 && instSwaps.length === 0) return;
1216
-
1217
- // Build property summary rows
1218
- const propRows: Array<[string, string, string, string]> = [];
1219
- for (const axis of axisNames) {
1220
- const vals = variantGroupProps[axis];
1221
- propRows.push([axis, "Variant", vals[0] || "\u2014", vals.slice(0, 8).join(", ")]);
1222
- }
1223
- for (const b of boolToggles) {
1224
- const name = b.name.replace(/#.*$/, "").trim();
1225
- propRows.push([name, "Boolean", String(b.value), "true, false"]);
1226
- }
1227
- for (const s of instSwaps) {
1228
- const name = s.name.replace(/#.*$/, "").trim();
1229
- propRows.push([name, "Instance Swap", s.value || "Default", "\u2014"]);
1230
- }
1231
-
1232
- // Build all variant strips + boolean toggles JS to embed in single call
1233
- const defaultProps = snapshot.variants?.[0]?.properties || {};
1234
- const MAX_PER_AXIS = 6;
1235
- const compW = snapshot.width || 200;
1236
- const compH = snapshot.height || 60;
1237
- // 3-column grid: card width = (content area - 2 gaps) / 3
1238
- const GRID_GAP = 16;
1239
- const GRID_COLS = 3;
1240
- const cardInnerW = Math.floor((CONTENT_W - CARD_PAD * 2 - GRID_GAP * (GRID_COLS - 1)) / GRID_COLS);
1241
- const thumbScale = Math.min(2, Math.max(0.8, (cardInnerW - 32) / Math.max(compW, 1)));
1242
- const thumbW = cardInnerW - 32; // artwork width = card width minus padding
1243
- const thumbH = Math.max(48, Math.min(120, Math.round(compH * thumbScale) + 16));
1244
-
1245
- // Build JS for all axis strips — 3-column card grid with style metadata
1246
- const allAxesJS = axisNames.map((axisName, ai) => {
1247
- const values = variantGroupProps[axisName].slice(0, MAX_PER_AXIS);
1248
- const remaining = variantGroupProps[axisName].length - values.length;
1249
- const samples = values.map(val => ({
1250
- label: val,
1251
- props: { ...defaultProps, [axisName]: val },
1252
- }));
1253
- return `
1254
- // ── Axis: ${axisName} ──
1255
- ${subsectionTitleJS(`axT${ai}`, "propCard", axisName)}
1256
- var grid${ai}=figma.createFrame();grid${ai}.name=${esc(axisName + " Grid")};
1257
- grid${ai}.layoutMode="HORIZONTAL";grid${ai}.layoutWrap="WRAP";
1258
- grid${ai}.primaryAxisSizingMode="AUTO";grid${ai}.counterAxisSizingMode="AUTO";
1259
- grid${ai}.itemSpacing=${GRID_GAP};grid${ai}.counterAxisSpacing=${GRID_GAP};grid${ai}.fills=[];
1260
- propCard.appendChild(grid${ai});grid${ai}.layoutSizingHorizontal="FILL";
1261
-
1262
- ${EXTRACT_STYLE_FN}
1263
-
1264
- if(compSet){
1265
- var samples${ai}=${JSON.stringify(samples)};
1266
- for(var si=0;si<samples${ai}.length;si++){
1267
- var s=samples${ai}[si];
1268
- var target=null;
1269
- for(var ci=0;ci<compSet.children.length;ci++){
1270
- var ch=compSet.children[ci];
1271
- if(!ch.variantProperties)continue;
1272
- var match=true;
1273
- for(var pk in s.props){if(ch.variantProperties[pk]!==s.props[pk]){match=false;break;}}
1274
- if(match){target=ch;break;}
1275
- }
1276
- if(!target&&compSet.children.length>0)target=compSet.children[0];
1277
- if(!target)continue;
1278
-
1279
- // Fixed-width card for 3-column grid
1280
- var card${ai}=figma.createFrame();card${ai}.name=s.label;
1281
- card${ai}.layoutMode="VERTICAL";
1282
- card${ai}.primaryAxisSizingMode="AUTO";card${ai}.counterAxisSizingMode="FIXED";
1283
- card${ai}.resize(${cardInnerW},10);
1284
- card${ai}.itemSpacing=12;
1285
- card${ai}.paddingTop=16;card${ai}.paddingBottom=16;card${ai}.paddingLeft=16;card${ai}.paddingRight=16;
1286
- card${ai}.fills=[{type:"SOLID",color:${C.bgCard}}];card${ai}.cornerRadius=8;
1287
- grid${ai}.appendChild(card${ai});
1288
-
1289
- // Artwork container — centered
1290
- var art=figma.createFrame();art.name="Artwork";
1291
- art.layoutMode="HORIZONTAL";
1292
- art.primaryAxisSizingMode="FIXED";art.counterAxisSizingMode="FIXED";
1293
- art.resize(${thumbW},${thumbH});
1294
- art.fills=[];art.cornerRadius=4;
1295
- art.primaryAxisAlignItems="CENTER";art.counterAxisAlignItems="CENTER";
1296
- art.clipsContent=true;
1297
- card${ai}.appendChild(art);art.layoutSizingHorizontal="FILL";
1298
- var inst=target.createInstance();
1299
- art.appendChild(inst);
1300
-
1301
- // Bold label
1302
- var lbl=figma.createText();lbl.fontName=F.b;lbl.fontSize=14;
1303
- lbl.lineHeight={unit:"PIXELS",value:20};lbl.characters=s.label;
1304
- lbl.fills=[{type:"SOLID",color:${C.textMain}}];
1305
- lbl.textAutoResize="HEIGHT";
1306
- card${ai}.appendChild(lbl);lbl.layoutSizingHorizontal="FILL";
1307
-
1308
- // Style metadata from instance
1309
- var sInfo=extractStyle(inst);
1310
- var metaParts=[];
1311
- if(sInfo.fills.length>0)metaParts.push("Fill: "+sInfo.fills[0]);
1312
- if(sInfo.radius)metaParts.push("Radius: "+sInfo.radius+"px");
1313
- if(sInfo.padding)metaParts.push("Padding: "+sInfo.padding);
1314
- if(sInfo.w&&sInfo.h)metaParts.push(sInfo.w+"\\u00D7"+sInfo.h);
1315
- if(sInfo.texts.length>0)metaParts.push(sInfo.texts[0]);
1316
- var metaStr=metaParts.slice(0,3).join(" \\u00B7 ");
1317
- if(metaStr){
1318
- var meta=figma.createText();meta.fontName=F.mono;meta.fontSize=11;
1319
- meta.lineHeight={unit:"PIXELS",value:16};meta.characters=metaStr;
1320
- meta.fills=[{type:"SOLID",color:${C.textMuted}}];
1321
- meta.textAutoResize="HEIGHT";
1322
- card${ai}.appendChild(meta);meta.layoutSizingHorizontal="FILL";
1323
- }
1324
- }
1325
- ${remaining > 0 ? `
1326
- // "and N more" label
1327
- var moreLabel${ai}=figma.createText();moreLabel${ai}.fontName=F.r;moreLabel${ai}.fontSize=13;
1328
- moreLabel${ai}.lineHeight={unit:"PIXELS",value:20};
1329
- moreLabel${ai}.characters="and ${remaining} more value${remaining > 1 ? "s" : ""}\\u2026";
1330
- moreLabel${ai}.fills=[{type:"SOLID",color:${C.textMuted}}];
1331
- moreLabel${ai}.textAutoResize="WIDTH_AND_HEIGHT";
1332
- propCard.appendChild(moreLabel${ai});
1333
- ` : ""}
1334
- }`;
1335
- }).join("\n");
1336
-
1337
- // Build JS for boolean toggles
1338
- const allBoolsJS = boolToggles.map((toggle, bi) => {
1339
- const cleanName = toggle.name.replace(/#.*$/, "").trim();
1340
- return `
1341
- // ── Boolean: ${cleanName} ──
1342
- ${subsectionTitleJS(`boolT${bi}`, "propCard", cleanName + " (Boolean)")}
1343
- if(compSet&&compSet.children.length>0){
1344
- var base${bi}=compSet.children[0];
1345
- var propKey${bi}=null;
1346
- if("componentProperties" in compSet&&compSet.componentProperties){
1347
- for(var k in compSet.componentProperties){
1348
- if(k.replace(/#.*$/,"").trim()===${esc(cleanName)}){propKey${bi}=k;break;}
1349
- }
1350
- }
1351
- var boolRow${bi}=figma.createFrame();boolRow${bi}.name="Toggle Comparison";
1352
- boolRow${bi}.layoutMode="HORIZONTAL";
1353
- boolRow${bi}.primaryAxisSizingMode="AUTO";boolRow${bi}.counterAxisSizingMode="AUTO";
1354
- boolRow${bi}.itemSpacing=16;boolRow${bi}.fills=[];
1355
- propCard.appendChild(boolRow${bi});boolRow${bi}.layoutSizingHorizontal="FILL";
1356
- var bvals${bi}=[{v:true,label:"true"},{v:false,label:"false"}];
1357
- for(var vi=0;vi<bvals${bi}.length;vi++){
1358
- var binst=base${bi}.createInstance();
1359
- if(propKey${bi}){try{var pp={};pp[propKey${bi}]=bvals${bi}[vi].v;binst.setProperties(pp);}catch(e){}}
1360
- var bc=figma.createFrame();bc.name=${esc(cleanName)}+"="+bvals${bi}[vi].v;
1361
- bc.layoutMode="VERTICAL";
1362
- bc.primaryAxisSizingMode="AUTO";bc.counterAxisSizingMode="AUTO";
1363
- bc.itemSpacing=8;
1364
- bc.paddingTop=16;bc.paddingBottom=16;bc.paddingLeft=16;bc.paddingRight=16;
1365
- bc.fills=[{type:"SOLID",color:${C.bgCard}}];bc.cornerRadius=8;
1366
- bc.primaryAxisAlignItems="CENTER";
1367
- boolRow${bi}.appendChild(bc);bc.layoutSizingHorizontal="FILL";
1368
- bc.appendChild(binst);
1369
- var blbl=figma.createText();blbl.fontName=F.sb;blbl.fontSize=12;
1370
- blbl.characters=bvals${bi}[vi].label;
1371
- blbl.fills=[{type:"SOLID",color:${C.textMain}}];
1372
- blbl.textAutoResize="WIDTH_AND_HEIGHT";
1373
- bc.appendChild(blbl);
1374
- }
1375
- }`;
1376
- }).join("\n");
1377
-
1378
- // Single consolidated call: table + all strips + all booleans
1379
- const propRowsJSON = JSON.stringify(propRows);
1380
- const r0 = await bridge.execute(`
1381
- (async () => {
1382
- ${FL}
1383
- var m=await figma.getNodeByIdAsync(${esc(mId)});
1384
- if(!m)return{error:"no master"};
1385
- var sourceNode=await figma.getNodeByIdAsync(${esc(spec.nodeId)});
1386
- if(!sourceNode)return{error:"no source"};
1387
-
1388
- ${sectionCardOpenJS("propCard", "m", "Properties")}
1389
- ${sectionTitleJS("propTitle", "propCard", "Properties", sectionNum)}
1390
-
1391
- // ── Property summary table ──
1392
- var headers=["Property","Type","Default","Options"];
1393
- var rows=${propRowsJSON};
1394
- var colWidths=[180,120,140,0];
1395
-
1396
- var tbl=figma.createFrame();tbl.name="Property Table";
1397
- tbl.layoutMode="VERTICAL";
1398
- tbl.primaryAxisSizingMode="AUTO";tbl.counterAxisSizingMode="AUTO";
1399
- tbl.itemSpacing=0;tbl.fills=[{type:"SOLID",color:${C.white}}];
1400
- tbl.cornerRadius=8;
1401
- tbl.strokes=[{type:"SOLID",color:${C.border}}];tbl.strokeWeight=1;tbl.strokeAlign="INSIDE";
1402
- tbl.clipsContent=true;
1403
- propCard.appendChild(tbl);tbl.layoutSizingHorizontal="FILL";
1404
-
1405
- var hdr=figma.createFrame();hdr.name="Header";
1406
- hdr.layoutMode="HORIZONTAL";
1407
- hdr.primaryAxisSizingMode="AUTO";hdr.counterAxisSizingMode="AUTO";
1408
- hdr.itemSpacing=0;hdr.paddingTop=12;hdr.paddingBottom=12;
1409
- hdr.paddingLeft=20;hdr.paddingRight=20;
1410
- hdr.fills=[{type:"SOLID",color:${C.bgCard}}];hdr.counterAxisAlignItems="CENTER";
1411
- hdr.strokes=[{type:"SOLID",color:${C.border}}];hdr.strokeWeight=1;
1412
- hdr.strokeTopWeight=0;hdr.strokeRightWeight=0;hdr.strokeBottomWeight=1;hdr.strokeLeftWeight=0;
1413
- hdr.strokeAlign="INSIDE";
1414
- tbl.appendChild(hdr);hdr.layoutSizingHorizontal="FILL";
1415
-
1416
- for(var hi=0;hi<headers.length;hi++){
1417
- var ht=figma.createText();ht.fontName=F.sb;ht.fontSize=13;
1418
- ht.lineHeight={unit:"PIXELS",value:20};ht.characters=headers[hi];
1419
- ht.fills=[{type:"SOLID",color:${C.textMain}}];ht.textAutoResize="HEIGHT";
1420
- hdr.appendChild(ht);
1421
- if(colWidths[hi]>0){ht.resize(colWidths[hi],20);ht.layoutSizingHorizontal="FIXED";}
1422
- else{ht.layoutSizingHorizontal="FILL";}
1423
- }
1424
-
1425
- for(var ri=0;ri<rows.length;ri++){
1426
- var row=figma.createFrame();row.name=rows[ri][0];
1427
- row.layoutMode="HORIZONTAL";
1428
- row.primaryAxisSizingMode="AUTO";row.counterAxisSizingMode="AUTO";
1429
- row.itemSpacing=0;row.paddingTop=10;row.paddingBottom=10;
1430
- row.paddingLeft=20;row.paddingRight=20;
1431
- row.fills=[{type:"SOLID",color:ri%2===0?${C.white}:${C.bgCard}}];
1432
- row.counterAxisAlignItems="CENTER";
1433
- if(ri<rows.length-1){
1434
- row.strokes=[{type:"SOLID",color:${C.border}}];row.strokeWeight=1;
1435
- row.strokeTopWeight=0;row.strokeRightWeight=0;row.strokeBottomWeight=1;row.strokeLeftWeight=0;
1436
- row.strokeAlign="INSIDE";
1437
- }
1438
- tbl.appendChild(row);row.layoutSizingHorizontal="FILL";
1439
- for(var ci=0;ci<4;ci++){
1440
- var ct=figma.createText();
1441
- ct.fontName=ci===3?F.mono:(ci===0?F.sb:F.r);
1442
- ct.fontSize=ci===3?11:13;
1443
- ct.lineHeight={unit:"PIXELS",value:ci===3?16:20};
1444
- ct.characters=rows[ri][ci]||"\\u2014";
1445
- ct.fills=[{type:"SOLID",color:ci===0?${C.textMain}:${C.textMuted}}];
1446
- ct.textAutoResize="HEIGHT";
1447
- row.appendChild(ct);
1448
- if(colWidths[ci]>0){ct.resize(colWidths[ci],20);ct.layoutSizingHorizontal="FIXED";}
1449
- else{ct.layoutSizingHorizontal="FILL";}
1450
- }
1451
- }
1452
-
1453
- // ── Variant strips + booleans ──
1454
- var compSet=sourceNode.type==="COMPONENT_SET"?sourceNode:(sourceNode.parent&&sourceNode.parent.type==="COMPONENT_SET"?sourceNode.parent:null);
1455
-
1456
- ${allAxesJS}
1457
- ${allBoolsJS}
1458
-
1459
- return{ok:true};
1460
- })();
1461
- `);
1462
- if (!r0.success) console.error("Property table failed:", r0.error);
1463
- }
1464
-
1465
- // ══════════════════════════════════════════════════════════════════════════
1466
- // PHASE 4b: SIZING COMPARISON — Side-by-side size variants with dimensions
1467
- // ══════════════════════════════════════════════════════════════════════════
1468
-
1469
- async function renderSizingComparison(
1470
- bridge: Bridge, mId: string, spec: ComponentSpec, sectionNum?: number,
1471
- ): Promise<void> {
1472
- const snapshot = spec.extraction.snapshot;
1473
- const variantGroupProps = snapshot.variantGroupProperties || {};
1474
- // Find a "Size" axis
1475
- const sizeAxis = Object.keys(variantGroupProps).find(k => /^size$/i.test(k));
1476
- if (!sizeAxis) return;
1477
-
1478
- const sizeValues = variantGroupProps[sizeAxis];
1479
- if (sizeValues.length < 2) return;
1480
-
1481
- const defaultProps = snapshot.variants?.[0]?.properties || {};
1482
- const spacing = spec.extraction.spacing;
1483
- const root = spacing.length > 0 ? spacing[0] : null;
1484
- const LINE_W = 1.5;
1485
-
1486
- // Build samples for each size value
1487
- const samples = sizeValues.slice(0, 5).map(val => ({
1488
- label: val,
1489
- props: { ...defaultProps, [sizeAxis]: val },
1490
- }));
1491
- const samplesJSON = JSON.stringify(samples);
1492
-
1493
- const r = await bridge.execute(`
1494
- (async () => {
1495
- ${FL}
1496
- var m=await figma.getNodeByIdAsync(${esc(mId)});
1497
- if(!m)return{error:"no master"};
1498
- var sourceNode=await figma.getNodeByIdAsync(${esc(spec.nodeId)});
1499
- if(!sourceNode)return{error:"no source"};
1500
-
1501
- ${sectionCardOpenJS("szCard", "m", "Spacing & Dimensions")}
1502
- ${sectionTitleJS("szTitle", "szCard", "Spacing & Dimensions", sectionNum)}
1503
-
1504
- var compSet=sourceNode.type==="COMPONENT_SET"?sourceNode:(sourceNode.parent&&sourceNode.parent.type==="COMPONENT_SET"?sourceNode.parent:null);
1505
- if(!compSet)return{ok:true};
1506
-
1507
- // Horizontal row of size variants, bottom-aligned
1508
- var szRow=figma.createFrame();szRow.name="Size Comparison";
1509
- szRow.layoutMode="HORIZONTAL";
1510
- szRow.primaryAxisSizingMode="AUTO";szRow.counterAxisSizingMode="AUTO";
1511
- szRow.itemSpacing=32;szRow.fills=[];
1512
- szRow.counterAxisAlignItems="MAX";
1513
- szCard.appendChild(szRow);szRow.layoutSizingHorizontal="FILL";
1514
-
1515
- var samples=${samplesJSON};
1516
- var sizeColor=${C.sizeBlue};
1517
- var padColor=${C.padGreen};
1518
-
1519
- ${EXTRACT_STYLE_FN}
1520
-
1521
- for(var si=0;si<samples.length;si++){
1522
- var s=samples[si];
1523
- var target=null;
1524
- for(var ci=0;ci<compSet.children.length;ci++){
1525
- var ch=compSet.children[ci];
1526
- if(!ch.variantProperties)continue;
1527
- var match=true;
1528
- for(var pk in s.props){if(ch.variantProperties[pk]!==s.props[pk]){match=false;break;}}
1529
- if(match){target=ch;break;}
1530
- }
1531
- if(!target)continue;
1532
-
1533
- // Column for this size variant
1534
- var col=figma.createFrame();col.name=s.label;
1535
- col.layoutMode="VERTICAL";
1536
- col.primaryAxisSizingMode="AUTO";col.counterAxisSizingMode="AUTO";
1537
- col.itemSpacing=0;col.fills=[];
1538
- col.primaryAxisAlignItems="CENTER";
1539
- szRow.appendChild(col);col.layoutSizingHorizontal="FILL";
1540
-
1541
- // Size label header
1542
- var szLbl=figma.createText();szLbl.fontName=F.b;szLbl.fontSize=14;
1543
- szLbl.lineHeight={unit:"PIXELS",value:20};
1544
- szLbl.characters=s.label;
1545
- szLbl.fills=[{type:"SOLID",color:${C.textMain}}];
1546
- szLbl.textAutoResize="WIDTH_AND_HEIGHT";
1547
- col.appendChild(szLbl);
1548
-
1549
- // Spacer
1550
- var sp1=figma.createFrame();sp1.resize(4,16);sp1.fills=[];
1551
- col.appendChild(sp1);sp1.layoutSizingHorizontal="FILL";
1552
-
1553
- // Canvas with component + dimension annotations
1554
- var inst=target.createInstance();
1555
- var iW=inst.width,iH=inst.height;
1556
- var margin=40;
1557
-
1558
- var canvas=figma.createFrame();canvas.name=s.label+" Canvas";
1559
- canvas.layoutMode="NONE";
1560
- canvas.fills=[{type:"SOLID",color:${C.bgCard}}];canvas.cornerRadius=8;
1561
- canvas.resize(iW+margin*2,iH+margin*2);
1562
- col.appendChild(canvas);
1563
-
1564
- inst.x=margin;inst.y=margin;
1565
- canvas.appendChild(inst);
1566
-
1567
- // Blue outline
1568
- var outline=figma.createRectangle();
1569
- outline.resize(iW,iH);outline.x=margin;outline.y=margin;outline.fills=[];
1570
- outline.strokes=[{type:"SOLID",color:sizeColor}];outline.strokeWeight=1;outline.strokeAlign="OUTSIDE";
1571
- canvas.appendChild(outline);
1572
-
1573
- // Width dimension line (above)
1574
- function dimLine(parent,x1,y1,x2,y2,label,color){
1575
- var isH=Math.abs(y2-y1)<Math.abs(x2-x1);
1576
- var line=figma.createVector();
1577
- line.vectorPaths=[{windingRule:"NONZERO",data:"M "+x1+" "+y1+" L "+x2+" "+y2}];
1578
- line.strokes=[{type:"SOLID",color:color}];line.strokeWeight=${LINE_W};line.fills=[];
1579
- line.strokeCap="ROUND";parent.appendChild(line);
1580
- var cap=4;
1581
- if(isH){
1582
- var c1=figma.createVector();c1.vectorPaths=[{windingRule:"NONZERO",data:"M "+x1+" "+(y1-cap)+" L "+x1+" "+(y1+cap)}];
1583
- c1.strokes=[{type:"SOLID",color:color}];c1.strokeWeight=${LINE_W};c1.fills=[];parent.appendChild(c1);
1584
- var c2=figma.createVector();c2.vectorPaths=[{windingRule:"NONZERO",data:"M "+x2+" "+(y2-cap)+" L "+x2+" "+(y2+cap)}];
1585
- c2.strokes=[{type:"SOLID",color:color}];c2.strokeWeight=${LINE_W};c2.fills=[];parent.appendChild(c2);
1586
- }else{
1587
- var c1=figma.createVector();c1.vectorPaths=[{windingRule:"NONZERO",data:"M "+(x1-cap)+" "+y1+" L "+(x1+cap)+" "+y1}];
1588
- c1.strokes=[{type:"SOLID",color:color}];c1.strokeWeight=${LINE_W};c1.fills=[];parent.appendChild(c1);
1589
- var c2=figma.createVector();c2.vectorPaths=[{windingRule:"NONZERO",data:"M "+(x2-cap)+" "+y2+" L "+(x2+cap)+" "+y2}];
1590
- c2.strokes=[{type:"SOLID",color:color}];c2.strokeWeight=${LINE_W};c2.fills=[];parent.appendChild(c2);
1591
- }
1592
- var lbl=figma.createText();lbl.fontName=F.mono;lbl.fontSize=10;
1593
- lbl.characters=label;lbl.fills=[{type:"SOLID",color:color}];
1594
- lbl.textAutoResize="WIDTH_AND_HEIGHT";parent.appendChild(lbl);
1595
- var mx=(x1+x2)/2,my=(y1+y2)/2;
1596
- if(isH){lbl.x=mx-lbl.width/2;lbl.y=my-lbl.height-4;}
1597
- else{lbl.x=mx+4;lbl.y=my-lbl.height/2;}
1598
- }
1599
-
1600
- dimLine(canvas,margin,margin-14,margin+iW,margin-14,Math.round(iW)+"px",sizeColor);
1601
- dimLine(canvas,margin-14,margin,margin-14,margin+iH,Math.round(iH)+"px",sizeColor);
1602
-
1603
- // Spacer before metadata
1604
- var sp2=figma.createFrame();sp2.resize(4,12);sp2.fills=[];
1605
- col.appendChild(sp2);sp2.layoutSizingHorizontal="FILL";
1606
-
1607
- // Extract and show key dimensions
1608
- var info=extractStyle(inst);
1609
- var dimParts=[];
1610
- dimParts.push("Height: "+Math.round(iH)+"px");
1611
- dimParts.push("Width: "+Math.round(iW)+"px");
1612
- if(info.padding)dimParts.push("Padding: "+info.padding);
1613
- if(info.radius)dimParts.push("Radius: "+info.radius+"px");
1614
-
1615
- for(var di=0;di<dimParts.length;di++){
1616
- var dTxt=figma.createText();dTxt.fontName=F.mono;dTxt.fontSize=11;
1617
- dTxt.lineHeight={unit:"PIXELS",value:18};
1618
- dTxt.characters=dimParts[di];
1619
- dTxt.fills=[{type:"SOLID",color:${C.textMuted}}];
1620
- dTxt.textAutoResize="HEIGHT";
1621
- col.appendChild(dTxt);dTxt.layoutSizingHorizontal="FILL";
1622
- }
1623
- }
1624
-
1625
- return{ok:true};
1626
- })();
1627
- `);
1628
- if (!r.success) console.error("Sizing comparison failed:", r.error);
1629
- }
1630
-
1631
- // ══════════════════════════════════════════════════════════════════════════
1632
- // PHASE 5: SPACING EXHIBIT — Green/orange/blue overlays + content panel
1633
- // ══════════════════════════════════════════════════════════════════════════
1634
-
1635
- async function renderSpacingExhibit(
1636
- bridge: Bridge, mId: string, spec: ComponentSpec, sectionNum?: number,
1637
- ): Promise<void> {
1638
- const spacing = spec.extraction.spacing;
1639
- if (spacing.length === 0) return;
1640
-
1641
- const root = spacing[0];
1642
- const childrenData = JSON.stringify(root.children || []);
1643
- const LINE_W = 1.5;
1644
-
1645
- const r = await bridge.execute(`
1646
- (async () => {
1647
- ${FL}
1648
- var m=await figma.getNodeByIdAsync(${esc(mId)});
1649
- if(!m)return{error:"no master"};
1650
- var sourceNode=await figma.getNodeByIdAsync(${esc(spec.nodeId)});
1651
- if(!sourceNode)return{error:"no source"};
1652
-
1653
- // Section card
1654
- ${sectionCardOpenJS("spcCard", "m", "Layout & Spacing")}
1655
- ${sectionTitleJS("spcTitle", "spcCard", "Layout & Spacing", sectionNum)}
1656
-
1657
- // Exhibit: artwork left | content right
1658
- var spcExh=figma.createFrame();spcExh.name="Spacing Exhibit";
1659
- spcExh.layoutMode="HORIZONTAL";
1660
- spcExh.primaryAxisSizingMode="AUTO";spcExh.counterAxisSizingMode="AUTO";
1661
- spcExh.itemSpacing=${EXHIBIT_GAP};spcExh.fills=[];
1662
- spcExh.counterAxisAlignItems="MIN";
1663
- spcCard.appendChild(spcExh);spcExh.layoutSizingHorizontal="FILL";
1664
-
1665
- // ── Left: Artwork with overlays (absolute positioning) ──
1666
- var src=sourceNode;
1667
- if(src.type==="COMPONENT_SET"&&src.children.length>0)src=src.children[0];
1668
- var compClone=src.clone();
1669
- var cW=compClone.width,cH=compClone.height;
1670
- var AM=80;
1671
-
1672
- var canvas=figma.createFrame();canvas.name="Spacing Artwork";
1673
- canvas.layoutMode="NONE";
1674
- canvas.fills=[{type:"SOLID",color:${C.bgCard}}];canvas.cornerRadius=8;
1675
- canvas.resize(Math.max(cW+AM*2,500),cH+AM*2);
1676
- spcExh.appendChild(canvas);
1677
-
1678
- compClone.x=AM;compClone.y=AM;
1679
- canvas.appendChild(compClone);
1680
-
1681
- // Blue outline around component
1682
- var blueOutline=figma.createRectangle();
1683
- blueOutline.resize(cW,cH);blueOutline.x=AM;blueOutline.y=AM;
1684
- blueOutline.fills=[];
1685
- blueOutline.strokes=[{type:"SOLID",color:${C.sizeBlue}}];
1686
- blueOutline.strokeWeight=1;blueOutline.strokeAlign="OUTSIDE";
1687
- canvas.appendChild(blueOutline);
1688
-
1689
- var padColor=${C.padGreen};
1690
- var gapColor=${C.gapOrange};
1691
- var sizeColor=${C.sizeBlue};
1692
- var pt=${root.paddingTop},pr=${root.paddingRight},pb=${root.paddingBottom},pl=${root.paddingLeft};
1693
- var compX=AM,compY=AM;
1694
-
1695
- // ── Green padding: L-shaped corner brackets ──
1696
- function drawBracket(parent,x,y,orient,armLen,color){
1697
- var hPath,vPath;
1698
- if(orient==="TL"){hPath="M "+x+" "+y+" L "+(x+armLen)+" "+y;vPath="M "+x+" "+y+" L "+x+" "+(y+armLen);}
1699
- else if(orient==="TR"){hPath="M "+x+" "+y+" L "+(x-armLen)+" "+y;vPath="M "+x+" "+y+" L "+x+" "+(y+armLen);}
1700
- else if(orient==="BL"){hPath="M "+x+" "+y+" L "+(x+armLen)+" "+y;vPath="M "+x+" "+y+" L "+x+" "+(y-armLen);}
1701
- else{hPath="M "+x+" "+y+" L "+(x-armLen)+" "+y;vPath="M "+x+" "+y+" L "+x+" "+(y-armLen);}
1702
- var h=figma.createVector();h.vectorPaths=[{windingRule:"NONZERO",data:hPath}];
1703
- h.strokes=[{type:"SOLID",color:color}];h.strokeWeight=1.5;h.fills=[];h.strokeCap="ROUND";parent.appendChild(h);
1704
- var v=figma.createVector();v.vectorPaths=[{windingRule:"NONZERO",data:vPath}];
1705
- v.strokes=[{type:"SOLID",color:color}];v.strokeWeight=1.5;v.fills=[];v.strokeCap="ROUND";parent.appendChild(v);
1706
- }
1707
- var bArm=Math.min(12,cW/6,cH/6);
1708
- if(pt>0){
1709
- drawBracket(canvas,compX,compY+pt,"TL",bArm,padColor);
1710
- drawBracket(canvas,compX+cW,compY+pt,"TR",bArm,padColor);
1711
- }
1712
- if(pb>0){
1713
- drawBracket(canvas,compX,compY+cH-pb,"BL",bArm,padColor);
1714
- drawBracket(canvas,compX+cW,compY+cH-pb,"BR",bArm,padColor);
1715
- }
1716
- if(pl>0){
1717
- drawBracket(canvas,compX+pl,compY,"TL",bArm,padColor);
1718
- drawBracket(canvas,compX+pl,compY+cH,"BL",bArm,padColor);
1719
- }
1720
- if(pr>0){
1721
- drawBracket(canvas,compX+cW-pr,compY,"TR",bArm,padColor);
1722
- drawBracket(canvas,compX+cW-pr,compY+cH,"BR",bArm,padColor);
1723
- }
1724
-
1725
- // Green padding pills with px suffix
1726
- function padPill(x,y,value){
1727
- var pill=figma.createFrame();pill.name="PadPill";
1728
- pill.layoutMode="HORIZONTAL";
1729
- pill.primaryAxisSizingMode="AUTO";pill.counterAxisSizingMode="AUTO";
1730
- pill.paddingTop=2;pill.paddingBottom=2;pill.paddingLeft=4;pill.paddingRight=4;
1731
- pill.fills=[{type:"SOLID",color:padColor}];pill.cornerRadius=4;
1732
- canvas.appendChild(pill);pill.x=x;pill.y=y;
1733
- var pTxt=figma.createText();pTxt.fontName=F.r;pTxt.fontSize=11;
1734
- pTxt.characters=value+"px";
1735
- pTxt.fills=[{type:"SOLID",color:{r:1,g:1,b:1}}];
1736
- pTxt.textAutoResize="WIDTH_AND_HEIGHT";
1737
- pill.appendChild(pTxt);
1738
- }
1739
- if(pt>0)padPill(compX+cW/2-12,compY+2,pt);
1740
- if(pb>0)padPill(compX+cW/2-12,compY+cH-16,pb);
1741
- if(pl>0)padPill(compX-28,compY+cH/2-8,pl);
1742
- if(pr>0)padPill(compX+cW+4,compY+cH/2-8,pr);
1743
-
1744
- // ── Orange gap dashed lines + pills between children ──
1745
- var children=${childrenData};
1746
- var layoutMode="${root.layoutMode}";
1747
- var itemSpacing=${root.itemSpacing};
1748
- if(children.length>1&&itemSpacing>0){
1749
- for(var ci=0;ci<children.length-1;ci++){
1750
- var c1=children[ci],c2=children[ci+1];
1751
- if(layoutMode==="HORIZONTAL"){
1752
- var gapX1=compX+c1.x+c1.w;
1753
- var gapX2=compX+c2.x;
1754
- var gapW=gapX2-gapX1;
1755
- if(gapW>0){
1756
- // Dashed line spanning the gap
1757
- var gLine=figma.createVector();
1758
- gLine.vectorPaths=[{windingRule:"NONZERO",data:"M "+gapX1+" "+(compY+cH/2)+" L "+gapX2+" "+(compY+cH/2)}];
1759
- gLine.strokes=[{type:"SOLID",color:gapColor}];gLine.strokeWeight=1;gLine.fills=[];
1760
- gLine.dashPattern=[4,4];canvas.appendChild(gLine);
1761
- // Gap pill centered above
1762
- var gPill=figma.createFrame();gPill.name="GapPill";
1763
- gPill.layoutMode="HORIZONTAL";
1764
- gPill.primaryAxisSizingMode="AUTO";gPill.counterAxisSizingMode="AUTO";
1765
- gPill.paddingTop=2;gPill.paddingBottom=2;gPill.paddingLeft=4;gPill.paddingRight=4;
1766
- gPill.fills=[{type:"SOLID",color:gapColor}];gPill.cornerRadius=4;
1767
- canvas.appendChild(gPill);gPill.x=gapX1+gapW/2-12;gPill.y=compY+cH/2-18;
1768
- var gTxt=figma.createText();gTxt.fontName=F.r;gTxt.fontSize=11;
1769
- gTxt.characters=String(itemSpacing)+"px";
1770
- gTxt.fills=[{type:"SOLID",color:{r:1,g:1,b:1}}];
1771
- gTxt.textAutoResize="WIDTH_AND_HEIGHT";
1772
- gPill.appendChild(gTxt);
1773
- }
1774
- }else{
1775
- var gapY1=compY+c1.y+c1.h;
1776
- var gapY2=compY+c2.y;
1777
- var gapH=gapY2-gapY1;
1778
- if(gapH>0){
1779
- var gLine=figma.createVector();
1780
- gLine.vectorPaths=[{windingRule:"NONZERO",data:"M "+(compX+cW/2)+" "+gapY1+" L "+(compX+cW/2)+" "+gapY2}];
1781
- gLine.strokes=[{type:"SOLID",color:gapColor}];gLine.strokeWeight=1;gLine.fills=[];
1782
- gLine.dashPattern=[4,4];canvas.appendChild(gLine);
1783
- var gPill=figma.createFrame();gPill.name="GapPill";
1784
- gPill.layoutMode="HORIZONTAL";
1785
- gPill.primaryAxisSizingMode="AUTO";gPill.counterAxisSizingMode="AUTO";
1786
- gPill.paddingTop=2;gPill.paddingBottom=2;gPill.paddingLeft=4;gPill.paddingRight=4;
1787
- gPill.fills=[{type:"SOLID",color:gapColor}];gPill.cornerRadius=4;
1788
- canvas.appendChild(gPill);gPill.x=compX+cW+8;gPill.y=gapY1+gapH/2-8;
1789
- var gTxt=figma.createText();gTxt.fontName=F.r;gTxt.fontSize=11;
1790
- gTxt.characters=String(itemSpacing)+"px";
1791
- gTxt.fills=[{type:"SOLID",color:{r:1,g:1,b:1}}];
1792
- gTxt.textAutoResize="WIDTH_AND_HEIGHT";
1793
- gPill.appendChild(gTxt);
1794
- }
1795
- }
1796
- }
1797
- }
1798
-
1799
- // ── Blue child element overlays (subtle) ──
1800
- for(var chi=0;chi<children.length;chi++){
1801
- var ch=children[chi];
1802
- var chRect=figma.createRectangle();
1803
- chRect.resize(ch.w,ch.h);chRect.x=compX+ch.x;chRect.y=compY+ch.y;
1804
- chRect.fills=[{type:"SOLID",color:sizeColor}];chRect.opacity=0.08;
1805
- canvas.appendChild(chRect);
1806
- }
1807
-
1808
- // ── Blue dimension lines (width above, height left) ──
1809
- function drawDimLine(parent,x1,y1,x2,y2,label,color){
1810
- var isH=Math.abs(y2-y1)<Math.abs(x2-x1);
1811
- var line=figma.createVector();
1812
- line.vectorPaths=[{windingRule:"NONZERO",data:"M "+x1+" "+y1+" L "+x2+" "+y2}];
1813
- line.strokes=[{type:"SOLID",color:color}];
1814
- line.strokeWeight=${LINE_W};line.fills=[];line.strokeCap="ROUND";
1815
- parent.appendChild(line);
1816
- var capLen=4;
1817
- if(isH){
1818
- var c1=figma.createVector();
1819
- c1.vectorPaths=[{windingRule:"NONZERO",data:"M "+x1+" "+(y1-capLen)+" L "+x1+" "+(y1+capLen)}];
1820
- c1.strokes=[{type:"SOLID",color:color}];c1.strokeWeight=${LINE_W};c1.fills=[];parent.appendChild(c1);
1821
- var c2=figma.createVector();
1822
- c2.vectorPaths=[{windingRule:"NONZERO",data:"M "+x2+" "+(y2-capLen)+" L "+x2+" "+(y2+capLen)}];
1823
- c2.strokes=[{type:"SOLID",color:color}];c2.strokeWeight=${LINE_W};c2.fills=[];parent.appendChild(c2);
1824
- }else{
1825
- var c1=figma.createVector();
1826
- c1.vectorPaths=[{windingRule:"NONZERO",data:"M "+(x1-capLen)+" "+y1+" L "+(x1+capLen)+" "+y1}];
1827
- c1.strokes=[{type:"SOLID",color:color}];c1.strokeWeight=${LINE_W};c1.fills=[];parent.appendChild(c1);
1828
- var c2=figma.createVector();
1829
- c2.vectorPaths=[{windingRule:"NONZERO",data:"M "+(x2-capLen)+" "+y2+" L "+(x2+capLen)+" "+y2}];
1830
- c2.strokes=[{type:"SOLID",color:color}];c2.strokeWeight=${LINE_W};c2.fills=[];parent.appendChild(c2);
1831
- }
1832
- var lbl=figma.createText();lbl.fontName=F.mono;lbl.fontSize=10;
1833
- lbl.characters=label;lbl.fills=[{type:"SOLID",color:color}];
1834
- lbl.textAutoResize="WIDTH_AND_HEIGHT";parent.appendChild(lbl);
1835
- var mx=(x1+x2)/2,my=(y1+y2)/2;
1836
- if(isH){lbl.x=mx-lbl.width/2;lbl.y=my-lbl.height-4;}
1837
- else{lbl.x=mx+4;lbl.y=my-lbl.height/2;}
1838
- }
1839
-
1840
- // Width dimension (above component)
1841
- drawDimLine(canvas,compX,compY-20,compX+cW,compY-20,Math.round(cW)+"px",sizeColor);
1842
- // Height dimension (left of component)
1843
- drawDimLine(canvas,compX-20,compY,compX-20,compY+cH,Math.round(cH)+"px",sizeColor);
1844
-
1845
- // Resize canvas to fit
1846
- canvas.resize(Math.max(cW+AM*2,500),cH+AM*2);
1847
-
1848
- // ── Right: Content panel ──
1849
- var spcCnt=figma.createFrame();spcCnt.name="Spacing Details";
1850
- spcCnt.layoutMode="VERTICAL";
1851
- spcCnt.primaryAxisSizingMode="AUTO";spcCnt.counterAxisSizingMode="AUTO";
1852
- spcCnt.itemSpacing=12;spcCnt.fills=[];
1853
- spcExh.appendChild(spcCnt);spcCnt.layoutSizingHorizontal="FILL";
1854
-
1855
- // Element header with type icon
1856
- var spcElRow=figma.createFrame();spcElRow.name="ElementHeader";
1857
- spcElRow.layoutMode="HORIZONTAL";
1858
- spcElRow.primaryAxisSizingMode="AUTO";spcElRow.counterAxisSizingMode="AUTO";
1859
- spcElRow.itemSpacing=6;spcElRow.fills=[];spcElRow.counterAxisAlignItems="CENTER";
1860
- spcCnt.appendChild(spcElRow);spcElRow.layoutSizingHorizontal="FILL";
1861
-
1862
- // Type icon (diamond for component)
1863
- var spcIcon=figma.createFrame();spcIcon.name="TypeIcon";
1864
- spcIcon.layoutMode="HORIZONTAL";
1865
- spcIcon.primaryAxisSizingMode="FIXED";spcIcon.counterAxisSizingMode="FIXED";
1866
- spcIcon.resize(20,20);spcIcon.fills=[];
1867
- spcIcon.strokes=[{type:"SOLID",color:${C.textMuted}}];
1868
- spcIcon.strokeWeight=1;spcIcon.strokeAlign="INSIDE";spcIcon.cornerRadius=3;
1869
- spcIcon.primaryAxisAlignItems="CENTER";spcIcon.counterAxisAlignItems="CENTER";
1870
- spcElRow.appendChild(spcIcon);
1871
- var spcIconLbl=figma.createText();spcIconLbl.fontName=F.r;spcIconLbl.fontSize=10;
1872
- spcIconLbl.characters="\\u25C7";
1873
- spcIconLbl.fills=[{type:"SOLID",color:${C.textMuted}}];
1874
- spcIconLbl.textAutoResize="WIDTH_AND_HEIGHT";
1875
- spcIcon.appendChild(spcIconLbl);
1876
-
1877
- // Element name
1878
- var spcName=figma.createText();spcName.fontName=F.b;spcName.fontSize=20;
1879
- spcName.lineHeight={unit:"PIXELS",value:28};
1880
- spcName.characters=${esc(root.element)};
1881
- spcName.fills=[{type:"SOLID",color:${C.textMain}}];
1882
- spcName.textAutoResize="WIDTH_AND_HEIGHT";
1883
- spcElRow.appendChild(spcName);
1884
-
1885
- // Attributes — two-column layout (muted label + semibold value)
1886
- function spcAttr(parent,label,value){
1887
- var row=figma.createFrame();row.name=label;
1888
- row.layoutMode="HORIZONTAL";
1889
- row.primaryAxisSizingMode="AUTO";row.counterAxisSizingMode="AUTO";
1890
- row.itemSpacing=8;row.fills=[];
1891
- parent.appendChild(row);row.layoutSizingHorizontal="FILL";
1892
- var lTxt=figma.createText();lTxt.fontName=F.r;lTxt.fontSize=12;
1893
- lTxt.lineHeight={unit:"PIXELS",value:18};lTxt.characters=label;
1894
- lTxt.fills=[{type:"SOLID",color:${C.textMuted}}];lTxt.textAutoResize="HEIGHT";
1895
- lTxt.resize(140,18);row.appendChild(lTxt);lTxt.layoutSizingHorizontal="FIXED";
1896
- var vTxt=figma.createText();vTxt.fontName=F.sb;vTxt.fontSize=12;
1897
- vTxt.lineHeight={unit:"PIXELS",value:18};vTxt.characters=String(value);
1898
- vTxt.fills=[{type:"SOLID",color:${C.textMain}}];vTxt.textAutoResize="HEIGHT";
1899
- row.appendChild(vTxt);vTxt.layoutSizingHorizontal="FILL";
1900
- }
1901
-
1902
- spcAttr(spcCnt,"Direction",${esc(root.layoutMode === "HORIZONTAL" ? "Horizontal" : root.layoutMode === "VERTICAL" ? "Vertical" : "None")});
1903
- spcAttr(spcCnt,"Vertical resizing",${esc(root.layoutSizingV || "Fixed")});
1904
- spcAttr(spcCnt,"Horizontal resizing",${esc(root.layoutSizingH || "Fixed")});
1905
- spcAttr(spcCnt,"Item spacing",String(${root.itemSpacing}));
1906
- spcAttr(spcCnt,"Padding top",String(${root.paddingTop}));
1907
- spcAttr(spcCnt,"Padding bottom",String(${root.paddingBottom}));
1908
- spcAttr(spcCnt,"Padding left",String(${root.paddingLeft}));
1909
- spcAttr(spcCnt,"Padding right",String(${root.paddingRight}));
1910
-
1911
- // ── Legend ──
1912
- var spcLeg=figma.createFrame();spcLeg.name="Legend";
1913
- spcLeg.layoutMode="HORIZONTAL";
1914
- spcLeg.primaryAxisSizingMode="AUTO";spcLeg.counterAxisSizingMode="AUTO";
1915
- spcLeg.itemSpacing=24;spcLeg.fills=[];spcLeg.paddingTop=16;
1916
- spcCard.appendChild(spcLeg);spcLeg.layoutSizingHorizontal="FILL";
1917
-
1918
- function legItem(parent,color,label){
1919
- var li=figma.createFrame();li.layoutMode="HORIZONTAL";
1920
- li.primaryAxisSizingMode="AUTO";li.counterAxisSizingMode="AUTO";
1921
- li.itemSpacing=8;li.fills=[];li.counterAxisAlignItems="CENTER";
1922
- parent.appendChild(li);
1923
- var sw=figma.createRectangle();sw.resize(12,12);
1924
- sw.fills=[{type:"SOLID",color:color}];sw.cornerRadius=2;li.appendChild(sw);
1925
- var lt=figma.createText();lt.fontName=F.r;lt.fontSize=12;
1926
- lt.characters=label;lt.fills=[{type:"SOLID",color:${C.textMuted}}];
1927
- lt.textAutoResize="WIDTH_AND_HEIGHT";li.appendChild(lt);
1928
- }
1929
- legItem(spcLeg,padColor,"Padding");
1930
- legItem(spcLeg,gapColor,"Item spacing");
1931
- legItem(spcLeg,sizeColor,"Element sizing");
1932
-
1933
- return{ok:true};
1934
- })();
1935
- `);
1936
- if (!r.success) console.error("Spacing exhibit failed:", r.error);
1937
- }
1938
-
1939
- // ── Color Token Exhibit ─────────────────────────────────────────────────
1940
-
1941
- async function renderColorExhibit(
1942
- bridge: Bridge, mId: string, spec: ComponentSpec, sectionNum?: number,
1943
- ): Promise<void> {
1944
- const tokens = spec.extraction.colorTokens;
1945
- if (tokens.length === 0) return;
1946
-
1947
- const seen = new Set<string>();
1948
- const unique = tokens.filter(t => {
1949
- const key = `${t.colorHex}|${t.tokenName}`;
1950
- if (seen.has(key)) return false;
1951
- seen.add(key);
1952
- return true;
1953
- }).slice(0, 30);
1954
-
1955
- // Group tokens by semantic role derived from token name or element
1956
- const rolePatterns: [RegExp, string][] = [
1957
- [/primary|brand/i, "Primary"],
1958
- [/secondary/i, "Secondary"],
1959
- [/danger|error|destructive/i, "Danger"],
1960
- [/warning|caution/i, "Warning"],
1961
- [/success|positive/i, "Success"],
1962
- [/interactive|action|link/i, "Interactive"],
1963
- [/disabled|inactive/i, "Disabled"],
1964
- [/inverse|on-color/i, "Inverse"],
1965
- [/border|stroke|outline/i, "Border"],
1966
- [/background|bg|surface|layer/i, "Background"],
1967
- [/text|label|content|foreground/i, "Text"],
1968
- [/icon/i, "Icon"],
1969
- [/focus|ring/i, "Focus"],
1970
- [/hover/i, "Hover"],
1971
- ];
1972
-
1973
- function classifyToken(t: { tokenName: string; element: string }): string {
1974
- const combined = `${t.tokenName} ${t.element}`;
1975
- for (const [re, label] of rolePatterns) {
1976
- if (re.test(combined)) return label;
1977
- }
1978
- return "Other";
1979
- }
1980
-
1981
- // Build grouped structure
1982
- const groupMap = new Map<string, typeof unique>();
1983
- for (const t of unique) {
1984
- const role = classifyToken(t);
1985
- if (!groupMap.has(role)) groupMap.set(role, []);
1986
- groupMap.get(role)!.push(t);
1987
- }
1988
- const groups = Array.from(groupMap.entries());
1989
-
1990
- const groupsJSON = JSON.stringify(groups);
1991
-
1992
- const r = await bridge.execute(`
1993
- (async () => {
1994
- ${FL}
1995
- var m=await figma.getNodeByIdAsync(${esc(mId)});
1996
- if(!m)return{error:"no master"};
1997
-
1998
- ${sectionCardOpenJS("colCard", "m", "Color Tokens")}
1999
- ${sectionTitleJS("colTitle", "colCard", "Color Tokens by Type", sectionNum)}
2000
-
2001
- var groups=${groupsJSON};
2002
-
2003
- for(var gi=0;gi<groups.length;gi++){
2004
- var groupName=groups[gi][0];
2005
- var groupTokens=groups[gi][1];
2006
-
2007
- // Group sub-header
2008
- var grpTitle=figma.createText();grpTitle.fontName=F.b;grpTitle.fontSize=16;
2009
- grpTitle.lineHeight={unit:"PIXELS",value:24};grpTitle.characters=groupName;
2010
- grpTitle.fills=[{type:"SOLID",color:${C.textMain}}];
2011
- grpTitle.textAutoResize="HEIGHT";
2012
- colCard.appendChild(grpTitle);grpTitle.layoutSizingHorizontal="FILL";
2013
-
2014
- // Horizontal swatch row for this group
2015
- var swatchRow=figma.createFrame();swatchRow.name=groupName+" Swatches";
2016
- swatchRow.layoutMode="HORIZONTAL";swatchRow.layoutWrap="WRAP";
2017
- swatchRow.primaryAxisSizingMode="AUTO";swatchRow.counterAxisSizingMode="AUTO";
2018
- swatchRow.itemSpacing=12;swatchRow.counterAxisSpacing=12;swatchRow.fills=[];
2019
- colCard.appendChild(swatchRow);swatchRow.layoutSizingHorizontal="FILL";
2020
-
2021
- for(var ti=0;ti<groupTokens.length;ti++){
2022
- var t=groupTokens[ti];
2023
-
2024
- // Swatch card: color block + label
2025
- var sc=figma.createFrame();sc.name=t.tokenName||t.colorHex;
2026
- sc.layoutMode="VERTICAL";
2027
- sc.primaryAxisSizingMode="AUTO";sc.counterAxisSizingMode="FIXED";
2028
- sc.resize(140,10);
2029
- sc.itemSpacing=6;sc.paddingTop=8;sc.paddingBottom=10;sc.paddingLeft=8;sc.paddingRight=8;
2030
- sc.fills=[{type:"SOLID",color:${C.bgCard}}];sc.cornerRadius=8;
2031
- swatchRow.appendChild(sc);
2032
-
2033
- // Color swatch rectangle
2034
- var sw=figma.createRectangle();sw.resize(124,36);sw.cornerRadius=6;
2035
- var hex=t.colorHex.replace("#","");
2036
- var cr=parseInt(hex.substring(0,2),16)/255;
2037
- var cg=parseInt(hex.substring(2,4),16)/255;
2038
- var cb=parseInt(hex.substring(4,6),16)/255;
2039
- sw.fills=[{type:"SOLID",color:{r:cr,g:cg,b:cb}}];
2040
- sw.strokes=[{type:"SOLID",color:${C.border}}];sw.strokeWeight=1;sw.strokeAlign="INSIDE";
2041
- sc.appendChild(sw);sw.layoutSizingHorizontal="FILL";
2042
-
2043
- // Hex label
2044
- var hexLbl=figma.createText();hexLbl.fontName=F.mono;hexLbl.fontSize=11;
2045
- hexLbl.lineHeight={unit:"PIXELS",value:16};hexLbl.characters=t.colorHex.toUpperCase();
2046
- hexLbl.fills=[{type:"SOLID",color:${C.textMain}}];
2047
- hexLbl.textAutoResize="HEIGHT";
2048
- sc.appendChild(hexLbl);hexLbl.layoutSizingHorizontal="FILL";
2049
-
2050
- // Token name
2051
- if(t.tokenName){
2052
- var tokLbl=figma.createText();tokLbl.fontName=F.r;tokLbl.fontSize=10;
2053
- tokLbl.lineHeight={unit:"PIXELS",value:14};tokLbl.characters=t.tokenName;
2054
- tokLbl.fills=[{type:"SOLID",color:${C.textMuted}}];
2055
- tokLbl.textAutoResize="HEIGHT";
2056
- sc.appendChild(tokLbl);tokLbl.layoutSizingHorizontal="FILL";
2057
- }
2058
- }
2059
-
2060
- // Spacer between groups (except last)
2061
- if(gi<groups.length-1){
2062
- var sep=figma.createFrame();sep.resize(4,8);sep.fills=[];
2063
- colCard.appendChild(sep);sep.layoutSizingHorizontal="FILL";
2064
- }
2065
- }
2066
-
2067
- return{ok:true};
2068
- })();
2069
- `);
2070
- if (!r.success) console.error("Color exhibit failed:", r.error);
2071
- }
2072
-
2073
- // ── Typography Exhibit ──────────────────────────────────────────────────
2074
-
2075
- async function renderTypographyExhibit(
2076
- bridge: Bridge, mId: string, spec: ComponentSpec, sectionNum?: number,
2077
- ): Promise<void> {
2078
- const typo = spec.extraction.typography;
2079
- if (typo.length === 0) return;
2080
-
2081
- const seen = new Set<string>();
2082
- const unique = typo.filter(t => {
2083
- const key = `${t.fontFamily}|${t.fontStyle}|${t.fontSize}`;
2084
- if (seen.has(key)) return false;
2085
- seen.add(key);
2086
- return true;
2087
- }).slice(0, 16);
2088
-
2089
- const typoJSON = JSON.stringify(unique.map(t => ({
2090
- element: t.element,
2091
- sample: (t.characters || "").slice(0, 40) || t.element,
2092
- family: t.fontFamily,
2093
- style: t.fontStyle,
2094
- size: t.fontSize,
2095
- lineHeight: t.lineHeightPx,
2096
- letterSpacing: t.letterSpacing,
2097
- token: t.tokenName,
2098
- })));
2099
-
2100
- const r = await bridge.execute(`
2101
- (async () => {
2102
- ${FL}
2103
- var m=await figma.getNodeByIdAsync(${esc(mId)});
2104
- if(!m)return{error:"no master"};
2105
-
2106
- ${sectionCardOpenJS("typCard", "m", "Typography")}
2107
- ${sectionTitleJS("typTitle", "typCard", "Typography", sectionNum)}
2108
-
2109
- var typo=${typoJSON};
2110
-
2111
- // Batch-load all unique fonts
2112
- var fontCache={};
2113
- for(var fi=0;fi<typo.length;fi++){
2114
- var fk=typo[fi].family+"|"+typo[fi].style;
2115
- if(!fontCache[fk]){
2116
- try{
2117
- await figma.loadFontAsync({family:typo[fi].family,style:typo[fi].style});
2118
- fontCache[fk]={family:typo[fi].family,style:typo[fi].style};
2119
- }catch(e){
2120
- try{
2121
- await figma.loadFontAsync({family:typo[fi].family,style:"Regular"});
2122
- fontCache[fk]={family:typo[fi].family,style:"Regular"};
2123
- }catch(e2){fontCache[fk]=F.r;}
2124
- }
2125
- }
2126
- }
2127
-
2128
- var outer=figma.createFrame();outer.name="Typography Table";
2129
- outer.layoutMode="VERTICAL";
2130
- outer.primaryAxisSizingMode="AUTO";outer.counterAxisSizingMode="AUTO";
2131
- outer.itemSpacing=0;outer.fills=[];
2132
- outer.cornerRadius=8;
2133
- outer.strokes=[{type:"SOLID",color:${C.border}}];outer.strokeWeight=1;outer.strokeAlign="INSIDE";
2134
- outer.clipsContent=true;
2135
- typCard.appendChild(outer);outer.layoutSizingHorizontal="FILL";
2136
-
2137
- for(var i=0;i<typo.length;i++){
2138
- var t=typo[i];
2139
- var actualSize=Math.min(t.size||14,72);
2140
- var rowPadV=Math.max(16,Math.round(actualSize*0.4));
2141
-
2142
- var row=figma.createFrame();row.name=t.element;
2143
- row.layoutMode="HORIZONTAL";
2144
- row.primaryAxisSizingMode="AUTO";row.counterAxisSizingMode="AUTO";
2145
- row.itemSpacing=24;
2146
- row.paddingTop=rowPadV;row.paddingBottom=rowPadV;row.paddingLeft=20;row.paddingRight=20;
2147
- row.fills=[{type:"SOLID",color:i%2===0?${C.white}:${C.bgCard}}];
2148
- row.counterAxisAlignItems="CENTER";
2149
- if(i<typo.length-1){
2150
- row.strokes=[{type:"SOLID",color:${C.border}}];row.strokeWeight=1;
2151
- row.strokeTopWeight=0;row.strokeRightWeight=0;row.strokeBottomWeight=1;row.strokeLeftWeight=0;
2152
- row.strokeAlign="INSIDE";
2153
- }
2154
- outer.appendChild(row);row.layoutSizingHorizontal="FILL";
2155
-
2156
- var eName=figma.createText();eName.fontName=F.sb;eName.fontSize=13;
2157
- eName.lineHeight={unit:"PIXELS",value:20};eName.characters=t.element;
2158
- eName.fills=[{type:"SOLID",color:${C.textMuted}}];
2159
- eName.textAutoResize="HEIGHT";eName.resize(140,20);
2160
- row.appendChild(eName);eName.layoutSizingHorizontal="FIXED";
2161
-
2162
- // Render sample at actual font size and family
2163
- var sampleFont=fontCache[t.family+"|"+t.style]||F.r;
2164
- var sample=figma.createText();sample.fontName=sampleFont;sample.fontSize=actualSize;
2165
- sample.lineHeight={unit:"PIXELS",value:Math.round(actualSize*1.4)};
2166
- sample.characters=t.sample;
2167
- sample.fills=[{type:"SOLID",color:${C.textMain}}];
2168
- sample.textAutoResize="HEIGHT";
2169
- row.appendChild(sample);sample.layoutSizingHorizontal="FILL";
2170
-
2171
- var spec=t.family+" "+t.style+" "+t.size;
2172
- if(t.lineHeight)spec+="/"+Math.round(t.lineHeight);
2173
- var specTxt=figma.createText();specTxt.fontName=F.mono;specTxt.fontSize=11;
2174
- specTxt.lineHeight={unit:"PIXELS",value:16};specTxt.characters=spec;
2175
- specTxt.fills=[{type:"SOLID",color:${C.textMuted}}];
2176
- specTxt.textAutoResize="HEIGHT";specTxt.resize(220,16);
2177
- row.appendChild(specTxt);specTxt.layoutSizingHorizontal="FIXED";
2178
-
2179
- if(t.token){
2180
- var tokTxt=figma.createText();tokTxt.fontName=F.mono;tokTxt.fontSize=11;
2181
- tokTxt.lineHeight={unit:"PIXELS",value:16};tokTxt.characters=t.token;
2182
- tokTxt.fills=[{type:"SOLID",color:${C.padGreen}}];
2183
- tokTxt.textAutoResize="WIDTH_AND_HEIGHT";
2184
- row.appendChild(tokTxt);
2185
- }
2186
- }
2187
-
2188
- return{ok:true};
2189
- })();
2190
- `);
2191
- if (!r.success) console.error("Typography exhibit failed:", r.error);
2192
- }
2193
-
2194
- // ══════════════════════════════════════════════════════════════════════════
2195
- // MAIN ENTRY — routes visual sections to exhibits, others to text cards
2196
- // ══════════════════════════════════════════════════════════════════════════
2197
-
2198
- export async function renderVisualDoc(spec: ComponentSpec, pageName?: string): Promise<string> {
2199
- const bridge = await getBridge();
2200
- const resolvedName = pageName || `${spec.componentName} \u2014 Spec`;
2201
-
2202
- // 1. Page + master frame
2203
- const { pageId, mId } = await initPage(bridge, resolvedName);
2204
-
2205
- // 2. Header + Hero (merged into one call)
2206
- const description = spec.description
2207
- || `${spec.componentName} is a ${(spec.nodeType || "component").toLowerCase().replace(/_/g, " ")} used in the design system.`;
2208
- // Derive section labels for subtitle
2209
- const sectionLabelMap: Record<string, string> = {
2210
- anatomy: "Anatomy", variants: "Properties", spacing: "Spacing",
2211
- "color-tokens": "Color Tokens", typography: "Typography",
2212
- };
2213
- const sectionLabels = spec.sections
2214
- .map(s => sectionLabelMap[s.id])
2215
- .filter((l): l is string => !!l);
2216
- await renderDocHeaderAndHero(bridge, mId, spec.componentName, description, spec, sectionLabels);
2217
-
2218
- // 3. Sections — route visual sections to exhibit renderers, others to text cards
2219
- let sectionNumber = 0;
2220
- for (let i = 0; i < spec.sections.length; i++) {
2221
- const sec = spec.sections[i];
2222
- sectionNumber++;
2223
-
2224
- switch (sec.id) {
2225
- // ── Visual exhibits (each creates its own card) ──
2226
- case "anatomy":
2227
- if (spec.extraction.anatomy.elements.length > 0) {
2228
- await renderAnatomyExhibit(bridge, mId, spec, sectionNumber);
2229
- } else {
2230
- await renderTextSection(bridge, mId, sec.title, sec.content, sectionNumber);
2231
- }
2232
- break;
2233
-
2234
- case "variants":
2235
- if (spec.extraction.snapshot.variants?.length > 1) {
2236
- await renderPropertyTable(bridge, mId, spec, sectionNumber);
2237
- } else {
2238
- await renderTextSection(bridge, mId, sec.title, sec.content, sectionNumber);
2239
- }
2240
- break;
2241
-
2242
- case "spacing":
2243
- // Render side-by-side sizing comparison if a Size axis exists
2244
- await renderSizingComparison(bridge, mId, spec, sectionNumber);
2245
- if (spec.extraction.spacing.length > 0) {
2246
- sectionNumber++;
2247
- await renderSpacingExhibit(bridge, mId, spec, sectionNumber);
2248
- } else if (!(spec.extraction.snapshot.variantGroupProperties || {})[
2249
- Object.keys(spec.extraction.snapshot.variantGroupProperties || {}).find(k => /^size$/i.test(k)) || ""
2250
- ]) {
2251
- await renderTextSection(bridge, mId, sec.title, sec.content, sectionNumber);
2252
- }
2253
- break;
2254
-
2255
- case "color-tokens":
2256
- if (spec.extraction.colorTokens.length > 0) {
2257
- await renderColorExhibit(bridge, mId, spec, sectionNumber);
2258
- } else {
2259
- await renderTextSection(bridge, mId, sec.title, sec.content, sectionNumber);
2260
- }
2261
- break;
2262
-
2263
- case "typography":
2264
- if (spec.extraction.typography.length > 0) {
2265
- await renderTypographyExhibit(bridge, mId, spec, sectionNumber);
2266
- } else {
2267
- await renderTextSection(bridge, mId, sec.title, sec.content, sectionNumber);
2268
- }
2269
- break;
2270
-
2271
- // ── Text-based sections (each in its own white card) ──
2272
- default:
2273
- await renderTextSection(bridge, mId, sec.title, sec.content, sectionNumber);
2274
- }
2275
- }
2276
-
2277
- // 3b. Footer + Zoom to fit (merged into one call)
2278
- {
2279
- const timestamp = new Date().toLocaleDateString("en-US", {
2280
- year: "numeric", month: "long", day: "numeric",
2281
- });
2282
- try {
2283
- await bridge.execute(`
2284
- (async () => {
2285
- ${FL}
2286
- var m=await figma.getNodeByIdAsync(${esc(mId)});
2287
- if(!m)return{error:"no master"};
2288
-
2289
- // Footer
2290
- var footer=figma.createFrame();footer.name="Footer";
2291
- footer.layoutMode="HORIZONTAL";
2292
- footer.primaryAxisSizingMode="AUTO";footer.counterAxisSizingMode="AUTO";
2293
- footer.itemSpacing=0;footer.fills=[];
2294
- footer.paddingTop=16;footer.paddingBottom=0;
2295
- footer.paddingLeft=8;footer.paddingRight=8;
2296
- footer.strokes=[{type:"SOLID",color:${C.border}}];
2297
- footer.strokeWeight=1;footer.strokeAlign="INSIDE";
2298
- footer.strokeTopWeight=1;footer.strokeRightWeight=0;
2299
- footer.strokeBottomWeight=0;footer.strokeLeftWeight=0;
2300
- footer.counterAxisAlignItems="CENTER";
2301
- m.appendChild(footer);footer.layoutSizingHorizontal="FILL";
2302
-
2303
- var left=figma.createText();left.fontName=F.r;left.fontSize=12;
2304
- left.lineHeight={unit:"PIXELS",value:18};
2305
- left.characters="Generated by MCP Power";
2306
- left.fills=[{type:"SOLID",color:${C.textMuted}}];
2307
- left.textAutoResize="WIDTH_AND_HEIGHT";
2308
- footer.appendChild(left);
2309
-
2310
- var spacer=figma.createFrame();spacer.name="Spacer";
2311
- spacer.resize(4,4);spacer.fills=[];
2312
- footer.appendChild(spacer);spacer.layoutGrow=1;
2313
-
2314
- var right=figma.createText();right.fontName=F.r;right.fontSize=12;
2315
- right.lineHeight={unit:"PIXELS",value:18};
2316
- right.characters=${esc(timestamp)};
2317
- right.fills=[{type:"SOLID",color:${C.textMuted}}];
2318
- right.textAutoResize="WIDTH_AND_HEIGHT";
2319
- footer.appendChild(right);
2320
-
2321
- // Zoom to fit
2322
- var pg=await figma.getNodeByIdAsync(${esc(pageId)});
2323
- if(pg&&pg.type==="PAGE"&&pg.children.length>0)
2324
- figma.viewport.scrollAndZoomIntoView(pg.children);
2325
-
2326
- return{ok:true};
2327
- })();
2328
- `);
2329
- } catch { /* ok */ }
2330
- }
2331
-
2332
- return pageId;
2333
- }