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
@@ -0,0 +1,406 @@
1
+ // Device + UI kit for Excalidraw wireframes. Same contract as style.js/charts.js: every builder
2
+ // returns an ElementSpec[] carrying `.x/.y/.w/.h/.nextX/.nextY`, so it composes inside stack()/row().
3
+ //
4
+ // Why a separate lib: style.js owns the *diagram* house style (cards, connectors, funnels) and a
5
+ // handful of generic wireframe bits. This file owns the *product UI* vocabulary — real device shells,
6
+ // nav chrome, form controls, icons. It re-exports style.js's wireframe primitives too, so one require
7
+ // gives you the whole kit.
8
+ //
9
+ // Rounded corners: build_excalidraw maps `roundness:true` to Excalidraw's ADAPTIVE_RADIUS (type 3),
10
+ // which caps at ~32px — far too tight for a phone silhouette (an iPhone's is ~14% of its width). So
11
+ // device shells are drawn as FILLED POLYGONS with exact arc corners, the same trick funnel()/donut()
12
+ // use for shapes Excalidraw has no primitive for. Everything inside a device stays a normal element.
13
+
14
+ const {
15
+ INK, WIRE, pal, measureText, wrapToWidth, box, bbox, translate,
16
+ windowFrame, navbar, button, input, textRows, imagePlaceholder, avatar, chip, divider,
17
+ } = require('./style.js');
18
+
19
+ const LH = 1.25;
20
+ const FONT = 5;
21
+ const txt = (o) => Object.assign({ type: 'text', fontFamily: FONT, color: INK }, o);
22
+ let _gc = 0;
23
+ function G(els, hint) {
24
+ const g = `wf${++_gc}-${hint}`;
25
+ for (const e of els) if (e.group == null) e.group = g;
26
+ return els;
27
+ }
28
+
29
+ // ── geometry ──────────────────────────────────────────────────────────────
30
+ // A closed rounded-rectangle polygon with an EXACT corner radius (Excalidraw's rect roundness can't
31
+ // go past ~32px). `seg` points per corner arc — 6 is smooth enough at board scale.
32
+ function roundRectPoints(x, y, w, h, r, seg = 6) {
33
+ const rr = Math.max(0, Math.min(r, Math.min(w, h) / 2));
34
+ const pts = [];
35
+ const arc = (cx, cy, a0, a1) => {
36
+ for (let i = 0; i <= seg; i++) {
37
+ const a = a0 + (a1 - a0) * (i / seg);
38
+ pts.push([cx + Math.cos(a) * rr, cy + Math.sin(a) * rr]);
39
+ }
40
+ };
41
+ const P = Math.PI;
42
+ arc(x + w - rr, y + rr, -P / 2, 0); // top-right
43
+ arc(x + w - rr, y + h - rr, 0, P / 2); // bottom-right
44
+ arc(x + rr, y + h - rr, P / 2, P); // bottom-left
45
+ arc(x + rr, y + rr, P, (3 * P) / 2); // top-left
46
+ pts.push(pts[0]);
47
+ return pts;
48
+ }
49
+ function roundRect(o) {
50
+ const { x, y, w, h, r = 12, fill = WIRE.paper, stroke = WIRE.edge, strokeWidth = 2, seg = 6 } = o;
51
+ return {
52
+ type: 'line', points: roundRectPoints(x, y, w, h, r, seg),
53
+ strokeColor: stroke, backgroundColor: fill, fillStyle: 'solid', strokeWidth,
54
+ };
55
+ }
56
+
57
+ // ── icons ─────────────────────────────────────────────────────────────────
58
+ // Line-drawn glyphs on a size×size box centred at (cx,cy). Deliberately simple: a wireframe icon
59
+ // only has to READ as its concept, not be pixel-perfect. Every entry returns ElementSpec[].
60
+ const ICONS = {
61
+ menu: (c, s, k, w) => [0, 0.5, 1].map((t) => ln([[c[0] - s / 2, c[1] - s / 2 + t * s], [c[0] + s / 2, c[1] - s / 2 + t * s]], k, w)),
62
+ close: (c, s, k, w) => [ln([[c[0] - s / 2, c[1] - s / 2], [c[0] + s / 2, c[1] + s / 2]], k, w), ln([[c[0] + s / 2, c[1] - s / 2], [c[0] - s / 2, c[1] + s / 2]], k, w)],
63
+ plus: (c, s, k, w) => [ln([[c[0] - s / 2, c[1]], [c[0] + s / 2, c[1]]], k, w), ln([[c[0], c[1] - s / 2], [c[0], c[1] + s / 2]], k, w)],
64
+ minus: (c, s, k, w) => [ln([[c[0] - s / 2, c[1]], [c[0] + s / 2, c[1]]], k, w)],
65
+ check: (c, s, k, w) => [ln([[c[0] - s / 2, c[1]], [c[0] - s / 8, c[1] + s / 3], [c[0] + s / 2, c[1] - s / 3]], k, w)],
66
+ search: (c, s, k, w) => [el(c[0] - s / 2, c[1] - s / 2, s * 0.68, s * 0.68, k, w), ln([[c[0] + s * 0.14, c[1] + s * 0.14], [c[0] + s / 2, c[1] + s / 2]], k, w)],
67
+ 'chevron-left': (c, s, k, w) => [ln([[c[0] + s / 4, c[1] - s / 2], [c[0] - s / 4, c[1]], [c[0] + s / 4, c[1] + s / 2]], k, w)],
68
+ 'chevron-right': (c, s, k, w) => [ln([[c[0] - s / 4, c[1] - s / 2], [c[0] + s / 4, c[1]], [c[0] - s / 4, c[1] + s / 2]], k, w)],
69
+ 'chevron-up': (c, s, k, w) => [ln([[c[0] - s / 2, c[1] + s / 4], [c[0], c[1] - s / 4], [c[0] + s / 2, c[1] + s / 4]], k, w)],
70
+ 'chevron-down': (c, s, k, w) => [ln([[c[0] - s / 2, c[1] - s / 4], [c[0], c[1] + s / 4], [c[0] + s / 2, c[1] - s / 4]], k, w)],
71
+ 'arrow-left': (c, s, k, w) => [ln([[c[0] + s / 2, c[1]], [c[0] - s / 2, c[1]]], k, w), ln([[c[0] - s / 6, c[1] - s / 3], [c[0] - s / 2, c[1]], [c[0] - s / 6, c[1] + s / 3]], k, w)],
72
+ 'arrow-right': (c, s, k, w) => [ln([[c[0] - s / 2, c[1]], [c[0] + s / 2, c[1]]], k, w), ln([[c[0] + s / 6, c[1] - s / 3], [c[0] + s / 2, c[1]], [c[0] + s / 6, c[1] + s / 3]], k, w)],
73
+ more: (c, s, k) => [-1, 0, 1].map((t) => dot(c[0] + t * s * 0.34, c[1], s * 0.09, k)),
74
+ 'more-v': (c, s, k) => [-1, 0, 1].map((t) => dot(c[0], c[1] + t * s * 0.34, s * 0.09, k)),
75
+ play: (c, s, k, w) => [poly([[c[0] - s / 3, c[1] - s / 2], [c[0] + s / 2, c[1]], [c[0] - s / 3, c[1] + s / 2]], k, w)],
76
+ heart: (c, s, k, w) => [poly(heartPts(c[0], c[1], s), k, w)],
77
+ star: (c, s, k, w) => [poly(starPts(c[0], c[1], s / 2, s / 4.6, 5), k, w)],
78
+ user: (c, s, k, w) => [el(c[0] - s * 0.22, c[1] - s / 2, s * 0.44, s * 0.44, k, w), ln([[c[0] - s * 0.38, c[1] + s / 2], [c[0] - s * 0.38, c[1] + s * 0.16], [c[0] + s * 0.38, c[1] + s * 0.16], [c[0] + s * 0.38, c[1] + s / 2]], k, w)],
79
+ home: (c, s, k, w) => [ln([[c[0] - s / 2, c[1]], [c[0], c[1] - s / 2], [c[0] + s / 2, c[1]]], k, w), ln([[c[0] - s / 3, c[1]], [c[0] - s / 3, c[1] + s / 2], [c[0] + s / 3, c[1] + s / 2], [c[0] + s / 3, c[1]]], k, w)],
80
+ bell: (c, s, k, w) => [ln([[c[0] - s * 0.36, c[1] + s * 0.2], [c[0] - s * 0.3, c[1] - s * 0.12], [c[0], c[1] - s * 0.44], [c[0] + s * 0.3, c[1] - s * 0.12], [c[0] + s * 0.36, c[1] + s * 0.2], [c[0] - s * 0.36, c[1] + s * 0.2]], k, w), ln([[c[0] - s * 0.1, c[1] + s * 0.34], [c[0] + s * 0.1, c[1] + s * 0.34]], k, w)],
81
+ trash: (c, s, k, w) => [ln([[c[0] - s * 0.4, c[1] - s * 0.26], [c[0] + s * 0.4, c[1] - s * 0.26]], k, w), ln([[c[0] - s * 0.28, c[1] - s * 0.26], [c[0] - s * 0.22, c[1] + s * 0.44], [c[0] + s * 0.22, c[1] + s * 0.44], [c[0] + s * 0.28, c[1] - s * 0.26]], k, w), ln([[c[0] - s * 0.12, c[1] - s * 0.26], [c[0] - s * 0.12, c[1] - s * 0.44], [c[0] + s * 0.12, c[1] - s * 0.44], [c[0] + s * 0.12, c[1] - s * 0.26]], k, w)],
82
+ settings: (c, s, k, w) => {
83
+ // flat-topped teeth — an alternating-radius star reads as a SUN, not a gear
84
+ const teeth = 7, R = s * 0.5, r = s * 0.36, pts = [];
85
+ for (let i = 0; i < teeth; i++) {
86
+ const a0 = (i / teeth) * Math.PI * 2, st = (Math.PI * 2) / teeth;
87
+ [[a0, R], [a0 + st * 0.42, R], [a0 + st * 0.58, r], [a0 + st, r]].forEach(([a, rr]) =>
88
+ pts.push([c[0] + Math.cos(a) * rr, c[1] + Math.sin(a) * rr]));
89
+ }
90
+ return [ln([...pts, pts[0]], k, w), el(c[0] - s * 0.16, c[1] - s * 0.16, s * 0.32, s * 0.32, k, w)];
91
+ },
92
+ share: (c, s, k, w) => [dot(c[0] + s * 0.32, c[1] - s * 0.36, s * 0.11, k), dot(c[0] - s * 0.34, c[1], s * 0.11, k), dot(c[0] + s * 0.32, c[1] + s * 0.36, s * 0.11, k), ln([[c[0] - s * 0.26, c[1] - s * 0.06], [c[0] + s * 0.24, c[1] - s * 0.3]], k, w), ln([[c[0] - s * 0.26, c[1] + s * 0.06], [c[0] + s * 0.24, c[1] + s * 0.3]], k, w)],
93
+ lock: (c, s, k, w) => [rc(c[0] - s * 0.32, c[1] - s * 0.04, s * 0.64, s * 0.5, k, w), ln([[c[0] - s * 0.18, c[1] - s * 0.04], [c[0] - s * 0.18, c[1] - s * 0.3], [c[0] + s * 0.18, c[1] - s * 0.3], [c[0] + s * 0.18, c[1] - s * 0.04]], k, w)],
94
+ mail: (c, s, k, w) => [rc(c[0] - s * 0.44, c[1] - s * 0.32, s * 0.88, s * 0.64, k, w), ln([[c[0] - s * 0.44, c[1] - s * 0.32], [c[0], c[1] + s * 0.06], [c[0] + s * 0.44, c[1] - s * 0.32]], k, w)],
95
+ camera: (c, s, k, w) => [rc(c[0] - s * 0.46, c[1] - s * 0.26, s * 0.92, s * 0.6, k, w), el(c[0] - s * 0.16, c[1] - s * 0.1, s * 0.32, s * 0.32, k, w), ln([[c[0] - s * 0.18, c[1] - s * 0.26], [c[0] - s * 0.1, c[1] - s * 0.4], [c[0] + s * 0.1, c[1] - s * 0.4], [c[0] + s * 0.18, c[1] - s * 0.26]], k, w)],
96
+ image: (c, s, k, w) => [rc(c[0] - s * 0.44, c[1] - s * 0.36, s * 0.88, s * 0.72, k, w), ln([[c[0] - s * 0.44, c[1] + s * 0.14], [c[0] - s * 0.12, c[1] - s * 0.14], [c[0] + s * 0.16, c[1] + s * 0.36]], k, w), dot(c[0] + s * 0.2, c[1] - s * 0.16, s * 0.07, k)],
97
+ edit: (c, s, k, w) => [ln([[c[0] - s * 0.44, c[1] + s * 0.44], [c[0] - s * 0.3, c[1] + s * 0.16], [c[0] + s * 0.3, c[1] - s * 0.44], [c[0] + s * 0.44, c[1] - s * 0.3], [c[0] - s * 0.16, c[1] + s * 0.3], [c[0] - s * 0.44, c[1] + s * 0.44]], k, w)],
98
+ filter: (c, s, k, w) => [ln([[c[0] - s * 0.46, c[1] - s * 0.34], [c[0] + s * 0.46, c[1] - s * 0.34], [c[0] + s * 0.1, c[1] + s * 0.06], [c[0] + s * 0.1, c[1] + s * 0.42], [c[0] - s * 0.1, c[1] + s * 0.28], [c[0] - s * 0.1, c[1] + s * 0.06], [c[0] - s * 0.46, c[1] - s * 0.34]], k, w)],
99
+ bookmark: (c, s, k, w) => [ln([[c[0] - s * 0.3, c[1] - s * 0.44], [c[0] + s * 0.3, c[1] - s * 0.44], [c[0] + s * 0.3, c[1] + s * 0.44], [c[0], c[1] + s * 0.14], [c[0] - s * 0.3, c[1] + s * 0.44], [c[0] - s * 0.3, c[1] - s * 0.44]], k, w)],
100
+ calendar: (c, s, k, w) => [rc(c[0] - s * 0.42, c[1] - s * 0.34, s * 0.84, s * 0.76, k, w), ln([[c[0] - s * 0.42, c[1] - s * 0.1], [c[0] + s * 0.42, c[1] - s * 0.1]], k, w), ln([[c[0] - s * 0.2, c[1] - s * 0.34], [c[0] - s * 0.2, c[1] - s * 0.48]], k, w), ln([[c[0] + s * 0.2, c[1] - s * 0.34], [c[0] + s * 0.2, c[1] - s * 0.48]], k, w)],
101
+ };
102
+ const ln = (points, k, w) => ({ type: 'line', points, strokeColor: k, strokeWidth: w, backgroundColor: 'transparent' });
103
+ const poly = (points, k, w) => ({ type: 'line', points: [...points, points[0]], strokeColor: k, backgroundColor: k, fillStyle: 'solid', strokeWidth: w });
104
+ const el = (x, y, w2, h2, k, w) => ({ type: 'ellipse', x, y, width: w2, height: h2, strokeColor: k, backgroundColor: 'transparent', strokeWidth: w });
105
+ const rc = (x, y, w2, h2, k, w) => ({ type: 'rectangle', x, y, width: w2, height: h2, strokeColor: k, backgroundColor: 'transparent', strokeWidth: w, roundness: true });
106
+ const dot = (cx, cy, r, k) => ({ type: 'ellipse', x: cx - r, y: cy - r, width: r * 2, height: r * 2, strokeColor: k, backgroundColor: k, fillStyle: 'solid', strokeWidth: 1 });
107
+ function starPts(cx, cy, R, r, n) {
108
+ const p = [];
109
+ for (let i = 0; i < n * 2; i++) {
110
+ const a = (i / (n * 2)) * Math.PI * 2 - Math.PI / 2;
111
+ const rr = i % 2 ? r : R;
112
+ p.push([cx + Math.cos(a) * rr, cy + Math.sin(a) * rr]);
113
+ }
114
+ return p;
115
+ }
116
+ function heartPts(cx, cy, s) {
117
+ const p = [];
118
+ for (let i = 0; i <= 24; i++) {
119
+ const t = (i / 24) * Math.PI * 2;
120
+ const x = 16 * Math.pow(Math.sin(t), 3);
121
+ const y = -(13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t));
122
+ p.push([cx + (x / 17) * (s / 2), cy + (y / 17) * (s / 2)]);
123
+ }
124
+ return p;
125
+ }
126
+
127
+ /** A line-drawn glyph. `name` must be a key of ICONS; unknown names fall back to a box (so a typo is
128
+ * visible on the board instead of silently drawing nothing). */
129
+ function icon(o) {
130
+ const { name = 'menu', x = 0, y = 0, size = 20, color = WIRE.strong, strokeWidth = 1.6 } = o;
131
+ const c = [x + size / 2, y + size / 2];
132
+ const f = ICONS[name];
133
+ const els = f ? f(c, size, color, strokeWidth).flat() : [rc(x, y, size, size, color, strokeWidth)];
134
+ return box(els, x, y, size, size);
135
+ }
136
+ const iconNames = () => Object.keys(ICONS);
137
+
138
+ /** A tappable icon: glyph inside a circle/square/bare hit area. The workhorse of mobile chrome. */
139
+ function iconButton(o) {
140
+ const {
141
+ x = 0, y = 0, size = 40, icon: name = 'menu', shape = 'circle', color = null,
142
+ variant = 'ghost', iconSize = null, strokeWidth = 1.6,
143
+ } = o;
144
+ const p = color ? pal(color) : null;
145
+ const solid = variant === 'solid';
146
+ const fill = solid ? (p ? p.fill : WIRE.chrome) : (variant === 'ghost' ? 'transparent' : WIRE.paper);
147
+ const stroke = variant === 'ghost' ? 'transparent' : (p ? p.stroke : WIRE.edge);
148
+ const glyph = solid && p ? p.stroke : (p ? p.stroke : WIRE.strong);
149
+ const out = [];
150
+ if (variant !== 'ghost') {
151
+ if (shape === 'circle') out.push({ type: 'ellipse', x, y, width: size, height: size, strokeColor: stroke, backgroundColor: fill, fillStyle: 'solid', strokeWidth: 1.5 });
152
+ else out.push(roundRect({ x, y, w: size, h: size, r: shape === 'square' ? 8 : size / 2, fill, stroke, strokeWidth: 1.5 }));
153
+ }
154
+ const is = iconSize || size * 0.5;
155
+ out.push(...icon({ name, x: x + (size - is) / 2, y: y + (size - is) / 2, size: is, color: glyph, strokeWidth }));
156
+ return G(box(out, x, y, size, size), 'iconbtn');
157
+ }
158
+
159
+ // ── device shells ─────────────────────────────────────────────────────────
160
+ // Real proportions, so a phone mock reads as a phone and a layout drawn inside it is honest about
161
+ // how much room it actually has. aspect = h/w of the SCREEN (logical points).
162
+ const DEVICES = {
163
+ iphone: { aspect: 852 / 393, w: 380, bezel: 11, radius: 0.13, island: true, home: true, status: true, label: 'iPhone' },
164
+ ipad: { aspect: 1194 / 834, w: 560, bezel: 16, radius: 0.05, camera: true, home: true, status: true, label: 'iPad' },
165
+ mac: { aspect: 900 / 1440, w: 920, bezel: 9, radius: 0.014, menubar: true, notch: true, base: true, label: 'Mac' },
166
+ };
167
+
168
+ /** A device shell with real proportions. Returns `.inner = {x,y,w,h}` — the SAFE content region
169
+ * (inside the bezel, below the status bar / notch, above the home indicator). Place children there
170
+ * (via stack/row) and they cannot hit the chrome. `screen` is the full screen rect if you need it. */
171
+ function device(o) {
172
+ const {
173
+ kind = 'iphone', x = 0, y = 0, w = null, h = null, label = null, labelSize = 14,
174
+ time = '9:41', status = null, wallpaper = WIRE.paper, bezelColor = '#343a40', dark = false,
175
+ } = o;
176
+ const d = DEVICES[kind] || DEVICES.iphone;
177
+ const bodyW = w || d.w;
178
+ const bezel = Math.max(4, Math.round(d.bezel * (bodyW / d.w)));
179
+ const screenW = bodyW - bezel * 2;
180
+ const screenH = h ? h - bezel * 2 : Math.round(screenW * d.aspect);
181
+ const bodyH = screenH + bezel * 2;
182
+ const r = Math.round(bodyW * d.radius) + bezel;
183
+ const out = [];
184
+ // caption above the device
185
+ let top = y;
186
+ if (label || d.label) {
187
+ const t = label || d.label;
188
+ out.push(txt({ x, y: top, text: t, fontSize: labelSize, color: WIRE.text, width: bodyW, align: 'center' }));
189
+ top += labelSize * LH + 8;
190
+ }
191
+ // body (filled polygon: exact corner radius, which a rect's adaptive roundness can't reach)
192
+ out.push(roundRect({ x, y: top, w: bodyW, h: bodyH, r, fill: bezelColor, stroke: '#212529', strokeWidth: 2, seg: 8 }));
193
+ const sx = x + bezel, sy = top + bezel;
194
+ out.push(roundRect({ x: sx, y: sy, w: screenW, h: screenH, r: Math.max(2, r - bezel), fill: dark ? '#212529' : wallpaper, stroke: dark ? '#343a40' : WIRE.line, strokeWidth: 1, seg: 8 }));
195
+
196
+ let innerTop = sy, innerBottom = sy + screenH;
197
+ const fg = dark ? '#f1f3f5' : INK;
198
+ const SB_H = 20;
199
+
200
+ // Status marks live in an explicit band [top, top+h] and the caller advances innerTop past it.
201
+ // (Deriving the band from innerTop and then rewinding innerTop is how the time ended up sitting on
202
+ // the first row of content — the audit caught it at 73%.)
203
+ const statusBand = (top, h) => {
204
+ out.push(txt({ x: sx + 14, y: top + (h - 12 * LH) / 2, text: time, fontSize: 12, color: fg, width: 60, align: 'left' }));
205
+ const bx = sx + screenW - 52, by = top + h / 2 - 5;
206
+ for (let i = 0; i < 3; i++) out.push({ type: 'rectangle', x: bx + i * 5, y: by + 8 - i * 2.5, width: 3, height: 3 + i * 2.5, strokeColor: fg, backgroundColor: fg, fillStyle: 'solid', strokeWidth: 1 });
207
+ out.push(rc(bx + 22, by + 1, 18, 9, fg, 1));
208
+ out.push({ type: 'rectangle', x: bx + 24, y: by + 3, width: 10, height: 5, strokeColor: fg, backgroundColor: fg, fillStyle: 'solid', strokeWidth: 1 });
209
+ };
210
+ const wantStatus = d.status && status !== false;
211
+
212
+ if (d.island) {
213
+ // dynamic island: a pill inset from the top; the status marks sit BESIDE it, in its own band
214
+ const iw = screenW * 0.34, ih = Math.max(18, screenW * 0.075), iy = sy + Math.max(8, screenW * 0.026);
215
+ out.push(roundRect({ x: sx + (screenW - iw) / 2, y: iy, w: iw, h: ih, r: ih / 2, fill: '#212529', stroke: '#212529', strokeWidth: 1 }));
216
+ if (wantStatus) statusBand(iy, ih);
217
+ innerTop = iy + ih + 8;
218
+ } else {
219
+ let bandTop = sy;
220
+ if (d.camera) { out.push(dot(sx + screenW / 2, sy + 11, 4, WIRE.hint)); bandTop = sy + 22; }
221
+ if (d.notch) { const nw = screenW * 0.16, nh = 12; out.push({ type: 'rectangle', x: sx + (screenW - nw) / 2, y: sy, width: nw, height: nh, strokeColor: '#212529', backgroundColor: '#212529', fillStyle: 'solid', strokeWidth: 1 }); bandTop = sy + nh + 2; }
222
+ if (wantStatus) { statusBand(bandTop, SB_H); bandTop += SB_H; }
223
+ innerTop = bandTop + 4;
224
+ }
225
+ if (d.menubar) {
226
+ const mbH = 22;
227
+ out.push({ type: 'rectangle', x: sx, y: innerTop, width: screenW, height: mbH, strokeColor: WIRE.line, backgroundColor: WIRE.chrome, fillStyle: 'solid', strokeWidth: 1 });
228
+ ['#ff5f57', '#febc2e', '#28c840'].forEach((c, i) => out.push(dot(sx + 14 + i * 14, innerTop + mbH / 2, 4, c)));
229
+ out.push(txt({ x: sx + 60, y: innerTop + 4, text: label || 'Finder', fontSize: 11, color: WIRE.text, width: 120, align: 'left' }));
230
+ innerTop += mbH;
231
+ }
232
+ if (d.home) {
233
+ const hw = screenW * 0.36, hh = 5;
234
+ out.push(roundRect({ x: sx + (screenW - hw) / 2, y: sy + screenH - 12, w: hw, h: hh, r: hh / 2, fill: dark ? '#868e96' : '#adb5bd', stroke: 'transparent', strokeWidth: 1 }));
235
+ innerBottom = sy + screenH - 20;
236
+ }
237
+ if (d.base) {
238
+ // laptop foot — what makes a display read as a Mac
239
+ const baseH = 12, lipW = bodyW * 1.06;
240
+ out.push({ type: 'line', points: [[x - (lipW - bodyW) / 2, top + bodyH], [x + bodyW + (lipW - bodyW) / 2, top + bodyH], [x + bodyW + (lipW - bodyW) / 2 - 18, top + bodyH + baseH], [x + (lipW - bodyW) / 2 - 12, top + bodyH + baseH], [x - (lipW - bodyW) / 2, top + bodyH]], strokeColor: '#212529', backgroundColor: '#adb5bd', fillStyle: 'solid', strokeWidth: 2 });
241
+ }
242
+
243
+ const inner = { x: sx + 8, y: innerTop + 6, w: screenW - 16, h: Math.max(20, innerBottom - innerTop - 12) };
244
+ const screen = { x: sx, y: sy, w: screenW, h: screenH };
245
+
246
+ // `content` is the safe way to fill a screen: the callback receives `inner`, and anything that
247
+ // escapes the glass gets reported. The build-time audit CANNOT see this on its own — the shell is a
248
+ // filled polygon, and isOpaqueBox() only knows rect/ellipse/diamond/image — so a hardcoded child
249
+ // width that overflows the bezel would otherwise ship looking clean.
250
+ if (typeof o.content === 'function') {
251
+ const kids = [].concat(...[].concat(o.content(inner)));
252
+ if (kids.length) {
253
+ const b = bbox(kids);
254
+ const over = [
255
+ b.x < screen.x - 1 && 'left', b.y < screen.y - 1 && 'top',
256
+ b.x + b.w > screen.x + screen.w + 1 && 'right', b.y + b.h > screen.y + screen.h + 1 && 'bottom',
257
+ ].filter(Boolean);
258
+ if (over.length) {
259
+ const by = Math.round(Math.max(b.x + b.w - (screen.x + screen.w), b.y + b.h - (screen.y + screen.h), screen.x - b.x, screen.y - b.y));
260
+ try {
261
+ console.warn(`[excalidraw] device(${kind}): content escapes the screen on the ${over.join('/')} by ~${by}px — it will render outside the bezel.\n Fix: size children from the \`inner\` you were handed (inner.w=${Math.round(inner.w)}), don't hardcode widths.`);
262
+ } catch (e) {}
263
+ }
264
+ out.push(...kids);
265
+ }
266
+ }
267
+
268
+ const els = box(out, x, y, bodyW, (top - y) + bodyH + (d.base ? 12 : 0));
269
+ els.inner = inner;
270
+ els.screen = screen;
271
+ return G(els, 'device');
272
+ }
273
+
274
+ // ── app chrome ────────────────────────────────────────────────────────────
275
+ /** Mobile top bar: optional back chevron, centred title, trailing icon buttons. */
276
+ function appBar(o) {
277
+ const { x = 0, y = 0, w = 340, h = 44, title = '', back = false, actions = [], fontSize = 15, border = true, fill = WIRE.paper } = o;
278
+ const out = [{ type: 'rectangle', x, y, width: w, height: h, strokeColor: border ? WIRE.line : 'transparent', backgroundColor: fill, fillStyle: 'solid', strokeWidth: 1, roundness: false }];
279
+ if (back) out.push(...iconButton({ x: x + 4, y: y + (h - 32) / 2, size: 32, icon: 'chevron-left', variant: 'ghost' }));
280
+ if (title) out.push(txt({ x, y: y + (h - fontSize * LH) / 2, text: title, fontSize, width: w, align: 'center' }));
281
+ actions.slice(0, 3).forEach((a, i) => {
282
+ out.push(...iconButton({ x: x + w - 8 - (i + 1) * 34, y: y + (h - 32) / 2, size: 32, icon: typeof a === 'string' ? a : a.icon, variant: 'ghost' }));
283
+ });
284
+ return G(box(out, x, y, w, h), 'appbar');
285
+ }
286
+
287
+ /** Bottom tab bar. `items` are icon names or {icon,label}; `active` is the index that reads selected. */
288
+ function tabBar(o) {
289
+ const { x = 0, y = 0, w = 340, h = 52, items = [], active = 0, color = 'blue', fontSize = 10, fill = WIRE.paper } = o;
290
+ const p = pal(color);
291
+ const out = [{ type: 'rectangle', x, y, width: w, height: h, strokeColor: WIRE.line, backgroundColor: fill, fillStyle: 'solid', strokeWidth: 1, roundness: false }];
292
+ const n = Math.max(1, items.length);
293
+ const cw = w / n;
294
+ items.forEach((it, i) => {
295
+ const name = typeof it === 'string' ? it : it.icon;
296
+ const label = typeof it === 'string' ? null : it.label;
297
+ const on = i === active;
298
+ const k = on ? p.stroke : WIRE.hint;
299
+ const is = 20;
300
+ out.push(...icon({ name, x: x + i * cw + (cw - is) / 2, y: y + (label ? 7 : (h - is) / 2), size: is, color: k, strokeWidth: on ? 2 : 1.5 }));
301
+ if (label) out.push(txt({ x: x + i * cw, y: y + 31, text: label, fontSize, color: k, width: cw, align: 'center' }));
302
+ });
303
+ return G(box(out, x, y, w, h), 'tabbar');
304
+ }
305
+
306
+ // ── form controls ─────────────────────────────────────────────────────────
307
+ // Truncate to a pixel width with an ellipsis. A row is ONE line by definition, so an over-long title
308
+ // has to be cut — but it must LOOK cut. Wrapping and keeping line 1 (what this used to do) turned
309
+ // "Yıldızlı" into "Yıldız" with no hint anything was lost.
310
+ function clip(s, fontSize, w) {
311
+ const str = String(s == null ? '' : s);
312
+ if (!str || measureText(str, fontSize) <= w) return str;
313
+ let acc = '';
314
+ for (const ch of str) {
315
+ if (measureText(acc + ch + '…', fontSize) > w) break;
316
+ acc += ch;
317
+ }
318
+ return acc.replace(/\s+$/, '') + '…';
319
+ }
320
+
321
+ /** A list/table row: leading icon or avatar, title + subtitle, trailing chevron/toggle/text. */
322
+ function listRow(o) {
323
+ const {
324
+ x = 0, y = 0, w = 340, h = 56, title = '', subtitle = null, leading = null, trailing = 'chevron-right',
325
+ fontSize = 14, subSize = 12, divider: rule = true,
326
+ } = o;
327
+ const out = [{ type: 'rectangle', x, y, width: w, height: h, strokeColor: 'transparent', backgroundColor: WIRE.paper, fillStyle: 'solid', strokeWidth: 1, roundness: false }];
328
+ let tx = x + 14;
329
+ if (leading) {
330
+ if (leading === 'avatar') { out.push(...avatar({ cx: x + 14 + 16, cy: y + h / 2, r: 16 })); tx = x + 14 + 40; }
331
+ else { out.push(...icon({ name: leading, x: x + 14, y: y + (h - 20) / 2, size: 20, color: WIRE.strong })); tx = x + 14 + 32; }
332
+ }
333
+ const tw = w - (tx - x) - 46;
334
+ if (subtitle) {
335
+ out.push(txt({ x: tx, y: y + h / 2 - fontSize * LH + 2, text: clip(title, fontSize, tw), fontSize, width: tw, align: 'left' }));
336
+ out.push(txt({ x: tx, y: y + h / 2 + 2, text: clip(subtitle, subSize, tw), fontSize: subSize, color: WIRE.text, width: tw, align: 'left' }));
337
+ } else {
338
+ out.push(txt({ x: tx, y: y + (h - fontSize * LH) / 2, text: clip(title, fontSize, tw), fontSize, width: tw, align: 'left' }));
339
+ }
340
+ if (trailing === 'toggle') out.push(...toggle({ x: x + w - 58, y: y + (h - 28) / 2, on: true }));
341
+ else if (typeof trailing === 'string' && ICONS[trailing]) out.push(...icon({ name: trailing, x: x + w - 30, y: y + (h - 16) / 2, size: 16, color: WIRE.hint }));
342
+ else if (trailing) out.push(txt({ x: x + w - 110, y: y + (h - subSize * LH) / 2, text: String(trailing), fontSize: subSize, color: WIRE.text, width: 96, align: 'right' }));
343
+ if (rule) out.push({ type: 'line', points: [[tx, y + h], [x + w, y + h]], strokeColor: WIRE.line, strokeWidth: 1 });
344
+ return G(box(out, x, y, w, h), 'listrow');
345
+ }
346
+
347
+ /** iOS-style switch. */
348
+ function toggle(o) {
349
+ const { x = 0, y = 0, w = 48, h = 28, on = false, color = 'green' } = o;
350
+ const p = pal(color);
351
+ const out = [roundRect({ x, y, w, h, r: h / 2, fill: on ? p.fill : WIRE.line, stroke: on ? p.stroke : WIRE.edge, strokeWidth: 1.5 })];
352
+ const kr = h / 2 - 3;
353
+ out.push({ type: 'ellipse', x: on ? x + w - kr * 2 - 3 : x + 3, y: y + 3, width: kr * 2, height: kr * 2, strokeColor: WIRE.edge, backgroundColor: '#ffffff', fillStyle: 'solid', strokeWidth: 1 });
354
+ return G(box(out, x, y, w, h), 'toggle');
355
+ }
356
+
357
+ /** Segmented control — the mobile tab picker. */
358
+ function segmented(o) {
359
+ const { x = 0, y = 0, w = 300, h = 34, items = [], active = 0, fontSize = 13, color = 'blue' } = o;
360
+ const p = pal(color);
361
+ const out = [roundRect({ x, y, w, h, r: 8, fill: WIRE.chrome, stroke: WIRE.edge, strokeWidth: 1.5 })];
362
+ const n = Math.max(1, items.length), cw = (w - 6) / n;
363
+ items.forEach((it, i) => {
364
+ const on = i === active;
365
+ if (on) out.push(roundRect({ x: x + 3 + i * cw, y: y + 3, w: cw, h: h - 6, r: 6, fill: p.fill, stroke: p.stroke, strokeWidth: 1 }));
366
+ out.push(txt({ x: x + 3 + i * cw, y: y + (h - fontSize * LH) / 2, text: String(it), fontSize, color: on ? p.stroke : WIRE.text, width: cw, align: 'center' }));
367
+ });
368
+ return G(box(out, x, y, w, h), 'segmented');
369
+ }
370
+
371
+ /** A slider at `value` (0..1). */
372
+ function slider(o) {
373
+ const { x = 0, y = 0, w = 260, value = 0.5, color = 'blue', knob = 11 } = o;
374
+ const p = pal(color);
375
+ const cy = y + knob;
376
+ const v = Math.max(0, Math.min(1, value));
377
+ const out = [
378
+ roundRect({ x, y: cy - 2, w, h: 4, r: 2, fill: WIRE.line, stroke: 'transparent', strokeWidth: 1 }),
379
+ roundRect({ x, y: cy - 2, w: Math.max(2, w * v), h: 4, r: 2, fill: p.stroke, stroke: 'transparent', strokeWidth: 1 }),
380
+ { type: 'ellipse', x: x + w * v - knob, y: cy - knob, width: knob * 2, height: knob * 2, strokeColor: WIRE.edge, backgroundColor: '#ffffff', fillStyle: 'solid', strokeWidth: 1.5 },
381
+ ];
382
+ return G(box(out, x, y, w, knob * 2), 'slider');
383
+ }
384
+
385
+ /** A search field — an input with a magnifier, the most-drawn control in mobile wireframes. */
386
+ function searchField(o) {
387
+ const { x = 0, y = 0, w = 300, h = 36, placeholder = 'Search', fontSize = 13 } = o;
388
+ const out = [roundRect({ x, y, w, h, r: h / 2, fill: WIRE.chrome, stroke: WIRE.edge, strokeWidth: 1.5 })];
389
+ out.push(...icon({ name: 'search', x: x + 12, y: y + (h - 15) / 2, size: 15, color: WIRE.hint }));
390
+ out.push(txt({ x: x + 36, y: y + (h - fontSize * LH) / 2, text: placeholder, fontSize, color: WIRE.hint, width: w - 48, align: 'left' }));
391
+ return G(box(out, x, y, w, h), 'search');
392
+ }
393
+
394
+ module.exports = {
395
+ // devices + chrome
396
+ device, DEVICES, appBar, tabBar,
397
+ // icons
398
+ icon, iconButton, ICONS, iconNames,
399
+ // controls
400
+ listRow, toggle, segmented, slider, searchField,
401
+ // geometry / text helpers
402
+ clip,
403
+ roundRect, roundRectPoints,
404
+ // re-exported from style.js so one require gives the whole UI kit
405
+ windowFrame, navbar, button, input, textRows, imagePlaceholder, avatar, chip, divider,
406
+ };