dreamcontext 0.17.1 → 0.18.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 (181) hide show
  1. package/README.md +321 -565
  2. package/agents/sleep-product.md +21 -2
  3. package/agents/sleep-state.md +24 -0
  4. package/agents/sleep-tasks.md +16 -0
  5. package/dist/agents/sleep-product.md +21 -2
  6. package/dist/agents/sleep-state.md +24 -0
  7. package/dist/agents/sleep-tasks.md +16 -0
  8. package/dist/dashboard/announcements/dashboard-highlights-0-17-0-18.excalidraw.md +1531 -0
  9. package/dist/dashboard/announcements/goal-skill-v2.excalidraw.md +1490 -0
  10. package/dist/dashboard/announcements/task-manager.excalidraw.md +1217 -0
  11. package/dist/dashboard/announcements/visual-announcements.excalidraw.md +1373 -0
  12. package/dist/dashboard/announcements.json +38 -0
  13. package/dist/dashboard/assets/{BrainCanvas3D-DyF_3yYD.js → BrainCanvas3D-D9G3dOLf.js} +1 -1
  14. package/dist/dashboard/assets/{_baseUniq-Do8VMBA0.js → _baseUniq-Cyshlz4Y.js} +1 -1
  15. package/dist/dashboard/assets/{ar-SA-G6X2FPQ2-DmbXDb2R.js → ar-SA-G6X2FPQ2-qj-2Cbgo.js} +1 -1
  16. package/dist/dashboard/assets/{arc-CwtUBQfU.js → arc-0OnX5WDs.js} +1 -1
  17. package/dist/dashboard/assets/{architectureDiagram-Q4EWVU46-O9bOog_f.js → architectureDiagram-Q4EWVU46-Cx3c0nUi.js} +1 -1
  18. package/dist/dashboard/assets/{az-AZ-76LH7QW2-c9Zk10vG.js → az-AZ-76LH7QW2-CBuoh6M3.js} +1 -1
  19. package/dist/dashboard/assets/{bg-BG-XCXSNQG7-uMTg84lX.js → bg-BG-XCXSNQG7-X5QYyZWx.js} +1 -1
  20. package/dist/dashboard/assets/{blockDiagram-DXYQGD6D-BTENXCSO.js → blockDiagram-DXYQGD6D-BwESAg1j.js} +1 -1
  21. package/dist/dashboard/assets/{bn-BD-2XOGV67Q-DUFKHcua.js → bn-BD-2XOGV67Q-DlodQLNR.js} +1 -1
  22. package/dist/dashboard/assets/{c4Diagram-AHTNJAMY-DMXPWRiQ.js → c4Diagram-AHTNJAMY-CopL0lbP.js} +1 -1
  23. package/dist/dashboard/assets/{ca-ES-6MX7JW3Y-BuYX1q89.js → ca-ES-6MX7JW3Y-DfqF6Yhy.js} +1 -1
  24. package/dist/dashboard/assets/channel-BLisRbYb.js +1 -0
  25. package/dist/dashboard/assets/{chunk-4BX2VUAB-DgefvnWS.js → chunk-4BX2VUAB-CRwifLK3.js} +1 -1
  26. package/dist/dashboard/assets/{chunk-4TB4RGXK-BnFO5G1t.js → chunk-4TB4RGXK-uxJYY_64.js} +1 -1
  27. package/dist/dashboard/assets/{chunk-55IACEB6-COm2TYEM.js → chunk-55IACEB6-DhYIp7Oc.js} +1 -1
  28. package/dist/dashboard/assets/{chunk-EDXVE4YY-CtsGCwZp.js → chunk-EDXVE4YY-CCxfcirt.js} +1 -1
  29. package/dist/dashboard/assets/{chunk-FMBD7UC4-cfSvx35j.js → chunk-FMBD7UC4-D7OC8QFC.js} +1 -1
  30. package/dist/dashboard/assets/{chunk-OYMX7WX6-DAhDKteL.js → chunk-OYMX7WX6-CwHgaUQo.js} +1 -1
  31. package/dist/dashboard/assets/{chunk-QZHKN3VN-B5h4qML3.js → chunk-QZHKN3VN-7Idpf6YJ.js} +1 -1
  32. package/dist/dashboard/assets/{chunk-YZCP3GAM-CGp_NiRg.js → chunk-YZCP3GAM-D4Lh0fgr.js} +1 -1
  33. package/dist/dashboard/assets/classDiagram-6PBFFD2Q-CgqeQiHi.js +1 -0
  34. package/dist/dashboard/assets/classDiagram-v2-HSJHXN6E-CgqeQiHi.js +1 -0
  35. package/dist/dashboard/assets/clone-CQXNMCV5.js +1 -0
  36. package/dist/dashboard/assets/{cose-bilkent-S5V4N54A-Dk7CMFA4.js → cose-bilkent-S5V4N54A-BYcMtJTF.js} +1 -1
  37. package/dist/dashboard/assets/{cs-CZ-2BRQDIVT-zfUTiS_d.js → cs-CZ-2BRQDIVT-rnrpKdSJ.js} +1 -1
  38. package/dist/dashboard/assets/{da-DK-5WZEPLOC-CU14f8NA.js → da-DK-5WZEPLOC-D5OGAvk7.js} +1 -1
  39. package/dist/dashboard/assets/{dagre-KV5264BT-BiMVxC4Q.js → dagre-KV5264BT-V0f1_b4R.js} +1 -1
  40. package/dist/dashboard/assets/{de-DE-XR44H4JA-CDOHAJGo.js → de-DE-XR44H4JA-CDyb8YfL.js} +1 -1
  41. package/dist/dashboard/assets/{diagram-5BDNPKRD-DfJEsVTA.js → diagram-5BDNPKRD-BmpizoaS.js} +1 -1
  42. package/dist/dashboard/assets/{diagram-G4DWMVQ6-CztLHIcY.js → diagram-G4DWMVQ6-ClQg-LTu.js} +1 -1
  43. package/dist/dashboard/assets/{diagram-MMDJMWI5-DqoQLlWK.js → diagram-MMDJMWI5-BkXcDMIV.js} +1 -1
  44. package/dist/dashboard/assets/{diagram-TYMM5635-DvyvQBCc.js → diagram-TYMM5635-BbFiiCM2.js} +1 -1
  45. package/dist/dashboard/assets/{el-GR-BZB4AONW-ChUphbcv.js → el-GR-BZB4AONW-Y1YnKZL0.js} +1 -1
  46. package/dist/dashboard/assets/{erDiagram-SMLLAGMA-D_uSs2_2.js → erDiagram-SMLLAGMA-Dzdv7_l5.js} +1 -1
  47. package/dist/dashboard/assets/{es-ES-U4NZUMDT-BNalBEhb.js → es-ES-U4NZUMDT-D0T536hs.js} +1 -1
  48. package/dist/dashboard/assets/{eu-ES-A7QVB2H4-BMiQyA2y.js → eu-ES-A7QVB2H4-Bfrya0et.js} +1 -1
  49. package/dist/dashboard/assets/{fa-IR-HGAKTJCU-Bd6ZnTSo.js → fa-IR-HGAKTJCU-pYE_hG-L.js} +1 -1
  50. package/dist/dashboard/assets/{fi-FI-Z5N7JZ37-BWAfJadC.js → fi-FI-Z5N7JZ37-D3C-fNyk.js} +1 -1
  51. package/dist/dashboard/assets/{flowDiagram-DWJPFMVM-Bmsp6kyU.js → flowDiagram-DWJPFMVM-CFhyjwKY.js} +1 -1
  52. package/dist/dashboard/assets/{fr-FR-RHASNOE6-CspbYzOW.js → fr-FR-RHASNOE6-CgwnG5JW.js} +1 -1
  53. package/dist/dashboard/assets/{ganttDiagram-T4ZO3ILL-wEdysXo_.js → ganttDiagram-T4ZO3ILL-BRKRiSzf.js} +1 -1
  54. package/dist/dashboard/assets/{gitGraphDiagram-UUTBAWPF-BEiGpPBj.js → gitGraphDiagram-UUTBAWPF-CTqCK49d.js} +1 -1
  55. package/dist/dashboard/assets/{gl-ES-HMX3MZ6V-CV76vsWr.js → gl-ES-HMX3MZ6V-DjJTqTm3.js} +1 -1
  56. package/dist/dashboard/assets/{graph-CZFIeNHZ.js → graph-D429kSbY.js} +1 -1
  57. package/dist/dashboard/assets/{he-IL-6SHJWFNN-Y1VoXJ_H.js → he-IL-6SHJWFNN-C84FlH6r.js} +1 -1
  58. package/dist/dashboard/assets/{hi-IN-IWLTKZ5I-Crh5S49o.js → hi-IN-IWLTKZ5I-Blhkfum2.js} +1 -1
  59. package/dist/dashboard/assets/{hu-HU-A5ZG7DT2-D4I3gCbp.js → hu-HU-A5ZG7DT2-DLC1JfN-.js} +1 -1
  60. package/dist/dashboard/assets/{id-ID-SAP4L64H-Chvzkuvi.js → id-ID-SAP4L64H-C3pB_IWB.js} +1 -1
  61. package/dist/dashboard/assets/image-FgR93jdt.js +1 -0
  62. package/dist/dashboard/assets/index-2m8ueOUn.js +1 -0
  63. package/dist/dashboard/assets/index-CWwjuihq.css +32 -0
  64. package/dist/dashboard/assets/{index-C7Bo5AQH.js → index-CbOXCDms.js} +1 -1
  65. package/dist/dashboard/assets/index-SJ64pf-4.js +551 -0
  66. package/dist/dashboard/assets/{infoDiagram-42DDH7IO-BzfuDHl4.js → infoDiagram-42DDH7IO-CIzDShDw.js} +1 -1
  67. package/dist/dashboard/assets/{ishikawaDiagram-UXIWVN3A-BVoNJ9VS.js → ishikawaDiagram-UXIWVN3A-CuasYzdX.js} +1 -1
  68. package/dist/dashboard/assets/{it-IT-JPQ66NNP-BH_gN5es.js → it-IT-JPQ66NNP-DGGEoNPu.js} +1 -1
  69. package/dist/dashboard/assets/{ja-JP-DBVTYXUO-DU_WmLVa.js → ja-JP-DBVTYXUO-CEqP9aps.js} +1 -1
  70. package/dist/dashboard/assets/{journeyDiagram-VCZTEJTY-DbwICa_a.js → journeyDiagram-VCZTEJTY-DHiq2OTQ.js} +1 -1
  71. package/dist/dashboard/assets/{kaa-6HZHGXH3-KYU9qowM.js → kaa-6HZHGXH3-GOFmTAXq.js} +1 -1
  72. package/dist/dashboard/assets/{kab-KAB-ZGHBKWFO-bjhyiUq5.js → kab-KAB-ZGHBKWFO-B865Wwzu.js} +1 -1
  73. package/dist/dashboard/assets/{kanban-definition-6JOO6SKY-D9XucLs4.js → kanban-definition-6JOO6SKY-BhlTKFbL.js} +1 -1
  74. package/dist/dashboard/assets/{kk-KZ-P5N5QNE5-CYTx_dAZ.js → kk-KZ-P5N5QNE5-CNfuE6b6.js} +1 -1
  75. package/dist/dashboard/assets/{km-KH-HSX4SM5Z-DqsuY4Lf.js → km-KH-HSX4SM5Z-CsmgRWxu.js} +1 -1
  76. package/dist/dashboard/assets/{ko-KR-MTYHY66A-BZe7NywF.js → ko-KR-MTYHY66A-B_02RgEz.js} +1 -1
  77. package/dist/dashboard/assets/{ku-TR-6OUDTVRD-F5d6HFWt.js → ku-TR-6OUDTVRD-JfQA5u05.js} +1 -1
  78. package/dist/dashboard/assets/{layout-CotqU0S9.js → layout-DvrQSvOI.js} +1 -1
  79. package/dist/dashboard/assets/{linear-Dqv87Scf.js → linear-6r0qttLs.js} +1 -1
  80. package/dist/dashboard/assets/{lt-LT-XHIRWOB4-BCCuaPbS.js → lt-LT-XHIRWOB4-DWFUXEdf.js} +1 -1
  81. package/dist/dashboard/assets/{lv-LV-5QDEKY6T-NLGi-vJ6.js → lv-LV-5QDEKY6T-C5yAd0n_.js} +1 -1
  82. package/dist/dashboard/assets/{min-BbJdnc3h.js → min-D_EP9dBf.js} +1 -1
  83. package/dist/dashboard/assets/{mindmap-definition-QFDTVHPH-Dk4QySu7.js → mindmap-definition-QFDTVHPH-Col3ao9B.js} +1 -1
  84. package/dist/dashboard/assets/{mr-IN-CRQNXWMA-BnGDIgVN.js → mr-IN-CRQNXWMA-Dwjz0Xdm.js} +1 -1
  85. package/dist/dashboard/assets/{my-MM-5M5IBNSE-CspgYE9_.js → my-MM-5M5IBNSE-EXJDNyjA.js} +1 -1
  86. package/dist/dashboard/assets/{nb-NO-T6EIAALU-BeRPw3hu.js → nb-NO-T6EIAALU-BWm2bgLd.js} +1 -1
  87. package/dist/dashboard/assets/{nl-NL-IS3SIHDZ-C-G-bljM.js → nl-NL-IS3SIHDZ-f8nP-yp-.js} +1 -1
  88. package/dist/dashboard/assets/{nn-NO-6E72VCQL-Ct7VZY8D.js → nn-NO-6E72VCQL-B0c_gCNR.js} +1 -1
  89. package/dist/dashboard/assets/{oc-FR-POXYY2M6-Cv5-C4Qq.js → oc-FR-POXYY2M6-C40PSwAh.js} +1 -1
  90. package/dist/dashboard/assets/{pa-IN-N4M65BXN-DAgkIThL.js → pa-IN-N4M65BXN-DDcgXFkB.js} +1 -1
  91. package/dist/dashboard/assets/{percentages-BXMCSKIN-vidRYabK.js → percentages-BXMCSKIN-MHBQP8nI.js} +7 -7
  92. package/dist/dashboard/assets/{pica-Cy-Xl-mi.js → pica-C5CFA09W.js} +1 -1
  93. package/dist/dashboard/assets/{pieDiagram-DEJITSTG-BnP4-zwd.js → pieDiagram-DEJITSTG-DFHmB0fL.js} +1 -1
  94. package/dist/dashboard/assets/{pl-PL-T2D74RX3-BuJQ3HD-.js → pl-PL-T2D74RX3-yFTBt0Xu.js} +1 -1
  95. package/dist/dashboard/assets/{pt-BR-5N22H2LF-BbAqsvD6.js → pt-BR-5N22H2LF-ilAW3Fdv.js} +1 -1
  96. package/dist/dashboard/assets/{pt-PT-UZXXM6DQ-DzFJedU3.js → pt-PT-UZXXM6DQ-CXHFN95K.js} +1 -1
  97. package/dist/dashboard/assets/{quadrantDiagram-34T5L4WZ-BXN88cHy.js → quadrantDiagram-34T5L4WZ-CR732_oP.js} +1 -1
  98. package/dist/dashboard/assets/{requirementDiagram-MS252O5E-D34hL42a.js → requirementDiagram-MS252O5E-mKoVG52Z.js} +1 -1
  99. package/dist/dashboard/assets/{ro-RO-JPDTUUEW-BV2-sOiv.js → ro-RO-JPDTUUEW-BKlWpyF4.js} +1 -1
  100. package/dist/dashboard/assets/{ru-RU-B4JR7IUQ-BCL4N9Ej.js → ru-RU-B4JR7IUQ-BGoxlhJm.js} +1 -1
  101. package/dist/dashboard/assets/{sankeyDiagram-XADWPNL6-0mFeok-3.js → sankeyDiagram-XADWPNL6-DXipts7M.js} +1 -1
  102. package/dist/dashboard/assets/{sequenceDiagram-FGHM5R23-C-5nW5j6.js → sequenceDiagram-FGHM5R23-DFQhU8q7.js} +1 -1
  103. package/dist/dashboard/assets/{si-LK-N5RQ5JYF-CNhaw4jx.js → si-LK-N5RQ5JYF-yDbJWFTH.js} +1 -1
  104. package/dist/dashboard/assets/{sk-SK-C5VTKIMK-mHCphOXu.js → sk-SK-C5VTKIMK-DglyhbNB.js} +1 -1
  105. package/dist/dashboard/assets/{sl-SI-NN7IZMDC--lzgHthQ.js → sl-SI-NN7IZMDC-DPfq0s4I.js} +1 -1
  106. package/dist/dashboard/assets/{stateDiagram-FHFEXIEX-C50iDFqe.js → stateDiagram-FHFEXIEX-Dm50jiEh.js} +1 -1
  107. package/dist/dashboard/assets/stateDiagram-v2-QKLJ7IA2-CoSm11sD.js +1 -0
  108. package/dist/dashboard/assets/{subset-shared.chunk-DNsjL37s.js → subset-shared.chunk-OHz6M6ct.js} +1 -1
  109. package/dist/dashboard/assets/{subset-worker.chunk-BaV3r-bl.js → subset-worker.chunk-BzwrOBtO.js} +1 -1
  110. package/dist/dashboard/assets/{sv-SE-XGPEYMSR-ckw8jfGN.js → sv-SE-XGPEYMSR-BLHNwr4k.js} +1 -1
  111. package/dist/dashboard/assets/{ta-IN-2NMHFXQM-CSeNmVhS.js → ta-IN-2NMHFXQM-Dw-6hUpN.js} +1 -1
  112. package/dist/dashboard/assets/{th-TH-HPSO5L25-Cvn1sg_3.js → th-TH-HPSO5L25-DPkcM_k9.js} +1 -1
  113. package/dist/dashboard/assets/{timeline-definition-GMOUNBTQ-DRYfuqHT.js → timeline-definition-GMOUNBTQ-BKFNQ_7e.js} +1 -1
  114. package/dist/dashboard/assets/{tr-TR-DEFEU3FU-CyhY22TB.js → tr-TR-DEFEU3FU-rBVsY5RS.js} +1 -1
  115. package/dist/dashboard/assets/{uk-UA-QMV73CPH-BvvAEO6U.js → uk-UA-QMV73CPH-CV6_yTZs.js} +1 -1
  116. package/dist/dashboard/assets/{vennDiagram-DHZGUBPP-Crril7Cj.js → vennDiagram-DHZGUBPP-BZb_r5tY.js} +1 -1
  117. package/dist/dashboard/assets/{vi-VN-M7AON7JQ-c7AwSUZ8.js → vi-VN-M7AON7JQ-CJMgSsLz.js} +1 -1
  118. package/dist/dashboard/assets/{wardley-RL74JXVD-Da5AhSWU.js → wardley-RL74JXVD-DgqjmWyN.js} +1 -1
  119. package/dist/dashboard/assets/{wardleyDiagram-NUSXRM2D-C4GeB_Df.js → wardleyDiagram-NUSXRM2D-BDpniS9m.js} +1 -1
  120. package/dist/dashboard/assets/webviewWindow-Bwv2421L.js +1 -0
  121. package/dist/dashboard/assets/window-BObcHN8e.js +1 -0
  122. package/dist/dashboard/assets/{xychartDiagram-5P7HB3ND-BPCFKXqR.js → xychartDiagram-5P7HB3ND-CCyPcx2A.js} +1 -1
  123. package/dist/dashboard/assets/{zh-CN-LNUGB5OW-BvNiZwKU.js → zh-CN-LNUGB5OW-9LJLF_AC.js} +1 -1
  124. package/dist/dashboard/assets/{zh-HK-E62DVLB3-CCFtoCWb.js → zh-HK-E62DVLB3-IefAiePp.js} +1 -1
  125. package/dist/dashboard/assets/{zh-TW-RAJ6MFWO-bYBr-stu.js → zh-TW-RAJ6MFWO-CHeY9rNZ.js} +1 -1
  126. package/dist/dashboard/index.html +2 -2
  127. package/dist/index.js +4099 -1538
  128. package/dist/skill-packs/agents/goal-implementer.md +42 -1
  129. package/dist/skill-packs/agents/goal-plan-reviewer.md +7 -0
  130. package/dist/skill-packs/agents/goal-planner.md +34 -0
  131. package/dist/skill-packs/catalog.json +31 -21
  132. package/dist/skill-packs/excalidraw/SKILL.md +244 -13
  133. package/dist/skill-packs/excalidraw/examples/Chart Kit.excalidraw.md +15740 -0
  134. package/dist/skill-packs/excalidraw/examples/Wireframe Kit.excalidraw.md +16860 -0
  135. package/dist/skill-packs/excalidraw/examples/atomic_check.js +99 -0
  136. package/dist/skill-packs/excalidraw/examples/chart.spec.json +122 -0
  137. package/dist/skill-packs/excalidraw/examples/chart_board.js +173 -0
  138. package/dist/skill-packs/excalidraw/examples/hello.spec.json +79 -9
  139. package/dist/skill-packs/excalidraw/examples/visual_board.js +82 -0
  140. package/dist/skill-packs/excalidraw/examples/wireframe_board.js +161 -0
  141. package/dist/skill-packs/excalidraw/scripts/build_excalidraw.js +292 -27
  142. package/dist/skill-packs/excalidraw/scripts/lib/charts.js +771 -0
  143. package/dist/skill-packs/excalidraw/scripts/lib/style.js +369 -35
  144. package/dist/skill-packs/excalidraw/scripts/lib/wireframe.js +406 -0
  145. package/dist/skill-packs/goal-skill/SKILL.md +369 -100
  146. package/dist/skill-packs/goal-skill/assets/goal-skill-demo.cjs +51 -0
  147. package/dist/skill-packs/goal-skill/assets/goal-skill-viewer.cjs +181 -0
  148. package/package.json +2 -1
  149. package/skill/references/integrations.md +54 -0
  150. package/skill/references/knowledge-and-recall.md +3 -1
  151. package/skill/references/sleep.md +4 -4
  152. package/skill-packs/agents/goal-implementer.md +42 -1
  153. package/skill-packs/agents/goal-plan-reviewer.md +7 -0
  154. package/skill-packs/agents/goal-planner.md +34 -0
  155. package/skill-packs/catalog.json +31 -21
  156. package/skill-packs/excalidraw/SKILL.md +244 -13
  157. package/skill-packs/excalidraw/examples/Chart Kit.excalidraw.md +15740 -0
  158. package/skill-packs/excalidraw/examples/Wireframe Kit.excalidraw.md +16860 -0
  159. package/skill-packs/excalidraw/examples/atomic_check.js +99 -0
  160. package/skill-packs/excalidraw/examples/chart.spec.json +122 -0
  161. package/skill-packs/excalidraw/examples/chart_board.js +173 -0
  162. package/skill-packs/excalidraw/examples/hello.spec.json +79 -9
  163. package/skill-packs/excalidraw/examples/visual_board.js +82 -0
  164. package/skill-packs/excalidraw/examples/wireframe_board.js +161 -0
  165. package/skill-packs/excalidraw/scripts/build_excalidraw.js +292 -27
  166. package/skill-packs/excalidraw/scripts/lib/charts.js +771 -0
  167. package/skill-packs/excalidraw/scripts/lib/style.js +369 -35
  168. package/skill-packs/excalidraw/scripts/lib/wireframe.js +406 -0
  169. package/skill-packs/goal-skill/SKILL.md +369 -100
  170. package/skill-packs/goal-skill/assets/goal-skill-demo.cjs +51 -0
  171. package/skill-packs/goal-skill/assets/goal-skill-viewer.cjs +181 -0
  172. package/skill-task-manager/SKILL.md +93 -0
  173. package/dist/dashboard/assets/channel-2YfRjala.js +0 -1
  174. package/dist/dashboard/assets/classDiagram-6PBFFD2Q-DB_UnN1h.js +0 -1
  175. package/dist/dashboard/assets/classDiagram-v2-HSJHXN6E-DB_UnN1h.js +0 -1
  176. package/dist/dashboard/assets/clone-DaDvB3N6.js +0 -1
  177. package/dist/dashboard/assets/index-BIfrbaFS.js +0 -516
  178. package/dist/dashboard/assets/index-BdZ3tppu.css +0 -32
  179. package/dist/dashboard/assets/stateDiagram-v2-QKLJ7IA2-eW9-s0Sm.js +0 -1
  180. package/dist/dashboard/assets/webviewWindow-BbG9Cr7f.js +0 -1
  181. package/dist/dashboard/assets/window-DmNpeJAL.js +0 -1
@@ -2,9 +2,26 @@
2
2
  // Module, Research). Excalifont (fontFamily 5), solid fills, always-rounded rects, 2px #1e1e1e ink,
3
3
  // Excalidraw's native pastel swatches used semantically. These helpers return ElementSpec[] for
4
4
  // build_excalidraw.js's buildExcalidraw({ elements }).
5
+ //
6
+ // Three rules keep boards clean and readable — the reason this file exists:
7
+ // 1. READABLE MEASURE — body text wraps to a bounded reading width (READ_W), never the whole
8
+ // board. sectionTitle/bullets/annotate/prose all cap + wrap. Long text is
9
+ // unreadable when it runs edge-to-edge; keep the measure ~60 chars.
10
+ // 2. NO OVERLAP — lay boards out with stack()/row() (flow layout: each element is placed
11
+ // after the previous one's measured height/width, so boxes can't collide).
12
+ // build_excalidraw also AUDITS the finished scene and warns on overlaps.
13
+ // 3. VISUAL-FIRST — Excalidraw's strength is pictures, not walls of text. Prefer the visual
14
+ // kit (funnel, windowFrame, button, input, textRows, imagePlaceholder,
15
+ // navbar, avatar, chip) over paragraphs. Use textRows() as body-copy filler
16
+ // in wireframes instead of real sentences.
5
17
 
6
18
  const INK = '#1e1e1e';
7
19
 
20
+ // Comfortable reading measures (px). READ_W ≈ 60 Excalifont chars at 18px — the width past which a
21
+ // line of text gets hard to scan. NOTE_W is for terse side-annotations. Never let prose exceed READ_W.
22
+ const READ_W = 620;
23
+ const NOTE_W = 260;
24
+
8
25
  // semantic palette: { fill, stroke } — matches the vault's swatches and their meaning
9
26
  const PALETTE = {
10
27
  green: { fill: '#b2f2bb', stroke: '#2f9e44' }, // benefit / positive / go
@@ -20,9 +37,23 @@ const PALETTE = {
20
37
  };
21
38
  function pal(c) { return PALETTE[c] || { fill: c, stroke: INK }; }
22
39
 
40
+ // neutral tones for wireframes / prototypes — grayscale chrome so the accent colors carry meaning
41
+ const WIRE = {
42
+ paper: '#ffffff',
43
+ panel: '#f8f9fa',
44
+ chrome: '#f1f3f5',
45
+ fill: '#f1f3f5',
46
+ line: '#dee2e6',
47
+ edge: '#ced4da',
48
+ hint: '#adb5bd',
49
+ text: '#868e96',
50
+ strong: '#495057',
51
+ };
52
+
23
53
  let _gc = 0;
24
54
  const newGroup = (hint) => `g${++_gc}-${hint || ''}`;
25
55
  const lines = (t) => String(t).split('\n').length;
56
+ const LH = 1.25; // line height used everywhere height is derived from line count
26
57
 
27
58
  // --- text fitting --------------------------------------------------------
28
59
  // Excalifont glyph advance, in multiples of fontSize. Latin is ~0.58; emoji, arrows, CJK and
@@ -50,30 +81,35 @@ function measureText(s, fontSize) { let w = 0; for (const ch of String(s)) w +=
50
81
  // Greedy word-wrap to a pixel width, honoring existing \n and HARD-breaking any single word that is
51
82
  // itself wider than the box (e.g. a long URL). Returns the text WITH the newlines baked in — the
52
83
  // Obsidian plugin keeps them instead of re-flowing the label into one over-wide centered line.
84
+ // A logical line's LEADING INDENT is preserved and re-applied to every line it wraps into, so a
85
+ // hanging indent (bullets(), indented notes) survives. Mirrors wrapText() in build_excalidraw.js.
53
86
  function wrapToWidth(text, fontSize, width) {
54
87
  const out = [];
55
88
  for (const logical of String(text).split('\n')) {
56
- const words = logical.split(/\s+/).filter(Boolean);
89
+ const indent = (logical.match(/^[ \t]*/) || [''])[0];
90
+ const avail = Math.max(1, width - measureText(indent, fontSize));
91
+ const push = (s) => out.push(indent + s);
92
+ const words = logical.slice(indent.length).split(/\s+/).filter(Boolean);
57
93
  if (!words.length) { out.push(''); continue; }
58
94
  let cur = '';
59
95
  for (let word of words) {
60
- while (measureText(word, fontSize) > width) {
96
+ while (measureText(word, fontSize) > avail) {
61
97
  let acc = '';
62
98
  for (const ch of word) {
63
- if (acc && measureText(acc + ch, fontSize) > width) break;
99
+ if (acc && measureText(acc + ch, fontSize) > avail) break;
64
100
  acc += ch;
65
101
  }
66
- if (cur) { out.push(cur); cur = ''; }
67
- out.push(acc);
102
+ if (cur) { push(cur); cur = ''; }
103
+ push(acc);
68
104
  word = word.slice(acc.length);
69
105
  if (!word) break;
70
106
  }
71
107
  if (!word) continue;
72
108
  const candidate = cur ? cur + ' ' + word : word;
73
- if (cur && measureText(candidate, fontSize) > width) { out.push(cur); cur = word; }
109
+ if (cur && measureText(candidate, fontSize) > avail) { push(cur); cur = word; }
74
110
  else cur = candidate;
75
111
  }
76
- if (cur) out.push(cur);
112
+ if (cur) push(cur);
77
113
  }
78
114
  return out.join('\n');
79
115
  }
@@ -84,7 +120,7 @@ function warnFit(msg) { if (!_warned.has(msg)) { _warned.add(msg); try { console
84
120
  // Wrap `text` to the card's inner width, then shrink the font (down to minFont) until the wrapped
85
121
  // label also fits the card height. Returns { text (with baked-in newlines), fontSize, lineCount, fits }.
86
122
  function fitText(o) {
87
- const { text = '', w = 260, h = 92, fontSize = 20, minFont = 9, lineHeight = 1.25, padX = 10, padY = 8 } = o;
123
+ const { text = '', w = 260, h = 92, fontSize = 20, minFont = 9, lineHeight = LH, padX = 10, padY = 8 } = o;
88
124
  const innerW = Math.max(1, w - 2 * padX);
89
125
  const innerH = Math.max(1, h - 2 * padY);
90
126
  let fs = Math.max(minFont, Math.round(fontSize));
@@ -98,9 +134,11 @@ function fitText(o) {
98
134
  return { text: wrapped, fontSize: fs, lineCount, fits: lineCount * fs * lineHeight <= innerH };
99
135
  }
100
136
 
101
- // arc-length midpoint of a polyline — keeps a connector label on the line even when it elbows
137
+ // Arc-length midpoint of a polyline — keeps a connector label on the line even when it elbows.
138
+ // Also reports whether the segment the midpoint lands on runs VERTICALLY, so the label can be placed
139
+ // beside a vertical run instead of being struck through by it.
102
140
  function polyMid(pts) {
103
- if (!pts || pts.length < 2) return (pts && pts[0]) || [0, 0];
141
+ if (!pts || pts.length < 2) return { x: (pts && pts[0] && pts[0][0]) || 0, y: (pts && pts[0] && pts[0][1]) || 0, vertical: false };
104
142
  const seg = [];
105
143
  let total = 0;
106
144
  for (let i = 1; i < pts.length; i++) { const l = Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]); seg.push(l); total += l; }
@@ -108,13 +146,142 @@ function polyMid(pts) {
108
146
  for (let i = 1; i < pts.length; i++) {
109
147
  if (half <= seg[i - 1] || i === pts.length - 1) {
110
148
  const t = seg[i - 1] ? half / seg[i - 1] : 0;
111
- return [pts[i - 1][0] + (pts[i][0] - pts[i - 1][0]) * t, pts[i - 1][1] + (pts[i][1] - pts[i - 1][1]) * t];
149
+ const dx = pts[i][0] - pts[i - 1][0], dy = pts[i][1] - pts[i - 1][1];
150
+ return { x: pts[i - 1][0] + dx * t, y: pts[i - 1][1] + dy * t, vertical: Math.abs(dy) > Math.abs(dx) };
112
151
  }
113
152
  half -= seg[i - 1];
114
153
  }
115
- return pts[0];
154
+ return { x: pts[0][0], y: pts[0][1], vertical: false };
155
+ }
156
+
157
+ // --- measurement + auto-layout ------------------------------------------
158
+ // Every builder below returns an ElementSpec[] that ALSO carries `.x/.y/.w/.h/.nextX/.nextY` so you
159
+ // can flow-lay-out boards without doing coordinate math by hand (the #1 source of overlap). `box()`
160
+ // stamps those; `bbox()` recovers them for a plain array; `stack()`/`row()` place a list with gaps.
161
+ function box(els, x, y, w, h) {
162
+ els.x = x; els.y = y; els.w = w; els.h = h; els.nextX = x + w; els.nextY = y + h;
163
+ return els;
164
+ }
165
+ // Tag every element of a composite (that doesn't already belong to a nested group) with ONE group id,
166
+ // so the widget moves as a unit in Excalidraw and the build-time overlap auditor treats its own chrome
167
+ // as intentional. Returns the same array (carried metadata intact).
168
+ function G(els, hint) {
169
+ const g = newGroup(hint);
170
+ for (const e of els) { if (e.group == null) e.group = g; }
171
+ return els;
172
+ }
173
+ // Pixel bounding box of a spec array (pre-build form). Handles point-based (line/arrow) + boxed els.
174
+ function bbox(els) {
175
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
176
+ for (const e of els) {
177
+ let x0, y0, x1, y1;
178
+ if (e.points && e.points.length) {
179
+ const xs = e.points.map((p) => p[0]); const ys = e.points.map((p) => p[1]);
180
+ x0 = (e.x || 0) + Math.min(...xs); y0 = (e.y || 0) + Math.min(...ys);
181
+ x1 = (e.x || 0) + Math.max(...xs); y1 = (e.y || 0) + Math.max(...ys);
182
+ } else {
183
+ x0 = e.x || 0; y0 = e.y || 0;
184
+ const w = e.width != null ? e.width : (e.text ? measureText(String(e.text).split('\n')[0], e.fontSize || 20) : 0);
185
+ const h = e.height != null ? e.height : (e.text ? lines(e.text) * (e.fontSize || 20) * LH : 0);
186
+ x1 = x0 + w; y1 = y0 + h;
187
+ }
188
+ if (x0 < minX) minX = x0; if (y0 < minY) minY = y0;
189
+ if (x1 > maxX) maxX = x1; if (y1 > maxY) maxY = y1;
190
+ }
191
+ if (!isFinite(minX)) return { x: 0, y: 0, w: 0, h: 0 };
192
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
116
193
  }
194
+ const measH = (els) => (els && els.h != null) ? els.h : bbox(els).h;
195
+ const measW = (els) => (els && els.w != null) ? els.w : bbox(els).w;
117
196
 
197
+ // Move a spec array by (dx,dy). x/y is the offset for BOTH boxed and point-based elements, so this
198
+ // works uniformly. Preserves carried metadata.
199
+ function translate(els, dx, dy) {
200
+ const moved = els.map((e) => Object.assign({}, e, { x: (e.x || 0) + dx, y: (e.y || 0) + dy }));
201
+ if (els.w != null) box(moved, (els.x || 0) + dx, (els.y || 0) + dy, els.w, els.h);
202
+ return moved;
203
+ }
204
+
205
+ // Flow a list of blocks TOP-TO-BOTTOM with a fixed gap — the height of each block is measured, so the
206
+ // next one is placed right below it and nothing ever overlaps. Each item is either a factory
207
+ // `(x, y) => els` (preferred: it draws itself at the running cursor) or an already-built `els` array
208
+ // (it gets shifted down to the cursor). Returns the merged array carrying `.w/.h/.nextY`.
209
+ function stack(o) {
210
+ const { x = 0, y = 0, gap = 24, items = [], align = 'left', width = null } = o;
211
+ const out = []; let cy = y, maxW = 0;
212
+ for (const it of items) {
213
+ if (it == null) continue;
214
+ let els = (typeof it === 'function') ? it(x, cy) : it;
215
+ if (typeof it !== 'function') { const b = bbox(els); els = translate(els, x - b.x, cy - b.y); }
216
+ const w = measW(els), h = measH(els);
217
+ if (align === 'center' && width != null) els = translate(els, (width - w) / 2, 0);
218
+ else if (align === 'right' && width != null) els = translate(els, (width - w), 0);
219
+ out.push(...els);
220
+ maxW = Math.max(maxW, w);
221
+ cy += h + gap;
222
+ }
223
+ return box(out, x, y, width || maxW, Math.max(0, cy - gap - y));
224
+ }
225
+ // Flow a list of blocks LEFT-TO-RIGHT with a fixed gap (same contract as stack, horizontal).
226
+ function row(o) {
227
+ const { x = 0, y = 0, gap = 24, items = [], valign = 'top' } = o;
228
+ const out = []; let cx = x, maxH = 0;
229
+ const built = items.filter(Boolean).map((it) => (typeof it === 'function') ? it : it);
230
+ // first measure heights so valign can center within the tallest block
231
+ const measured = built.map((it) => (typeof it === 'function') ? it(0, 0) : it);
232
+ maxH = measured.reduce((m, els) => Math.max(m, measH(els)), 0);
233
+ built.forEach((it, i) => {
234
+ const probe = measured[i];
235
+ const w = measW(probe), h = measH(probe);
236
+ const dy = valign === 'middle' ? (maxH - h) / 2 : valign === 'bottom' ? (maxH - h) : 0;
237
+ let els = (typeof it === 'function') ? it(cx, y + dy) : translate(it, cx - bbox(it).x, y + dy - bbox(it).y);
238
+ out.push(...els);
239
+ cx += w + gap;
240
+ });
241
+ return box(out, x, y, Math.max(0, cx - gap - x), maxH);
242
+ }
243
+
244
+ // --- readable text primitives -------------------------------------------
245
+ // A bounded, wrapped paragraph. Text NEVER exceeds `width` (default the reading measure) — this is
246
+ // the antidote to edge-to-edge text. Height is derived from the wrapped line count. Visual-first
247
+ // boards use this sparingly; prefer pictures.
248
+ function prose(o) {
249
+ const { x = 0, y = 0, text = '', fontSize = 18, width = READ_W, color = INK, align = 'left' } = o;
250
+ const w = Math.min(width, Math.max(40, measureText(String(text), fontSize)));
251
+ const wrapped = wrapToWidth(text, fontSize, w);
252
+ const lc = wrapped.split('\n').length;
253
+ const out = [{ type: 'text', x, y, text: wrapped, fontSize, color, width: w, align, fontFamily: 5 }];
254
+ return box(out, x, y, w, lc * fontSize * LH);
255
+ }
256
+
257
+ // big plain section header (no box), like "Configuration" / "Flow". Long titles WRAP to `maxWidth`
258
+ // instead of running off the board.
259
+ function sectionTitle(o) {
260
+ const { x = 0, y = 0, text, fontSize = 44, color = INK, maxWidth = 1000, align = 'left' } = o;
261
+ const w = Math.min(maxWidth, Math.max(200, measureText(String(text), fontSize) + fontSize));
262
+ const wrapped = wrapToWidth(text, fontSize, w);
263
+ const lc = wrapped.split('\n').length;
264
+ const out = [{ type: 'text', x, y, text: wrapped, fontSize, color, width: w, align, fontFamily: 5 }];
265
+ return box(out, x, y, w, lc * fontSize * LH);
266
+ }
267
+
268
+ // bullet list as one left-aligned block. Each item wraps to `width` (default reading measure) with a
269
+ // hanging indent under the bullet, so long items stay readable instead of one giant line.
270
+ function bullets(o) {
271
+ const { x = 0, y = 0, items = [], fontSize = 20, color = INK, bullet = '• ', width = READ_W } = o;
272
+ const indent = ' '.repeat(bullet.length);
273
+ const bw = measureText(bullet, fontSize);
274
+ const outLines = [];
275
+ for (const it of items) {
276
+ const wrapped = wrapToWidth(it, fontSize, Math.max(40, width - bw)).split('\n');
277
+ wrapped.forEach((ln, i) => outLines.push((i === 0 ? bullet : indent) + ln));
278
+ }
279
+ const text = outLines.join('\n');
280
+ const out = [{ type: 'text', x, y, text, fontSize, color, width, fontFamily: 5 }];
281
+ return box(out, x, y, width, outLines.length * fontSize * LH);
282
+ }
283
+
284
+ // --- rounded card / flow node -------------------------------------------
118
285
  // rounded card: filled rect + centered label, grouped so they move together. The label is
119
286
  // hard-wrapped to the card width and the font auto-shrinks (to `minFont`) until it also fits the
120
287
  // height, so text can never spill outside the box. Warns if even the floor font won't fit.
@@ -124,28 +291,22 @@ function card(o) {
124
291
  const p = pal(color);
125
292
  const fit = fitText({ text, w, h, fontSize, minFont, padX, padY });
126
293
  if (!fit.fits) warnFit(`label ${JSON.stringify(String(text).slice(0, 32))} won't fit a ${w}×${h} card even at ${minFont}px — enlarge the card or shorten the text.`);
127
- const textH = fit.lineCount * fit.fontSize * 1.25;
294
+ const textH = fit.lineCount * fit.fontSize * LH;
128
295
  const ty = y + Math.max(padY, (h - textH) / 2);
129
- return [
296
+ return box([
130
297
  { type: 'rectangle', x, y, width: w, height: h, strokeColor: stroke, backgroundColor: p.fill, fillStyle: 'solid', strokeWidth, roundness, group },
131
298
  { type: 'text', x, y: ty, text: fit.text, fontSize: fit.fontSize, color: textColor, width: w, align: 'center', fontFamily: 5, group },
132
- ];
299
+ ], x, y, w, h);
133
300
  }
134
301
  // small node (flow step) — card with tighter defaults
135
302
  function node(o) { return card(Object.assign({ w: 200, h: 64, fontSize: 18 }, o)); }
136
303
 
137
- // big plain section header (no box), like "Configuration" / "Flow"
138
- function sectionTitle(o) {
139
- const { x, y, text, fontSize = 44, color = INK } = o;
140
- return [{ type: 'text', x, y, text, fontSize, color, fontFamily: 5, width: Math.max(200, String(text).length * fontSize * 0.6) }];
141
- }
142
-
143
304
  // labeled connector arrow between two points (centers of cards, etc.).
144
305
  // Routing: pass `via:[[x,y],…]` for explicit waypoints, or `elbow:'hv'|'vh'` for an auto right-angle
145
306
  // bend ('hv' = horizontal then vertical, 'vh' = vertical then horizontal). Use these to route a link
146
307
  // AROUND intervening boxes instead of cutting a diagonal straight through them.
147
308
  function connector(o) {
148
- const { from, to, label, double = false, strokeColor = INK, strokeWidth = 2, dashed = false, labelColor = '#495057', fontSize = 16, via = null, elbow = null } = o;
309
+ const { from, to, label, double = false, strokeColor = INK, strokeWidth = 2, dashed = false, labelColor = '#495057', fontSize = 16, via = null, elbow = null, labelSide = 'left' } = o;
149
310
  let pts;
150
311
  if (via && via.length) pts = [from, ...via, to];
151
312
  else if (elbow === 'hv') pts = [from, [to[0], from[1]], to];
@@ -153,19 +314,26 @@ function connector(o) {
153
314
  else pts = [from, to];
154
315
  const out = [{ type: 'arrow', points: pts, strokeColor, strokeWidth, strokeStyle: dashed ? 'dashed' : 'solid', endArrow: 'arrow', startArrow: double ? 'arrow' : null }];
155
316
  if (label) {
156
- const [mx, my] = polyMid(pts);
317
+ const m = polyMid(pts);
157
318
  const w = Math.max(40, measureText(String(label), fontSize) + fontSize);
158
- out.push({ type: 'text', x: mx - w / 2, y: my - fontSize - 4, text: label, fontSize, color: labelColor, width: w, align: 'center', fontFamily: 5 });
319
+ // On a vertical run the line would strike straight through a centered label, so sit the label
320
+ // beside it (`labelSide`, default left); on a horizontal run keep it centered just above.
321
+ const el = m.vertical
322
+ ? { x: labelSide === 'right' ? m.x + 8 : m.x - w - 8, y: m.y - fontSize * 0.62, align: labelSide === 'right' ? 'left' : 'right' }
323
+ : { x: m.x - w / 2, y: m.y - fontSize - 4, align: 'center' };
324
+ out.push({ type: 'text', x: el.x, y: el.y, text: label, fontSize, color: labelColor, width: w, align: el.align, fontFamily: 5 });
159
325
  }
160
326
  return out;
161
327
  }
162
328
 
163
- // side-annotation: short arrow from a node edge to a spec note (the "90sec / deepseek..." pattern)
329
+ // side-annotation: short arrow from a node edge to a spec note (the "90sec / deepseek..." pattern).
330
+ // The note wraps to NOTE_W so it stays a tidy column, never a runaway line.
164
331
  function annotate(o) {
165
- const { from, to, text, fontSize = 16, color = '#495057' } = o;
332
+ const { from, to, text, fontSize = 16, color = '#495057', width = NOTE_W } = o;
333
+ const wrapped = wrapToWidth(text, fontSize, width);
166
334
  return [
167
335
  { type: 'arrow', points: [from, to], strokeColor: color, strokeWidth: 1.5, endArrow: 'arrow' },
168
- { type: 'text', x: to[0] + 6, y: to[1] - fontSize * 0.6, text, fontSize, color, width: Math.max(80, String(text).split('\n')[0].length * fontSize * 0.62), align: 'left', fontFamily: 5 },
336
+ { type: 'text', x: to[0] + 6, y: to[1] - fontSize * 0.6, text: wrapped, fontSize, color, width, align: 'left', fontFamily: 5 },
169
337
  ];
170
338
  }
171
339
 
@@ -178,13 +346,12 @@ function column(o) {
178
346
  const c = (typeof it === 'object' && it.color) || color;
179
347
  out.push(...card({ x, y: y + i * (h + gap), w, h, text: t, color: c, fontSize }));
180
348
  });
181
- return out;
349
+ return box(out, x, y, w, items.length ? items.length * (h + gap) - gap : 0);
182
350
  }
183
351
 
184
352
  // hub-and-spoke: central node + satellites with arrows (the "Personalization Layer" + APIs pattern)
185
353
  function hub(o) {
186
354
  const { cx, cy, label, color = 'purple', rx = 64, ry = 44, radius = 170, spokes = [], spokeColor = 'blue', spokeRx = 30, spokeRy = 30, startAngle = -Math.PI / 2 } = o;
187
- const group = newGroup('hub');
188
355
  const out = [];
189
356
  const p = pal(color);
190
357
  out.push({ type: 'ellipse', x: cx - rx, y: cy - ry, width: rx * 2, height: ry * 2, strokeColor: p.stroke, backgroundColor: p.fill, fillStyle: 'solid', strokeWidth: 2 });
@@ -203,13 +370,169 @@ function hub(o) {
203
370
  return out;
204
371
  }
205
372
 
206
- // bullet list as a single left-aligned block (their "Configuration" spec list pattern)
207
- function bullets(o) {
208
- const { x, y, items = [], fontSize = 20, color = INK, bullet = '• ', width } = o;
209
- const text = items.map((i) => bullet + i).join('\n');
210
- return [{ type: 'text', x, y, text, fontSize, color, fontFamily: 5, width: width || Math.max(200, Math.max(...items.map((i) => i.length)) * fontSize * 0.6) }];
373
+ // --- funnel -------------------------------------------------------------
374
+ // A classic marketing/conversion funnel: filled trapezoid bands narrowing top→bottom, one per stage,
375
+ // each with a centered (auto-fit) label. Optional per-stage `note` (e.g. a metric / drop-off) is
376
+ // placed in a right margin with a short horizontal connector — no diagonal crossings. `stages` is a
377
+ // list of { label, note?, color? }. Returns ElementSpec[] carrying `.w/.h/.nextY`.
378
+ function funnel(o) {
379
+ const {
380
+ x = 0, y = 0, w = 520, stageH = 84, gap = 8, stages = [],
381
+ topW = null, botW = null, fontSize = 20, labelColor = INK,
382
+ noteColor = WIRE.strong, noteGap = 44, notes: showNotes = true,
383
+ } = o;
384
+ const n = Math.max(1, stages.length);
385
+ const tW = topW != null ? topW : w;
386
+ const bW = botW != null ? botW : Math.max(80, Math.round(w * 0.34));
387
+ const cx = x + w / 2;
388
+ const palette = ['blue', 'purple', 'yellow', 'mint', 'green', 'red'];
389
+ const out = [];
390
+ const hasNotes = showNotes && stages.some((s) => s.note);
391
+ let cy = y;
392
+ stages.forEach((s, i) => {
393
+ const wt = tW + (bW - tW) * (i / n);
394
+ const wb = tW + (bW - tW) * ((i + 1) / n);
395
+ const p = pal(s.color || palette[i % palette.length]);
396
+ // closed, filled trapezoid (sharp corners read as a funnel; roundness blobs it)
397
+ out.push({
398
+ type: 'line',
399
+ points: [[cx - wt / 2, cy], [cx + wt / 2, cy], [cx + wb / 2, cy + stageH], [cx - wb / 2, cy + stageH], [cx - wt / 2, cy]],
400
+ strokeColor: p.stroke, backgroundColor: p.fill, fillStyle: 'solid', strokeWidth: 2,
401
+ });
402
+ // label fit to the narrower (bottom) width so it never spills past the band edges
403
+ const lw = Math.max(60, Math.min(wt, wb) - 24);
404
+ const fit = fitText({ text: s.label, w: lw + 24, h: stageH, fontSize, minFont: 12 });
405
+ const th = fit.lineCount * fit.fontSize * LH;
406
+ out.push({ type: 'text', x: cx - lw / 2, y: cy + (stageH - th) / 2, text: fit.text, fontSize: fit.fontSize, color: labelColor, width: lw, align: 'center', fontFamily: 5 });
407
+ if (hasNotes && s.note) {
408
+ const edgeX = cx + ((wt + wb) / 2) / 2; // right edge at the band's vertical middle
409
+ const midY = cy + stageH / 2;
410
+ const nx = x + w + noteGap;
411
+ out.push({ type: 'arrow', points: [[edgeX, midY], [nx - 6, midY]], strokeColor: noteColor, strokeWidth: 1.5, endArrow: 'arrow' });
412
+ const wrapped = wrapToWidth(s.note, 15, NOTE_W);
413
+ out.push({ type: 'text', x: nx, y: midY - 15 * 0.6 * lines(wrapped), text: wrapped, fontSize: 15, color: noteColor, width: NOTE_W, align: 'left', fontFamily: 5 });
414
+ }
415
+ cy += stageH + gap;
416
+ });
417
+ const totalW = w + (hasNotes ? noteGap + NOTE_W : 0);
418
+ return box(out, x, y, totalW, Math.max(0, cy - gap - y));
419
+ }
420
+
421
+ // --- wireframe / prototype kit ------------------------------------------
422
+ // Neutral, grayscale components so you can sketch a UI PROTOTYPE (browser page, app screen, form)
423
+ // that reads as a mock, not as decoration. Real content stays sparse; textRows() stands in for body
424
+ // copy so a wireframe never becomes a wall of text.
425
+
426
+ // A window / browser chrome. `kind:'browser'` draws a URL bar; `kind:'app'` a centered title.
427
+ // `.inner = {x,y,w,h}` is the safe content region — place children there and they won't hit the chrome.
428
+ function windowFrame(o) {
429
+ const { x = 0, y = 0, w = 560, h = 380, title = '', url = null, kind = 'browser', bar = 38, fill = WIRE.paper, chrome = WIRE.chrome, stroke = WIRE.edge, pad = 16 } = o;
430
+ const out = [];
431
+ out.push({ type: 'rectangle', x, y, width: w, height: h, strokeColor: stroke, backgroundColor: fill, fillStyle: 'solid', strokeWidth: 2, roundness: true });
432
+ out.push({ type: 'rectangle', x, y, width: w, height: bar, strokeColor: stroke, backgroundColor: chrome, fillStyle: 'solid', strokeWidth: 2, roundness: true });
433
+ ['#ff5f57', '#febc2e', '#28c840'].forEach((c, i) => out.push({ type: 'ellipse', x: x + 14 + i * 20, y: y + bar / 2 - 5, width: 10, height: 10, strokeColor: c, backgroundColor: c, fillStyle: 'solid', strokeWidth: 1 }));
434
+ if (kind === 'browser') {
435
+ const ux = x + 80, uw = w - 96;
436
+ out.push({ type: 'rectangle', x: ux, y: y + 7, width: uw, height: bar - 14, strokeColor: WIRE.line, backgroundColor: WIRE.paper, fillStyle: 'solid', strokeWidth: 1, roundness: true });
437
+ if (url) out.push({ type: 'text', x: ux + 12, y: y + bar / 2 - 7, text: url, fontSize: 12, color: WIRE.text, width: uw - 24, fontFamily: 3 });
438
+ } else if (title) {
439
+ out.push({ type: 'text', x: x + 80, y: y + bar / 2 - 8, text: title, fontSize: 13, color: WIRE.strong, width: w - 160, align: 'center', fontFamily: 5 });
440
+ }
441
+ G(out, 'window');
442
+ box(out, x, y, w, h);
443
+ out.inner = { x: x + pad, y: y + bar + pad, w: w - 2 * pad, h: h - bar - 2 * pad };
444
+ return out;
445
+ }
446
+
447
+ // A solid CTA button (default) or an outline button (`variant:'outline'`).
448
+ function button(o) {
449
+ const { x = 0, y = 0, w = 150, h = 42, text = 'Button', color = 'blue', fontSize = 15, variant = 'solid', textColor = '#ffffff' } = o;
450
+ const p = pal(color);
451
+ const solid = variant !== 'outline';
452
+ const fit = fitText({ text, w, h, fontSize, minFont: 11 });
453
+ const th = fit.lineCount * fit.fontSize * LH;
454
+ const g = newGroup('button');
455
+ return box([
456
+ { type: 'rectangle', x, y, width: w, height: h, strokeColor: p.stroke, backgroundColor: solid ? p.stroke : WIRE.paper, fillStyle: 'solid', strokeWidth: 2, roundness: true, group: g },
457
+ { type: 'text', x, y: y + (h - th) / 2, text: fit.text, fontSize: fit.fontSize, color: solid ? textColor : p.stroke, width: w, align: 'center', fontFamily: 5, group: g },
458
+ ], x, y, w, h);
459
+ }
460
+
461
+ // A text input field with a muted placeholder, and an optional label above it.
462
+ function input(o) {
463
+ const { x = 0, y = 0, w = 260, h = 42, placeholder = '', label = null, fontSize = 14 } = o;
464
+ const out = []; let yy = y;
465
+ if (label) { out.push({ type: 'text', x, y: yy, text: label, fontSize: 12, color: WIRE.text, width: w, fontFamily: 5 }); yy += 12 * LH + 6; }
466
+ out.push({ type: 'rectangle', x, y: yy, width: w, height: h, strokeColor: WIRE.edge, backgroundColor: WIRE.paper, fillStyle: 'solid', strokeWidth: 1.5, roundness: true });
467
+ if (placeholder) out.push({ type: 'text', x: x + 12, y: yy + h / 2 - fontSize * 0.6, text: placeholder, fontSize, color: WIRE.hint, width: w - 24, fontFamily: 5 });
468
+ return box(G(out, 'input'), x, y, w, (yy - y) + h);
469
+ }
470
+
471
+ // Grey bars that stand in for body copy in a wireframe — VISUAL filler, no real text. The last row is
472
+ // short (`last` fraction) like a real paragraph's final line.
473
+ function textRows(o) {
474
+ const { x = 0, y = 0, w = 320, rows = 3, lineH = 12, gap = 10, color = WIRE.line, last = 0.6 } = o;
475
+ const out = [];
476
+ for (let i = 0; i < rows; i++) {
477
+ const rw = (i === rows - 1 && rows > 1) ? Math.round(w * last) : w;
478
+ out.push({ type: 'rectangle', x, y: y + i * (lineH + gap), width: rw, height: lineH, strokeColor: 'transparent', backgroundColor: color, fillStyle: 'solid', strokeWidth: 0, roundness: true });
479
+ }
480
+ return box(out, x, y, w, rows * lineH + (rows - 1) * gap);
481
+ }
482
+
483
+ // The universal image-placeholder glyph: a box with an X. Use anywhere a real screenshot would go.
484
+ function imagePlaceholder(o) {
485
+ const { x = 0, y = 0, w = 220, h = 150, fill = WIRE.fill, stroke = WIRE.hint, label = null } = o;
486
+ const out = [
487
+ { type: 'rectangle', x, y, width: w, height: h, strokeColor: stroke, backgroundColor: fill, fillStyle: 'solid', strokeWidth: 1.5, roundness: false },
488
+ { type: 'line', points: [[x, y], [x + w, y + h]], strokeColor: stroke, strokeWidth: 1.5 },
489
+ { type: 'line', points: [[x + w, y], [x, y + h]], strokeColor: stroke, strokeWidth: 1.5 },
490
+ ];
491
+ if (label) out.push({ type: 'text', x, y: y + h / 2 - 8, text: label, fontSize: 13, color: WIRE.text, width: w, align: 'center', fontFamily: 5 });
492
+ return box(G(out, 'imgph'), x, y, w, h);
493
+ }
494
+
495
+ // A round avatar/user placeholder.
496
+ function avatar(o) {
497
+ const { cx = 0, cy = 0, r = 24, fill = WIRE.line, stroke = WIRE.hint } = o;
498
+ return box([{ type: 'ellipse', x: cx - r, y: cy - r, width: 2 * r, height: 2 * r, strokeColor: stroke, backgroundColor: fill, fillStyle: 'solid', strokeWidth: 1.5 }], cx - r, cy - r, 2 * r, 2 * r);
499
+ }
500
+
501
+ // A top navigation bar: brand on the left, links on the right, optional CTA button.
502
+ function navbar(o) {
503
+ const { x = 0, y = 0, w = 800, h = 54, brand = '', items = [], cta = null, fill = WIRE.paper, stroke = WIRE.line, color = WIRE.strong } = o;
504
+ const out = [{ type: 'rectangle', x, y, width: w, height: h, strokeColor: stroke, backgroundColor: fill, fillStyle: 'solid', strokeWidth: 1.5, roundness: false }];
505
+ if (brand) out.push({ type: 'text', x: x + 22, y: y + h / 2 - 11, text: brand, fontSize: 18, color: INK, fontFamily: 5 });
506
+ let rx = x + w - 22 - (cta ? 132 : 0);
507
+ for (let i = items.length - 1; i >= 0; i--) {
508
+ const t = items[i]; const tw = measureText(t, 14) + 20; rx -= tw;
509
+ out.push({ type: 'text', x: rx, y: y + h / 2 - 9, text: t, fontSize: 14, color, width: tw, align: 'center', fontFamily: 5 });
510
+ }
511
+ if (cta) out.push(...button({ x: x + w - 22 - 120, y: y + (h - 34) / 2, w: 120, h: 34, text: cta, fontSize: 13 }));
512
+ return box(G(out, 'navbar'), x, y, w, h);
513
+ }
514
+
515
+ // A small pill/tag/badge sized to its text.
516
+ function chip(o) {
517
+ const { x = 0, y = 0, text = '', color = 'gray', fontSize = 13 } = o;
518
+ const p = pal(color);
519
+ const w = Math.round(measureText(text, fontSize) + 22), h = Math.round(fontSize + 14);
520
+ const g = newGroup('chip');
521
+ return box([
522
+ { type: 'rectangle', x, y, width: w, height: h, strokeColor: p.stroke, backgroundColor: p.fill, fillStyle: 'solid', strokeWidth: 1.5, roundness: true, group: g },
523
+ { type: 'text', x, y: y + (h - fontSize * LH) / 2, text, fontSize, color: p.stroke, width: w, align: 'center', fontFamily: 5, group: g },
524
+ ], x, y, w, h);
211
525
  }
212
526
 
527
+ // A thin horizontal rule.
528
+ function divider(o) {
529
+ const { x = 0, y = 0, w = 600, color = WIRE.line, strokeWidth = 1.5 } = o;
530
+ return box([{ type: 'line', points: [[x, y], [x + w, y]], strokeColor: color, strokeWidth }], x, y, w, 0);
531
+ }
532
+
533
+ // bullet list as a single left-aligned block (their "Configuration" spec list pattern) — kept for
534
+ // back-compat; prefer bullets() above which wraps to a reading measure.
535
+
213
536
  // center helpers for wiring connectors to card edges
214
537
  const center = (x, y, w, h) => [x + w / 2, y + h / 2];
215
538
  const rightOf = (x, y, w, h) => [x + w, y + h / 2];
@@ -217,4 +540,15 @@ const leftOf = (x, y, w, h) => [x, y + h / 2];
217
540
  const bottomOf = (x, y, w, h) => [x + w / 2, y + h];
218
541
  const topOf = (x, y, w, h) => [x + w / 2, y];
219
542
 
220
- module.exports = { INK, PALETTE, pal, card, node, sectionTitle, connector, annotate, column, hub, bullets, fitText, measureText, center, rightOf, leftOf, bottomOf, topOf };
543
+ module.exports = {
544
+ INK, PALETTE, WIRE, READ_W, NOTE_W, pal,
545
+ // text + layout
546
+ measureText, fitText, wrapToWidth, prose, sectionTitle, bullets, annotate,
547
+ box, bbox, translate, stack, row,
548
+ // structure
549
+ card, node, connector, column, hub, funnel,
550
+ // wireframe / prototype kit
551
+ windowFrame, button, input, textRows, imagePlaceholder, avatar, navbar, chip, divider,
552
+ // edge helpers
553
+ center, rightOf, leftOf, bottomOf, topOf,
554
+ };