@templatical/editor 0.34.2 → 0.35.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 (206) hide show
  1. package/dist/{AiChatSidebar-CkHJ28sf.js → AiChatSidebar-DPHbCGu4.js} +29 -21
  2. package/dist/{AiFeatureMenu-DWdKguDN.js → AiFeatureMenu--8z7qvg1.js} +4 -4
  3. package/dist/{BlockIssueBadge-DcyHTAqw.js → BlockIssueBadge-BGCsJqqM.js} +2 -2
  4. package/dist/BlockPreviewCanvas-DKspIy8V.js +131 -0
  5. package/dist/{CloudHeaderExtras-BJ3Cebhv.js → CloudHeaderExtras-CRUQvgSV.js} +2 -2
  6. package/dist/{CloudPanels-BGz9ygo6.js → CloudPanels-Bp5gFtaL.js} +3 -3
  7. package/dist/{CollaboratorBar-KphmSQeZ.js → CollaboratorBar-DvwZm5u7.js} +57 -49
  8. package/dist/{ColorPicker-C6EOwtHC.js → ColorPicker-DtozuN75.js} +1 -1
  9. package/dist/{CommentsPanel-DmWh8f08.js → CommentsPanel-3C-DsEjy.js} +1 -1
  10. package/dist/{CommentsSidebar-CL_tffzT.js → CommentsSidebar-go1FnjDW.js} +34 -25
  11. package/dist/{CountdownToolbar-B8J4i5Xp.js → CountdownToolbar-SLBuij7Q.js} +1 -1
  12. package/dist/{DesignReferenceSidebar-BDhEeaAh.js → DesignReferenceSidebar-rcE_BvUd.js} +55 -47
  13. package/dist/{IssuesPanel-CiOjeRFI.js → IssuesPanel-Cig46N1i.js} +23 -15
  14. package/dist/{MergeTagInput-RytOlh1A.js → MergeTagInput-D1hzsn2b.js} +42 -33
  15. package/dist/{MergeTagModeToggle-Bi-FydXj.js → MergeTagModeToggle-CH9wR5YF.js} +70 -58
  16. package/dist/{ParagraphEditor-C87tkmtE.js → ParagraphEditor-CT2AdMdZ.js} +365 -323
  17. package/dist/{RestoreVersionDialog-Db5rZRMA.js → RestoreVersionDialog-mdqgQxFR.js} +3 -3
  18. package/dist/{RichTextEditorContent-BmTcV2-a.js → RichTextEditorContent-riOB-rKD.js} +48 -36
  19. package/dist/{SaveBlockDialog-CDCzx6Bo.js → SaveBlockDialog-Cn2v4Lpj.js} +6 -6
  20. package/dist/{SavedBlocksBrowserModal-DQR6kJB1.js → SavedBlocksBrowserModal-BORqqASb.js} +7 -7
  21. package/dist/{SavedBlocksPanels-DFzV0CRD.js → SavedBlocksPanels-ffBnWuVU.js} +1 -1
  22. package/dist/{SavedBlocksPickBar-XyXmnSnO.js → SavedBlocksPickBar-BmQC2tWN.js} +2 -2
  23. package/dist/{TemplateScoringPanel-CpyEKMzC.js → TemplateScoringPanel-CxAtdreR.js} +36 -27
  24. package/dist/{TestEmailModal-DIfJy9eE.js → TestEmailModal-Cjn1padY.js} +7 -7
  25. package/dist/{TestEmailPanel-C8Wx5yPS.js → TestEmailPanel-Bf3XZ1jn.js} +1 -1
  26. package/dist/{TitleEditor-iNFOisr4.js → TitleEditor-pYEUX6lR.js} +5 -5
  27. package/dist/{TplModal-CowM3o_F.js → TplModal-DRx4ytx8.js} +1 -1
  28. package/dist/{VersionHistoryMenu-CV90OhXs.js → VersionHistoryMenu-CrNrGsaa.js} +21 -13
  29. package/dist/{VersionHistoryPanels-BE3pplJG.js → VersionHistoryPanels-CsuQk6W3.js} +1 -1
  30. package/dist/{VersionPreviewBanner-BwUWBtlH.js → VersionPreviewBanner-BqbO0Zss.js} +1 -1
  31. package/dist/blockTypeIcons-D_4HdaY9.js +175 -0
  32. package/dist/bundle-stats.json +6 -6
  33. package/dist/{ca-C0hfqt4U.js → ca-D9S-UCfZ.js} +2 -0
  34. package/dist/cdn/chunks/{AiChatSidebar-BEnIWn6s.js → AiChatSidebar-D0f74_nS.js} +3 -3
  35. package/dist/cdn/chunks/{AiChatSidebar-BEnIWn6s.js.map → AiChatSidebar-D0f74_nS.js.map} +1 -1
  36. package/dist/cdn/chunks/{AiFeatureMenu-DMl23akc.js → AiFeatureMenu-DwGb3eF9.js} +3 -3
  37. package/dist/cdn/chunks/{AiFeatureMenu-DMl23akc.js.map → AiFeatureMenu-DwGb3eF9.js.map} +1 -1
  38. package/dist/cdn/chunks/{BlockIssueBadge-CloK4k3M.js → BlockIssueBadge-CosQgeJ-.js} +2 -2
  39. package/dist/cdn/chunks/{BlockIssueBadge-CloK4k3M.js.map → BlockIssueBadge-CosQgeJ-.js.map} +1 -1
  40. package/dist/cdn/chunks/BlockPreviewCanvas-CIpZ2a6g.js +133 -0
  41. package/dist/cdn/chunks/BlockPreviewCanvas-CIpZ2a6g.js.map +1 -0
  42. package/dist/cdn/chunks/{CloudHeaderExtras-b4KpVeUN.js → CloudHeaderExtras-CQzQwKFB.js} +3 -3
  43. package/dist/cdn/chunks/{CloudHeaderExtras-b4KpVeUN.js.map → CloudHeaderExtras-CQzQwKFB.js.map} +1 -1
  44. package/dist/cdn/chunks/{CloudPanels-DzwOq9lp.js → CloudPanels-BmexuLOU.js} +4 -4
  45. package/dist/cdn/chunks/{CloudPanels-DzwOq9lp.js.map → CloudPanels-BmexuLOU.js.map} +1 -1
  46. package/dist/cdn/chunks/{CollaboratorBar-WXKvpoGK.js → CollaboratorBar-DEICobpp.js} +3 -3
  47. package/dist/cdn/chunks/{CollaboratorBar-WXKvpoGK.js.map → CollaboratorBar-DEICobpp.js.map} +1 -1
  48. package/dist/cdn/chunks/{ColorPicker-BubTDiS1.js → ColorPicker-C7yC58FK.js} +2 -2
  49. package/dist/cdn/chunks/{ColorPicker-BubTDiS1.js.map → ColorPicker-C7yC58FK.js.map} +1 -1
  50. package/dist/cdn/chunks/{CommentsPanel-DAA2VqX4.js → CommentsPanel-OjmKxLL7.js} +2 -2
  51. package/dist/cdn/chunks/{CommentsPanel-DAA2VqX4.js.map → CommentsPanel-OjmKxLL7.js.map} +1 -1
  52. package/dist/cdn/chunks/{CommentsSidebar-Ci0M2ePp.js → CommentsSidebar-C4pWZGpy.js} +2 -2
  53. package/dist/cdn/chunks/{CommentsSidebar-Ci0M2ePp.js.map → CommentsSidebar-C4pWZGpy.js.map} +1 -1
  54. package/dist/cdn/chunks/{CountdownToolbar-CzZq26Vp.js → CountdownToolbar-wlJOxyAl.js} +2 -2
  55. package/dist/cdn/chunks/{CountdownToolbar-CzZq26Vp.js.map → CountdownToolbar-wlJOxyAl.js.map} +1 -1
  56. package/dist/cdn/chunks/{DesignReferenceSidebar-C_ehdX5C.js → DesignReferenceSidebar-DELhnh8h.js} +3 -3
  57. package/dist/cdn/chunks/{DesignReferenceSidebar-C_ehdX5C.js.map → DesignReferenceSidebar-DELhnh8h.js.map} +1 -1
  58. package/dist/cdn/chunks/{IssuesPanel-BXAN2_al.js → IssuesPanel-C9i3o8aE.js} +2 -2
  59. package/dist/cdn/chunks/{IssuesPanel-BXAN2_al.js.map → IssuesPanel-C9i3o8aE.js.map} +1 -1
  60. package/dist/cdn/chunks/{MergeTagInput-1vcI9uQH.js → MergeTagInput-D76Kyvgu.js} +5 -5
  61. package/dist/cdn/chunks/{MergeTagInput-1vcI9uQH.js.map → MergeTagInput-D76Kyvgu.js.map} +1 -1
  62. package/dist/cdn/chunks/{MergeTagModeToggle-B_mUYJo4.js → MergeTagModeToggle-Dz3gxN2y.js} +3 -3
  63. package/dist/cdn/chunks/{MergeTagModeToggle-B_mUYJo4.js.map → MergeTagModeToggle-Dz3gxN2y.js.map} +1 -1
  64. package/dist/cdn/chunks/{ParagraphEditor-DCDHMcmx.js → ParagraphEditor-DQn0HMfV.js} +176 -172
  65. package/dist/cdn/chunks/{ParagraphEditor-DCDHMcmx.js.map → ParagraphEditor-DQn0HMfV.js.map} +1 -1
  66. package/dist/cdn/chunks/{RestoreVersionDialog-BkSSwf-Q.js → RestoreVersionDialog-BdjOL8Oy.js} +3 -3
  67. package/dist/cdn/chunks/{RestoreVersionDialog-BkSSwf-Q.js.map → RestoreVersionDialog-BdjOL8Oy.js.map} +1 -1
  68. package/dist/cdn/chunks/{RichTextEditorContent-S4KogjIj.js → RichTextEditorContent-Drzwgh0J.js} +4 -4
  69. package/dist/cdn/chunks/{RichTextEditorContent-S4KogjIj.js.map → RichTextEditorContent-Drzwgh0J.js.map} +1 -1
  70. package/dist/cdn/chunks/{SaveBlockDialog-BH774Nt0.js → SaveBlockDialog-B3rbmXSM.js} +5 -5
  71. package/dist/cdn/chunks/{SaveBlockDialog-BH774Nt0.js.map → SaveBlockDialog-B3rbmXSM.js.map} +1 -1
  72. package/dist/cdn/chunks/{SavedBlocksBrowserModal-CSUqw_dD.js → SavedBlocksBrowserModal-CkUXlnd2.js} +6 -6
  73. package/dist/cdn/chunks/{SavedBlocksBrowserModal-CSUqw_dD.js.map → SavedBlocksBrowserModal-CkUXlnd2.js.map} +1 -1
  74. package/dist/cdn/chunks/{SavedBlocksPanels-B6ApbCjG.js → SavedBlocksPanels-BPi3PZKP.js} +2 -2
  75. package/dist/cdn/chunks/{SavedBlocksPanels-B6ApbCjG.js.map → SavedBlocksPanels-BPi3PZKP.js.map} +1 -1
  76. package/dist/cdn/chunks/{SavedBlocksPickBar-C9MXSRcR.js → SavedBlocksPickBar-BvB9A5W2.js} +3 -3
  77. package/dist/cdn/chunks/{SavedBlocksPickBar-C9MXSRcR.js.map → SavedBlocksPickBar-BvB9A5W2.js.map} +1 -1
  78. package/dist/cdn/chunks/{TemplateScoringPanel-QVOxc9TZ.js → TemplateScoringPanel-BBCyLnOi.js} +2 -2
  79. package/dist/cdn/chunks/{TemplateScoringPanel-QVOxc9TZ.js.map → TemplateScoringPanel-BBCyLnOi.js.map} +1 -1
  80. package/dist/cdn/chunks/{TestEmailModal-_CZvaO58.js → TestEmailModal-DAGTsBqi.js} +7 -7
  81. package/dist/cdn/chunks/{TestEmailModal-_CZvaO58.js.map → TestEmailModal-DAGTsBqi.js.map} +1 -1
  82. package/dist/cdn/chunks/{TestEmailPanel-DXLxvyrD.js → TestEmailPanel-BfenVvfx.js} +2 -2
  83. package/dist/cdn/chunks/{TestEmailPanel-DXLxvyrD.js.map → TestEmailPanel-BfenVvfx.js.map} +1 -1
  84. package/dist/cdn/chunks/{TitleEditor-BuCOahW3.js → TitleEditor-oQ4gr6wS.js} +5 -5
  85. package/dist/cdn/chunks/{TitleEditor-BuCOahW3.js.map → TitleEditor-oQ4gr6wS.js.map} +1 -1
  86. package/dist/cdn/chunks/{TplModal-CqY8R3hn.js → TplModal-DxOMcVwp.js} +2 -2
  87. package/dist/cdn/chunks/{TplModal-CqY8R3hn.js.map → TplModal-DxOMcVwp.js.map} +1 -1
  88. package/dist/cdn/chunks/{VersionHistoryMenu-DDjIBvad.js → VersionHistoryMenu-BfVUvWHT.js} +4 -4
  89. package/dist/cdn/chunks/{VersionHistoryMenu-DDjIBvad.js.map → VersionHistoryMenu-BfVUvWHT.js.map} +1 -1
  90. package/dist/cdn/chunks/{VersionHistoryPanels-BuTw04T5.js → VersionHistoryPanels-NpDb641L.js} +2 -2
  91. package/dist/cdn/chunks/{VersionHistoryPanels-BuTw04T5.js.map → VersionHistoryPanels-NpDb641L.js.map} +1 -1
  92. package/dist/cdn/chunks/{VersionPreviewBanner-fGAv20qI.js → VersionPreviewBanner-Cpgt4xHc.js} +2 -2
  93. package/dist/cdn/chunks/{VersionPreviewBanner-fGAv20qI.js.map → VersionPreviewBanner-Cpgt4xHc.js.map} +1 -1
  94. package/dist/cdn/chunks/{blockTypeIcons-ZEzEpCti.js → blockTypeIcons-t9vvaiuH.js} +2 -2
  95. package/dist/cdn/chunks/{blockTypeIcons-ZEzEpCti.js.map → blockTypeIcons-t9vvaiuH.js.map} +1 -1
  96. package/dist/cdn/chunks/{ca-C0hfqt4U.js → ca-D9S-UCfZ.js} +3 -1
  97. package/dist/cdn/chunks/{ca-C0hfqt4U.js.map → ca-D9S-UCfZ.js.map} +1 -1
  98. package/dist/cdn/chunks/{cloud-BP5-6__d.js → cloud-CullB76F.js} +2 -2
  99. package/dist/cdn/chunks/{cloud-BP5-6__d.js.map → cloud-CullB76F.js.map} +1 -1
  100. package/dist/cdn/chunks/{createCloudRuntime-JFE-sxbT.js → createCloudRuntime-Cx2JpUYU.js} +4 -4
  101. package/dist/cdn/chunks/{createCloudRuntime-JFE-sxbT.js.map → createCloudRuntime-Cx2JpUYU.js.map} +1 -1
  102. package/dist/{de-C6J2Iae8.js → cdn/chunks/de-BUPszLpi.js} +4 -0
  103. package/dist/cdn/chunks/{de-C6J2Iae8.js.map → de-BUPszLpi.js.map} +1 -1
  104. package/dist/cdn/chunks/{en-BaUYOQi8.js → en-BbOC5x3w.js} +3 -1
  105. package/dist/cdn/chunks/en-BbOC5x3w.js.map +1 -0
  106. package/dist/cdn/chunks/en-rGHphLWo.js +2 -0
  107. package/dist/cdn/chunks/{es-CKDYfJYn.js → es-0bCDcT9p.js} +3 -1
  108. package/dist/cdn/chunks/{es-CKDYfJYn.js.map → es-0bCDcT9p.js.map} +1 -1
  109. package/dist/cdn/chunks/{extensions-7z04EIGg.js → extensions-BpXY7jO5.js} +3 -3
  110. package/dist/cdn/chunks/{extensions-7z04EIGg.js.map → extensions-BpXY7jO5.js.map} +1 -1
  111. package/dist/{fr-L8DR73mK.js → cdn/chunks/fr-Brr8mJ9e.js} +4 -0
  112. package/dist/cdn/chunks/fr-Brr8mJ9e.js.map +1 -0
  113. package/dist/cdn/chunks/icons-BLxcQUck.js +1675 -0
  114. package/dist/cdn/chunks/icons-BLxcQUck.js.map +1 -0
  115. package/dist/cdn/chunks/{nl-GDWeuX18.js → nl-Co3_RL0M.js} +3 -1
  116. package/dist/cdn/chunks/nl-Co3_RL0M.js.map +1 -0
  117. package/dist/cdn/chunks/{preRenderCustomBlocks-LFB-e-0m.js → preRenderCustomBlocks-Bdpiiw4A.js} +2 -2
  118. package/dist/cdn/chunks/{preRenderCustomBlocks-LFB-e-0m.js.map → preRenderCustomBlocks-Bdpiiw4A.js.map} +1 -1
  119. package/dist/cdn/chunks/{pt-BR-B1AVVhWj.js → pt-BR-BECez5b0.js} +3 -1
  120. package/dist/cdn/chunks/{pt-BR-B1AVVhWj.js.map → pt-BR-BECez5b0.js.map} +1 -1
  121. package/dist/cdn/chunks/{src-CX_VHu8r.js → src-BC-eolWY.js} +161 -160
  122. package/dist/cdn/chunks/src-BC-eolWY.js.map +1 -0
  123. package/dist/cdn/chunks/{src-BM3f9I7t.js → src-CUvZWL6Y.js} +83 -83
  124. package/dist/cdn/chunks/src-CUvZWL6Y.js.map +1 -0
  125. package/dist/cdn/chunks/{src-Cxl-9O-L.js → src-CaXeO-gG.js} +17 -17
  126. package/dist/cdn/chunks/{src-Cxl-9O-L.js.map → src-CaXeO-gG.js.map} +1 -1
  127. package/dist/cdn/chunks/{src-CLoCCDfs.js → src-Dwgq7hf4.js} +71 -48
  128. package/dist/cdn/chunks/src-Dwgq7hf4.js.map +1 -0
  129. package/dist/cdn/chunks/{useEditorCore-ehUSEsbw.js → useEditorCore-D4MytYB9.js} +495 -485
  130. package/dist/cdn/chunks/useEditorCore-D4MytYB9.js.map +1 -0
  131. package/dist/cdn/chunks/{useMergeTag-TLhF2fGK.js → useMergeTag-CSocWpxV.js} +2 -2
  132. package/dist/cdn/chunks/{useMergeTag-TLhF2fGK.js.map → useMergeTag-CSocWpxV.js.map} +1 -1
  133. package/dist/cdn/editor.css +1 -1
  134. package/dist/cdn/editor.js +920 -907
  135. package/dist/cdn/editor.js.map +1 -1
  136. package/dist/check-CY-Ec6FV.js +11 -0
  137. package/dist/chevron-down-BghbWA9p.js +11 -0
  138. package/dist/chevron-up-n3qFAPS3.js +11 -0
  139. package/dist/circle-alert-L5uGGFRP.js +30 -0
  140. package/dist/clock-Dgnwp18n.js +16 -0
  141. package/dist/{cloud-BMzRKCbk.js → cloud-B1c4XIsn.js} +1 -1
  142. package/dist/{createCloudRuntime-DbTFm1ES.js → createCloudRuntime-PevFSH74.js} +2 -2
  143. package/dist/createLucideIcon-05lB8iWe.js +96 -0
  144. package/dist/{cdn/chunks/de-C6J2Iae8.js → de-BUPszLpi.js} +2 -2
  145. package/dist/{dist-PL6vP4SC.js → dist-tGkjkX-v.js} +143 -122
  146. package/dist/{en-BaUYOQi8.js → en-BbOC5x3w.js} +2 -0
  147. package/dist/{es-CKDYfJYn.js → es-0bCDcT9p.js} +2 -0
  148. package/dist/{extensions-Mf8iHey9.js → extensions-Bawm4BOM.js} +2 -2
  149. package/dist/{cdn/chunks/fr-L8DR73mK.js → fr-Brr8mJ9e.js} +2 -2
  150. package/dist/image-up-ovhjl9u9.js +27 -0
  151. package/dist/info-c3KI927I.js +23 -0
  152. package/dist/loader-circle-B2Y8xZQb.js +12 -0
  153. package/dist/message-circle-B7K-naUa.js +11 -0
  154. package/dist/{nl-GDWeuX18.js → nl-Co3_RL0M.js} +2 -0
  155. package/dist/pencil-BhWTFcp4.js +14 -0
  156. package/dist/{preRenderCustomBlocks-CSIF-HyJ.js → preRenderCustomBlocks-DFXH54Zr.js} +1 -1
  157. package/dist/{pt-BR-B1AVVhWj.js → pt-BR-BECez5b0.js} +2 -0
  158. package/dist/refresh-cw-BqUR5vgO.js +37 -0
  159. package/dist/send-D1vvuW79.js +14 -0
  160. package/dist/shield-check-DjK54PnW.js +14 -0
  161. package/dist/sparkles-DNdS9V_3.js +28 -0
  162. package/dist/style.css +1 -1
  163. package/dist/templatical-editor.js +1238 -1167
  164. package/dist/text-align-start-Bcmo_VUY.js +58 -0
  165. package/dist/trash-C9bMXvw4.js +30 -0
  166. package/dist/triangle-alert-Bn8Bd2Yv.js +22 -0
  167. package/dist/upload-DKgg1gno.js +21 -0
  168. package/dist/{useEditorCore-DdkQuTHl.js → useEditorCore-CYGvurZa.js} +1089 -1026
  169. package/dist/{useMergeTag-gSuYus-M.js → useMergeTag-C9GEpljB.js} +1 -1
  170. package/dist/{useScrollToBlock-CdI8fGqt.js → useScrollToBlock-oUx1HIZ2.js} +27 -23
  171. package/dist/x-BPKoRPTG.js +14 -0
  172. package/package.json +9 -9
  173. package/dist/BlockPreviewCanvas-B621ANn9.js +0 -129
  174. package/dist/blockTypeIcons-DQsKtHik.js +0 -134
  175. package/dist/cdn/chunks/BlockPreviewCanvas-DSiJFJa0.js +0 -131
  176. package/dist/cdn/chunks/BlockPreviewCanvas-DSiJFJa0.js.map +0 -1
  177. package/dist/cdn/chunks/en-BaUYOQi8.js.map +0 -1
  178. package/dist/cdn/chunks/en-mEKGL1PX.js +0 -2
  179. package/dist/cdn/chunks/fr-L8DR73mK.js.map +0 -1
  180. package/dist/cdn/chunks/icons-SP5Itd-A.js +0 -1218
  181. package/dist/cdn/chunks/icons-SP5Itd-A.js.map +0 -1
  182. package/dist/cdn/chunks/nl-GDWeuX18.js.map +0 -1
  183. package/dist/cdn/chunks/src-BM3f9I7t.js.map +0 -1
  184. package/dist/cdn/chunks/src-CLoCCDfs.js.map +0 -1
  185. package/dist/cdn/chunks/src-CX_VHu8r.js.map +0 -1
  186. package/dist/cdn/chunks/useEditorCore-ehUSEsbw.js.map +0 -1
  187. package/dist/check-JK8Uacq0.js +0 -7
  188. package/dist/chevron-down-a-fb_Ijn.js +0 -7
  189. package/dist/chevron-up-x4l0et_Y.js +0 -7
  190. package/dist/circle-alert-CJDW5LZo.js +0 -25
  191. package/dist/clock-BW6rXk2f.js +0 -12
  192. package/dist/createLucideIcon-Bvw1Zx56.js +0 -43
  193. package/dist/image-up-CuzFc1mu.js +0 -23
  194. package/dist/info-DybAwcA3.js +0 -19
  195. package/dist/loader-circle-t-SVgmJ3.js +0 -7
  196. package/dist/message-circle-DtwKrlhN.js +0 -7
  197. package/dist/pencil-2qhC-Y4q.js +0 -10
  198. package/dist/refresh-cw-C8SBuPdg.js +0 -29
  199. package/dist/send-BoqJliF6.js +0 -10
  200. package/dist/shield-check-D0dMaHtE.js +0 -10
  201. package/dist/sparkles-Dy2BJjiX.js +0 -23
  202. package/dist/text-align-start-DrxjxvIy.js +0 -43
  203. package/dist/trash-rYMzzHZd.js +0 -25
  204. package/dist/triangle-alert-DxnFj4-B.js +0 -17
  205. package/dist/upload-DZu1adQz.js +0 -17
  206. package/dist/x-DJOd1hS4.js +0 -10
@@ -1 +0,0 @@
1
- {"version":3,"file":"src-BM3f9I7t.js","names":[],"sources":["../../../../quality/src/types.ts","../../../../quality/src/contrast.ts","../../../../quality/src/walk.ts","../../../../quality/src/run-rules.ts","../../../../quality/src/accessibility/messages/de.ts","../../../../quality/src/accessibility/messages/en.ts","../../../../quality/src/accessibility/messages/index.ts","../../../../quality/src/accessibility/rules/img-missing-alt.ts","../../../../quality/src/accessibility/rules/img-alt-is-filename.ts","../../../../quality/src/accessibility/rules/img-alt-too-long.ts","../../../../quality/src/accessibility/rules/img-decorative-needs-empty-alt.ts","../../../../quality/src/accessibility/dictionaries/de.ts","../../../../quality/src/accessibility/dictionaries/en.ts","../../../../quality/src/accessibility/dictionaries/index.ts","../../../../quality/src/accessibility/rules/img-linked-no-context.ts","../../../../quality/src/html-utils.ts","../../../../quality/src/accessibility/rules/heading-empty.ts","../../../../quality/src/accessibility/rules/heading-skip-level.ts","../../../../quality/src/accessibility/rules/heading-multiple-h1.ts","../../../../quality/src/accessibility/rules/link-empty.ts","../../../../quality/src/accessibility/rules/link-vague-text.ts","../../../../quality/src/accessibility/rules/link-href-empty.ts","../../../../quality/src/accessibility/rules/link-target-blank-no-rel.ts","../../../../quality/src/accessibility/rules/link-nested-anchor.ts","../../../../quality/src/accessibility/rules/text-all-caps.ts","../../../../quality/src/accessibility/rules/text-justified.ts","../../../../quality/src/accessibility/rules/text-low-contrast.ts","../../../../quality/src/accessibility/rules/text-too-small.ts","../../../../quality/src/accessibility/rules/button-vague-label.ts","../../../../quality/src/accessibility/rules/button-touch-target.ts","../../../../quality/src/accessibility/rules/button-low-contrast.ts","../../../../quality/src/accessibility/rules/missing-preheader.ts","../../../../quality/src/accessibility/index.ts","../../../../quality/src/structure/messages/de.ts","../../../../quality/src/structure/messages/en.ts","../../../../quality/src/structure/messages/index.ts","../../../../quality/src/structure/rules/duplicate-block-id.ts","../../../../quality/src/structure/rules/empty-column.ts","../../../../quality/src/structure/rules/empty-section.ts","../../../../quality/src/structure/rules/nested-section.ts","../../../../quality/src/structure/rules/section-column-mismatch.ts","../../../../quality/src/structure/index.ts","../../../../quality/src/links/messages/de.ts","../../../../quality/src/links/messages/en.ts","../../../../quality/src/links/messages/index.ts","../../../../quality/src/url-walker.ts","../../../../quality/src/links/rules/javascript-protocol.ts","../../../../quality/src/links/rules/unsupported-protocol.ts","../../../../quality/src/links/rules/malformed-mailto.ts","../../../../quality/src/links/rules/malformed-tel.ts","../../../../quality/src/links/rules/localhost-or-staging.ts","../../../../quality/src/links/index.ts","../../../../quality/src/util.ts","../../../../quality/src/lint-template.ts"],"sourcesContent":["import type {\n Block,\n SectionBlock,\n TemplateContent,\n TemplateSettings,\n} from \"@templatical/types\";\n\nexport type Severity = \"error\" | \"warning\" | \"info\" | \"off\";\n\nexport interface LintIssue {\n /** Block id, or null for template-level issues. */\n blockId: string | null;\n ruleId: string;\n severity: Exclude<Severity, \"off\">;\n message: string;\n fix?: LintPatch;\n}\n\nexport interface LintPatchContext {\n updateBlock: (blockId: string, patch: Partial<Block>) => void;\n updateSettings: (patch: Partial<TemplateSettings>) => void;\n removeBlock: (blockId: string) => void;\n}\n\nexport interface LintPatch {\n description: string;\n apply: (ctx: LintPatchContext) => void;\n}\n\nexport interface LintThresholds {\n altMaxLength: number;\n minFontSize: number;\n allCapsMinLength: number;\n minTouchTargetPx: number;\n}\n\n/**\n * Per-rule severity override. Set a rule to `'off'` to disable it.\n * Keys are the full prefixed rule IDs (`a11y.*`, `structure.*`, `link.*`)\n * so a value copied from `LintIssue.ruleId` pastes straight in.\n */\nexport type RuleOverrides = Record<string, Severity>;\n\n/** Options consumed only by the accessibility linter. */\nexport interface AccessibilityLintOptions {\n rules?: RuleOverrides;\n thresholds?: Partial<LintThresholds>;\n}\n\n/** Options consumed only by the structure linter. */\nexport interface StructureLintOptions {\n rules?: RuleOverrides;\n}\n\n/** Options consumed only by the links linter. */\nexport interface LinksLintOptions {\n rules?: RuleOverrides;\n /**\n * Host patterns that should flag as \"staging / non-production\".\n * Each entry is a glob-style pattern matched against the URL host.\n * `*` matches any run of characters (including `.`), so `*.staging.*`\n * matches `app.staging.example.com`.\n *\n * Default: ['localhost', '127.0.0.1', '0.0.0.0', '*.local',\n * '*.staging.*', '*.dev.*']\n */\n nonProductionHosts?: string[];\n}\n\nexport interface LintOptions {\n /**\n * Fully disable linting. When true, the editor skips lazy-loading the\n * package, hides the sidebar tab, and suppresses inline badges.\n */\n disabled?: boolean;\n /** Locale for vague-text dictionaries and message text. Falls back to `en`. */\n locale?: string;\n /**\n * Accessibility linter config. Set to `false` to disable the whole\n * `lintAccessibility` linter without enumerating its rules.\n */\n accessibility?: false | AccessibilityLintOptions;\n /**\n * Structure linter config. Set to `false` to disable the whole\n * `lintStructure` linter without enumerating its rules.\n */\n structure?: false | StructureLintOptions;\n /**\n * Links linter config. Set to `false` to disable the whole `lintLinks`\n * linter without enumerating its rules.\n */\n links?: false | LinksLintOptions;\n}\n\nexport interface ResolvedLinksOptions {\n nonProductionHosts: string[];\n}\n\nexport interface ResolvedOptions {\n locale: string;\n rules: RuleOverrides;\n thresholds: LintThresholds;\n links: ResolvedLinksOptions;\n /** Returns the effective severity for a rule (override or default). */\n severity: (ruleId: string) => Severity;\n}\n\nexport interface WalkContext {\n parent: Block | null;\n section: SectionBlock | null;\n columnIndex: number | null;\n depth: number;\n /**\n * Nearest opaque ancestor background, or template settings background.\n * Hex string, lowercased.\n */\n resolvedBackgroundColor: string;\n /**\n * Document-level default text color (`settings.textColor`), lowercased hex.\n * A text block that doesn't set its own `color` inherits this, so contrast\n * rules resolve the effective color against it.\n */\n resolvedTextColor: string;\n}\n\nexport interface RuleMeta {\n /** Stable identifier — used for severity overrides and message lookup. */\n id: string;\n /** Default severity when no override is supplied. */\n severity: Exclude<Severity, \"off\">;\n}\n\n/**\n * What a rule emits per match. The orchestrator combines this with the\n * rule's `meta` (for `ruleId` + default severity) and resolves the\n * localized message via the active locale's message map.\n */\nexport interface RuleHit {\n blockId: string | null;\n /** Interpolation values for the rule's localized message template. */\n params?: Record<string, string | number>;\n fix?: LintPatch;\n}\n\nexport interface Rule {\n meta: RuleMeta;\n /** Block-level rule. Returns a hit or null. */\n block?: (\n block: Block,\n ctx: WalkContext,\n opts: ResolvedOptions,\n ) => RuleHit | null;\n /** Template-level rule. Runs once after the walk. */\n template?: (content: TemplateContent, opts: ResolvedOptions) => RuleHit[];\n}\n\nexport const DEFAULT_A11Y_THRESHOLDS: LintThresholds = {\n altMaxLength: 125,\n minFontSize: 14,\n allCapsMinLength: 20,\n minTouchTargetPx: 44,\n};\n\nexport const DEFAULT_NON_PRODUCTION_HOSTS: string[] = [\n \"localhost\",\n \"127.0.0.1\",\n \"0.0.0.0\",\n \"*.local\",\n \"*.staging.*\",\n \"*.dev.*\",\n];\n","/**\n * WCAG 2.1 sRGB relative-luminance contrast.\n *\n * Inputs are hex strings (`#rgb`, `#rrggbb`, optional leading `#`).\n * Returns the contrast ratio (1–21) per WCAG, or `NaN` if either input\n * cannot be parsed as an opaque solid hex color.\n *\n * The codebase uses OKLch for design tokens, but contrast math is\n * sRGB-defined; mixing the two gives incorrect results.\n */\nexport function getContrastRatio(fg: string, bg: string): number {\n const fgRgb = parseHex(fg);\n const bgRgb = parseHex(bg);\n\n if (!fgRgb || !bgRgb) {\n return Number.NaN;\n }\n\n const l1 = relativeLuminance(fgRgb);\n const l2 = relativeLuminance(bgRgb);\n const lighter = Math.max(l1, l2);\n const darker = Math.min(l1, l2);\n\n return (lighter + 0.05) / (darker + 0.05);\n}\n\nexport interface Rgb {\n r: number;\n g: number;\n b: number;\n}\n\nconst HEX3 = /^#?([0-9a-f])([0-9a-f])([0-9a-f])$/i;\nconst HEX6 = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i;\nconst HEX8 = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i;\n\nexport function parseHex(input: string | undefined | null): Rgb | null {\n if (typeof input !== \"string\") {\n return null;\n }\n\n const trimmed = input.trim();\n\n const match8 = HEX8.exec(trimmed);\n if (match8) {\n // Only treat fully-opaque (alpha = ff) as a valid RGB color; partial\n // alpha can't be flattened without knowing the underlay.\n if (match8[4].toLowerCase() !== \"ff\") return null;\n return {\n r: parseInt(match8[1], 16),\n g: parseInt(match8[2], 16),\n b: parseInt(match8[3], 16),\n };\n }\n\n const match6 = HEX6.exec(trimmed);\n if (match6) {\n return {\n r: parseInt(match6[1], 16),\n g: parseInt(match6[2], 16),\n b: parseInt(match6[3], 16),\n };\n }\n\n const match3 = HEX3.exec(trimmed);\n if (match3) {\n return {\n r: parseInt(match3[1] + match3[1], 16),\n g: parseInt(match3[2] + match3[2], 16),\n b: parseInt(match3[3] + match3[3], 16),\n };\n }\n\n return null;\n}\n\nexport function isOpaqueHex(input: string | undefined | null): boolean {\n return parseHex(input ?? \"\") !== null;\n}\n\nfunction relativeLuminance({ r, g, b }: Rgb): number {\n const rs = channel(r / 255);\n const gs = channel(g / 255);\n const bs = channel(b / 255);\n return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;\n}\n\nfunction channel(c: number): number {\n return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n}\n","import type { Block, TemplateContent } from \"@templatical/types\";\nimport { isSection } from \"@templatical/types\";\nimport type { WalkContext } from \"./types\";\nimport { isOpaqueHex } from \"./contrast\";\n\nexport type Visitor = (block: Block, ctx: WalkContext) => void;\n\nconst DEFAULT_BG = \"#ffffff\";\nconst DEFAULT_TEXT = \"#1a1a1a\";\n\n/**\n * Pure traversal of the block tree. Calls `visit` once per block in\n * document order, providing a `WalkContext` that includes the resolved\n * background color (nearest opaque ancestor) and structural refs.\n *\n * Sections cannot nest (renderer enforces this), so the walker doesn't\n * descend into a section that lives inside a column. Custom blocks are\n * visited but not descended into.\n */\nexport function walkBlocks(content: TemplateContent, visit: Visitor): void {\n const rootBg = isOpaqueHex(content.settings.backgroundColor)\n ? content.settings.backgroundColor.toLowerCase()\n : DEFAULT_BG;\n // Document-level default text color — constant across the tree. Text blocks\n // that don't set their own `color` inherit it, so it's the effective color\n // contrast rules compare against.\n const rootTextColor = isOpaqueHex(content.settings.textColor)\n ? content.settings.textColor.toLowerCase()\n : DEFAULT_TEXT;\n\n const walk = (block: Block, ctx: WalkContext): void => {\n // A block's own opaque backgroundColor is what's behind its content —\n // visit it with that resolved bg so contrast rules compare against the\n // right surface. Falls back to the inherited section/template bg.\n const ownBg = block.styles?.backgroundColor;\n const effectiveBg = isOpaqueHex(ownBg)\n ? (ownBg as string).toLowerCase()\n : ctx.resolvedBackgroundColor;\n const blockCtx: WalkContext =\n effectiveBg === ctx.resolvedBackgroundColor\n ? ctx\n : { ...ctx, resolvedBackgroundColor: effectiveBg };\n\n visit(block, blockCtx);\n\n if (!isSection(block)) {\n return;\n }\n\n block.children.forEach((column, columnIndex) => {\n column.forEach((child) =>\n walk(child, {\n parent: block,\n section: block,\n columnIndex,\n depth: ctx.depth + 1,\n resolvedBackgroundColor: effectiveBg,\n resolvedTextColor: ctx.resolvedTextColor,\n }),\n );\n });\n };\n\n for (const block of content.blocks) {\n walk(block, {\n parent: null,\n section: null,\n columnIndex: null,\n depth: 0,\n resolvedBackgroundColor: rootBg,\n resolvedTextColor: rootTextColor,\n });\n }\n}\n","import type { TemplateContent } from \"@templatical/types\";\nimport type {\n AccessibilityLintOptions,\n LinksLintOptions,\n LintIssue,\n ResolvedOptions,\n Rule,\n RuleHit,\n RuleOverrides,\n Severity,\n StructureLintOptions,\n} from \"./types\";\nimport { DEFAULT_A11Y_THRESHOLDS, DEFAULT_NON_PRODUCTION_HOSTS } from \"./types\";\nimport { walkBlocks } from \"./walk\";\n\nexport type MessageFormatter = (\n locale: string,\n ruleId: string,\n params?: Record<string, string | number>,\n) => string;\n\n/**\n * Walk the tree once, dispatch every block-level rule, then run every\n * template-level rule. Each tool (lintAccessibility, lintStructure, …)\n * wraps this with its own rule list + message formatter and a pre-built\n * `ResolvedOptions` containing that tool's overrides and tool-scoped config.\n */\nexport function runRules(\n content: TemplateContent,\n rules: Rule[],\n opts: ResolvedOptions,\n formatMessage: MessageFormatter,\n): LintIssue[] {\n const issues: LintIssue[] = [];\n\n function buildIssue(\n ruleId: string,\n severity: Exclude<Severity, \"off\">,\n hit: RuleHit,\n ): LintIssue {\n return {\n blockId: hit.blockId,\n ruleId,\n severity,\n message: formatMessage(opts.locale, ruleId, hit.params),\n fix: hit.fix,\n };\n }\n\n walkBlocks(content, (block, ctx) => {\n for (const rule of rules) {\n const sev = opts.severity(rule.meta.id);\n if (sev === \"off\" || !rule.block) continue;\n const hit = rule.block(block, ctx, opts);\n if (hit !== null) {\n issues.push(buildIssue(rule.meta.id, sev, hit));\n }\n }\n });\n\n for (const rule of rules) {\n const sev = opts.severity(rule.meta.id);\n if (sev === \"off\" || !rule.template) continue;\n const hits = rule.template(content, opts);\n for (const hit of hits) {\n issues.push(buildIssue(rule.meta.id, sev, hit));\n }\n }\n\n return issues;\n}\n\n/**\n * Build a `ResolvedOptions` for a given tool. Each tool wrapper passes its\n * own per-tool bag; fields not relevant to the tool fall back to defaults\n * (e.g. `lintStructure` still gets `thresholds` populated, but no structure\n * rule reads them).\n */\nexport function resolveOptions(args: {\n locale: string | undefined;\n rules: Rule[];\n overrides: RuleOverrides | undefined;\n thresholds: Partial<import(\"./types\").LintThresholds> | undefined;\n nonProductionHosts: string[] | undefined;\n}): ResolvedOptions {\n const overrides = args.overrides ?? {};\n const thresholds = {\n ...DEFAULT_A11Y_THRESHOLDS,\n ...(args.thresholds ?? {}),\n };\n const links = {\n nonProductionHosts: args.nonProductionHosts ?? DEFAULT_NON_PRODUCTION_HOSTS,\n };\n const locale = args.locale ?? \"en\";\n const rules = args.rules;\n\n return {\n locale,\n rules: overrides,\n thresholds,\n links,\n severity: (ruleId: string): Severity => {\n const override = overrides[ruleId];\n if (override !== undefined) {\n return override;\n }\n const rule = rules.find((r) => r.meta.id === ruleId);\n return rule?.meta.severity ?? \"warning\";\n },\n };\n}\n\n/**\n * Resolver for the accessibility linter — reads `options.accessibility`.\n */\nexport function resolveAccessibilityOptions(\n locale: string | undefined,\n tool: AccessibilityLintOptions,\n rules: Rule[],\n): ResolvedOptions {\n return resolveOptions({\n locale,\n rules,\n overrides: tool.rules,\n thresholds: tool.thresholds,\n nonProductionHosts: undefined,\n });\n}\n\n/**\n * Resolver for the structure linter — reads `options.structure`.\n */\nexport function resolveStructureOptions(\n locale: string | undefined,\n tool: StructureLintOptions,\n rules: Rule[],\n): ResolvedOptions {\n return resolveOptions({\n locale,\n rules,\n overrides: tool.rules,\n thresholds: undefined,\n nonProductionHosts: undefined,\n });\n}\n\n/**\n * Resolver for the links linter — reads `options.links`.\n */\nexport function resolveLinksOptions(\n locale: string | undefined,\n tool: LinksLintOptions,\n rules: Rule[],\n): ResolvedOptions {\n return resolveOptions({\n locale,\n rules,\n overrides: tool.rules,\n thresholds: undefined,\n nonProductionHosts: tool.nonProductionHosts,\n });\n}\n","import type en from \"./en\";\n\nconst de: typeof en = {\n \"a11y.img-missing-alt\":\n \"Bild ohne Alt-Text. Füge eine kurze Beschreibung hinzu oder markiere das Bild als dekorativ.\",\n \"a11y.img-alt-is-filename\":\n 'Alt-Text sieht wie ein Dateiname aus (\"{alt}\"). Beschreibe stattdessen kurz, was das Bild zeigt.',\n \"a11y.img-alt-too-long\":\n \"Alt-Text ist {length} Zeichen lang; bleibe unter {max}.\",\n \"a11y.img-decorative-needs-empty-alt\":\n \"Dekoratives Bild hat Alt-Text. Entferne den Alt-Text oder hebe die Markierung als dekorativ auf.\",\n \"a11y.img-linked-no-context\":\n \"Verlinktes Bild beschreibt nur das Motiv, nicht das Linkziel. Nenne das Ziel (z. B. „Frühlingssale ansehen“).\",\n \"a11y.heading-empty\":\n \"Überschrift ist leer. Füge Text hinzu oder entferne den Block.\",\n \"a11y.heading-skip-level\":\n \"Überschrift springt von H{from} auf H{to}. Eine Ebene pro Schritt.\",\n \"a11y.heading-multiple-h1\":\n \"E-Mail enthält mehr als eine H1. Verwende H1 nur einmal für die Hauptüberschrift.\",\n \"a11y.link-empty\":\n \"Ein Link in diesem Block hat keinen Text und kein beschriebenes Bild.\",\n \"a11y.link-vague-text\":\n \"Link-Text „{text}“ ist unspezifisch. Beschreibe stattdessen das Ziel.\",\n \"a11y.link-href-empty\":\n \"Ein Link in diesem Block hat ein leeres oder „#“-href.\",\n \"a11y.link-target-blank-no-rel\":\n 'Link öffnet in neuem Tab, aber rel=\"noopener\" fehlt – ergänze es, damit das Ziel nicht auf window.opener zugreifen kann.',\n \"a11y.link-nested-anchor\":\n \"Ein Link liegt innerhalb eines anderen Links. Verschachtelte Anker sind ungültiges HTML und werden von E-Mail-Clients unterschiedlich gerendert – flache einen einzigen Anker daraus.\",\n \"a11y.text-all-caps\":\n \"Längere Texte in Großbuchstaben sind schwerer lesbar. Verwende Groß- und Kleinschreibung.\",\n \"a11y.text-justified\":\n \"Blocksatz dehnt die Wortabstände zu ungleichmäßigen „Flüssen“ aus Leerraum, die für Menschen mit Legasthenie und Sehbeeinträchtigung am schwersten zu verfolgen sind. Verwende linksbündigen Text.\",\n \"a11y.text-low-contrast\":\n \"Überschriftskontrast beträgt {ratio}:1; WCAG AA verlangt mindestens {required}:1.\",\n \"a11y.text-too-small\": \"Text ist {size}px; mindestens {min}px verwenden.\",\n \"a11y.button-vague-label\":\n \"Button-Beschriftung „{text}“ ist unspezifisch. Beschreibe die Aktion.\",\n \"a11y.button-touch-target\":\n \"Button ist etwa {height}px hoch; mindestens {min}px verwenden, um Fehltipper auf Mobilgeräten zu vermeiden.\",\n \"a11y.button-low-contrast\":\n \"Buttontextkontrast beträgt {ratio}:1; WCAG AA verlangt mindestens {required}:1.\",\n \"a11y.missing-preheader\":\n \"Kein Preheader-Text gesetzt. Postfächer zeigen sonst Bruchstücke des ersten Blocks an.\",\n};\n\nexport default de;\n","/**\n * English rule messages. The source of truth — other locales annotate\n * themselves `typeof en` so missing or extra keys fail typecheck.\n *\n * Templates use `{name}` placeholders, interpolated by `formatMessage`.\n */\nconst en = {\n \"a11y.img-missing-alt\":\n \"Image is missing alt text. Add a short description, or mark the image as decorative.\",\n \"a11y.img-alt-is-filename\":\n 'Alt text looks like a filename (\"{alt}\"). Replace with a short description of what the image conveys.',\n \"a11y.img-alt-too-long\":\n \"Alt text is {length} characters; aim for under {max}.\",\n \"a11y.img-decorative-needs-empty-alt\":\n \"Decorative image has alt text. Either clear the alt text or unmark the image as decorative.\",\n \"a11y.img-linked-no-context\":\n \"Linked image alt describes the image but not the link destination. Include where the link goes (e.g. 'Buy spring sale').\",\n \"a11y.heading-empty\": \"Heading is empty. Add text or remove the block.\",\n \"a11y.heading-skip-level\":\n \"Heading jumps from H{from} to H{to}. Step one level at a time.\",\n \"a11y.heading-multiple-h1\":\n \"Email has more than one H1. Use H1 once for the main heading.\",\n \"a11y.link-empty\": \"A link in this block has no text and no described image.\",\n \"a11y.link-vague-text\":\n 'Link text \"{text}\" is vague. Describe the destination instead.',\n \"a11y.link-href-empty\": \"A link in this block has an empty or '#' href.\",\n \"a11y.link-target-blank-no-rel\":\n 'Link opens in a new tab but is missing rel=\"noopener\" — add it to prevent the destination from accessing window.opener.',\n \"a11y.link-nested-anchor\":\n \"A link is nested inside another link. Nested anchors are invalid HTML and clients render them inconsistently — flatten to a single anchor.\",\n \"a11y.text-all-caps\":\n \"Long all-caps text is harder to read for everyone. Use sentence case.\",\n \"a11y.text-justified\":\n \"Justified text stretches word spacing into uneven 'rivers' of white space, which is hardest to track for dyslexic and low-vision readers. Use left-aligned text.\",\n \"a11y.text-low-contrast\":\n \"Heading contrast is {ratio}:1; WCAG AA requires at least {required}:1.\",\n \"a11y.text-too-small\": \"Text is {size}px; aim for at least {min}px.\",\n \"a11y.button-vague-label\":\n 'Button label \"{text}\" is vague. Describe the action.',\n \"a11y.button-touch-target\":\n \"Button is roughly {height}px tall; aim for at least {min}px to avoid mis-taps on mobile.\",\n \"a11y.button-low-contrast\":\n \"Button text contrast is {ratio}:1; WCAG AA requires at least {required}:1.\",\n \"a11y.missing-preheader\":\n \"No preheader text set. Inboxes will fall back to fragments of the first block.\",\n};\n\nexport default en;\n","import en from \"./en\";\n\nexport type MessageMap = typeof en;\nexport type RuleMessageId = keyof MessageMap;\n\n/**\n * Auto-discovered locale registry. Drop a `messages/<lang>.ts` file and\n * it's bundled automatically — same pattern as the editor's i18n.\n *\n * Eager glob: synchronous, all locales bundled into the package. Tiny\n * (a few hundred bytes per locale) so the cost is negligible compared\n * to the lazy-loading overhead.\n */\nconst modules = import.meta.glob<{ default: MessageMap }>(\"./*.ts\", {\n eager: true,\n});\n\nconst MESSAGES: Record<string, MessageMap> = {};\nfor (const path in modules) {\n const match = /\\.\\/([^/]+)\\.ts$/.exec(path);\n if (!match) continue;\n const locale = match[1];\n if (locale === \"index\") continue;\n MESSAGES[locale] = modules[path].default;\n}\n\nexport const SUPPORTED_MESSAGE_LOCALES = Object.keys(MESSAGES);\n\nexport function getMessages(locale: string): MessageMap {\n const base = locale.split(\"-\")[0]?.toLowerCase() ?? \"en\";\n return MESSAGES[base] ?? MESSAGES.en ?? en;\n}\n\n/**\n * Resolve a localized message for a rule. `params` interpolate `{name}`\n * placeholders. Falls back to English when the locale doesn't ship the\n * key (shouldn't happen — the parity test enforces it).\n */\nexport function formatMessage(\n locale: string,\n ruleId: RuleMessageId,\n params?: Record<string, string | number>,\n): string {\n const map = getMessages(locale);\n const template = map[ruleId] ?? en[ruleId];\n if (!params) return template;\n return template.replace(/\\{(\\w+)\\}/g, (_, key: string) => {\n const value = params[key];\n return value === undefined ? `{${key}}` : String(value);\n });\n}\n","import { isImage } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.img-missing-alt\",\n severity: \"error\",\n};\n\nexport const imgMissingAlt: Rule = {\n meta,\n block(block) {\n if (!isImage(block)) return null;\n if (block.decorative === true) return null;\n const alt = block.alt?.trim() ?? \"\";\n if (alt !== \"\") return null;\n if ((block.src ?? \"\").trim() === \"\") return null;\n return { blockId: block.id };\n },\n};\n","import { isImage } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.img-alt-is-filename\",\n severity: \"warning\",\n};\n\nconst FILENAME_PATTERNS: RegExp[] = [\n /\\.(jpe?g|png|gif|webp|svg)$/i,\n /^IMG[_-]?\\d+/i,\n /^Untitled/i,\n /^Screen[\\s_-]?Shot/i,\n /^DSC[_-]?\\d+/i,\n];\n\nexport const imgAltIsFilename: Rule = {\n meta,\n block(block) {\n if (!isImage(block) || block.decorative === true) return null;\n const alt = block.alt?.trim() ?? \"\";\n if (alt === \"\") return null;\n if (!FILENAME_PATTERNS.some((re) => re.test(alt))) return null;\n\n return {\n blockId: block.id,\n params: { alt },\n };\n },\n};\n","import { isImage } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.img-alt-too-long\",\n severity: \"warning\",\n};\n\nexport const imgAltTooLong: Rule = {\n meta,\n block(block, _ctx, opts) {\n if (!isImage(block) || block.decorative === true) return null;\n const alt = block.alt ?? \"\";\n if (alt.length <= opts.thresholds.altMaxLength) return null;\n return {\n blockId: block.id,\n params: { length: alt.length, max: opts.thresholds.altMaxLength },\n };\n },\n};\n","import { isImage } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.img-decorative-needs-empty-alt\",\n severity: \"info\",\n};\n\nexport const imgDecorativeNeedsEmptyAlt: Rule = {\n meta,\n block(block) {\n if (!isImage(block)) return null;\n if (block.decorative !== true) return null;\n if ((block.alt ?? \"\").trim() === \"\") return null;\n return {\n blockId: block.id,\n fix: {\n description: \"Clear alt text\",\n apply: (ctx) => ctx.updateBlock(block.id, { alt: \"\" }),\n },\n };\n },\n};\n","import type en from \"./en\";\n\nconst de: typeof en = {\n vagueLinkText: [\n \"hier klicken\",\n \"hier\",\n \"mehr lesen\",\n \"mehr\",\n \"weiter\",\n \"weiterlesen\",\n \"siehe mehr\",\n \"dies\",\n \"dieser link\",\n \"link\",\n \"klick\",\n ],\n vagueButtonLabels: [\n \"hier klicken\",\n \"klicken\",\n \"senden\",\n \"los\",\n \"ok\",\n \"okay\",\n \"ja\",\n \"nein\",\n ],\n linkedImageActionHints: [\n \"kaufen\",\n \"shoppen\",\n \"ansehen\",\n \"lesen\",\n \"lernen\",\n \"öffnen\",\n \"los\",\n \"sehen\",\n \"entdecken\",\n \"erkunden\",\n \"stöbern\",\n \"herunterladen\",\n \"holen\",\n \"abholen\",\n \"einlösen\",\n \"anschauen\",\n \"jetzt\",\n ],\n};\n\nexport default de;\n","/**\n * English vague-text dictionaries. Treated as the source of truth — other\n * locales annotate themselves `typeof en` so missing/extra phrases fail\n * typecheck.\n *\n * Phrases are matched case-insensitively against trimmed text content.\n */\nconst en = {\n vagueLinkText: [\n \"click here\",\n \"here\",\n \"read more\",\n \"more\",\n \"learn more\",\n \"see more\",\n \"this\",\n \"this link\",\n \"link\",\n \"click\",\n ],\n vagueButtonLabels: [\n \"click here\",\n \"click\",\n \"submit\",\n \"go\",\n \"ok\",\n \"okay\",\n \"yes\",\n \"no\",\n ],\n /**\n * Action verbs that signal a linked image's alt describes the link\n * destination, not just the visual subject. Used by `img-linked-no-context`.\n * Stored lowercase; tokenized matching is case-insensitive.\n */\n linkedImageActionHints: [\n \"buy\",\n \"shop\",\n \"view\",\n \"read\",\n \"learn\",\n \"open\",\n \"go\",\n \"see\",\n \"explore\",\n \"discover\",\n \"browse\",\n \"download\",\n \"get\",\n \"claim\",\n \"redeem\",\n \"watch\",\n ],\n};\n\nexport default en;\n","import en from \"./en\";\n\nexport type Dictionary = typeof en;\n\n/**\n * Auto-discovered locale registry. Drop a `dictionaries/<lang>.ts` file\n * and it's bundled automatically — same pattern as the editor's i18n\n * and the sibling `messages/` registry.\n *\n * Eager glob: synchronous, all locales bundled into the package. Tiny\n * (a few hundred bytes per locale) so the cost is negligible.\n */\nconst modules = import.meta.glob<{ default: Dictionary }>(\"./*.ts\", {\n eager: true,\n});\n\nconst DICTIONARIES: Record<string, Dictionary> = {};\nfor (const path in modules) {\n const match = /\\.\\/([^/]+)\\.ts$/.exec(path);\n if (!match) continue;\n const locale = match[1];\n if (locale === \"index\") continue;\n DICTIONARIES[locale] = modules[path].default;\n}\n\n/**\n * Returns a dictionary that unions every registered locale. Vague phrases\n * are universally vague — a German-locale email with an English \"Click here\"\n * CTA, or an English email with a French \"cliquez ici\", is still a vague\n * CTA, so the rule must detect across languages regardless of editor locale.\n *\n * The `locale` argument is accepted for API symmetry and future use (e.g.\n * weighted matching) but currently doesn't change the returned set.\n */\nexport function getDictionary(_locale: string): Dictionary {\n return UNIONED_DICTIONARY;\n}\n\nfunction unionAll(pick: (d: Dictionary) => readonly string[]): string[] {\n const set = new Set<string>();\n for (const dict of Object.values(DICTIONARIES)) {\n for (const phrase of pick(dict)) set.add(phrase);\n }\n return Array.from(set);\n}\n\nconst UNIONED_DICTIONARY: Dictionary = {\n vagueLinkText: unionAll((d) => d.vagueLinkText),\n vagueButtonLabels: unionAll((d) => d.vagueButtonLabels),\n linkedImageActionHints: unionAll((d) => d.linkedImageActionHints),\n};\n\nexport const SUPPORTED_DICTIONARY_LOCALES = Object.keys(DICTIONARIES);\n\n/**\n * Normalize text for dictionary matching: lowercase, collapse whitespace,\n * strip leading/trailing non-alphanumeric characters (punctuation, arrows,\n * emoji, decorative symbols). \"Click here!\", \"→ click here\", \"click here?\"\n * all collapse to \"click here\" so the dictionary's plain phrase matches.\n */\nexport function normalizeForMatch(input: string): string {\n return input\n .toLowerCase()\n .replace(/\\s+/g, \" \")\n .replace(/^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu, \"\")\n .trim();\n}\n","import { isImage } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\nimport { getDictionary } from \"../dictionaries\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.img-linked-no-context\",\n severity: \"warning\",\n};\n\nexport const imgLinkedNoContext: Rule = {\n meta,\n block(block, _ctx, opts) {\n if (!isImage(block) || block.decorative === true) return null;\n if (!block.linkUrl || block.linkUrl.trim() === \"\") return null;\n const alt = (block.alt ?? \"\").trim();\n if (alt === \"\") return null;\n const tokens = alt\n .toLocaleLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean);\n const hints = getDictionary(opts.locale).linkedImageActionHints;\n if (tokens.some((token) => hints.includes(token))) return null;\n return { blockId: block.id };\n },\n};\n","import { Parser } from \"htmlparser2\";\n\nexport interface AnchorInfo {\n href: string;\n text: string;\n target: string | null;\n rel: string | null;\n /** True if the anchor wraps an image with non-empty alt. */\n hasImageWithAlt: boolean;\n}\n\n/**\n * Extract every anchor from a TipTap-style HTML fragment. Used by\n * link-* rules. Doesn't try to be a full DOM — only the data the rules\n * need.\n *\n * Nested `<a>` is invalid HTML; htmlparser2 follows the HTML5 spec and\n * emits an implicit `</a>` when a second `<a>` opens, so anchors are\n * effectively flat siblings. We mirror that with a single in-flight\n * anchor (no stack); a defensive finalize-on-reopen handles the\n * theoretical case where the parser ever stops emitting the implicit\n * close. Detecting nested-anchor markup as its own concern lives in\n * the `a11y.link-nested-anchor` rule, which inspects the raw input\n * before this normalization.\n */\nexport function extractAnchors(html: string): AnchorInfo[] {\n const anchors: AnchorInfo[] = [];\n let current: AnchorInfo | null = null;\n let buffer = \"\";\n\n const finalize = () => {\n if (current === null) return;\n current.text = buffer.trim();\n anchors.push(current);\n current = null;\n buffer = \"\";\n };\n\n const parser = new Parser({\n onopentag(name, attribs) {\n if (name === \"a\") {\n finalize();\n current = {\n href: attribs.href ?? \"\",\n text: \"\",\n target: attribs.target ?? null,\n rel: attribs.rel ?? null,\n hasImageWithAlt: false,\n };\n return;\n }\n\n if (name === \"img\" && current !== null) {\n const alt = (attribs.alt ?? \"\").trim();\n if (alt !== \"\") {\n current.hasImageWithAlt = true;\n }\n }\n },\n ontext(text) {\n if (current !== null) {\n buffer += text;\n }\n },\n onclosetag(name) {\n if (name === \"a\") {\n finalize();\n }\n },\n });\n\n parser.write(html);\n parser.end();\n finalize();\n\n return anchors;\n}\n\n/**\n * Whether the raw HTML contains an `<a>` opened inside another open\n * `<a>` — invalid markup that htmlparser2 silently normalizes by\n * emitting an implicit `</a>` before the inner open. `extractAnchors`\n * runs against the normalized parse and therefore can't distinguish\n * nested-from-sibling input; this helper inspects the raw text so the\n * `a11y.link-nested-anchor` rule can flag the structural problem.\n *\n * Tokenization here ignores anchor-like tokens inside HTML comments,\n * which is enough for TipTap email-template HTML. CDATA, `<script>`,\n * and attribute-value occurrences aren't expected in this surface.\n */\nexport function hasNestedAnchors(html: string): boolean {\n const stripped = stripHtmlComments(html);\n const tokens = stripped.matchAll(/<\\/?a\\b[^<>]*>/gi);\n let depth = 0;\n for (const match of tokens) {\n if (match[0].startsWith(\"</\")) {\n if (depth > 0) depth--;\n continue;\n }\n if (depth > 0) return true;\n depth++;\n }\n return false;\n}\n\n/**\n * Linear-time `<!-- … -->` stripper. Replaces the original\n * `replace(/<!--[\\s\\S]*?-->/g, \"\")`, which had two related problems:\n *\n * - Polynomial ReDoS: each `<!--` start re-scanned the rest of the\n * input for `-->`, costing O(n²) over inputs like `<!--<!--<!--…`.\n * - Incomplete sanitization: an unterminated `<!--` left the literal\n * `<!--` in the output, so downstream rendering could still parse it\n * as an open HTML comment and swallow the rest of the document.\n *\n * The scanner discards an unterminated comment entirely — there is no\n * way to recover meaningful content after a stray `<!--`, and leaving\n * it in would re-introduce the sanitizer gap.\n */\nfunction stripHtmlComments(html: string): string {\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const start = html.indexOf(\"<!--\", i);\n if (start === -1) {\n out += html.substring(i);\n break;\n }\n out += html.substring(i, start);\n const end = html.indexOf(\"-->\", start + 4);\n if (end === -1) {\n // Unterminated comment — drop everything from this point onward.\n break;\n }\n i = end + 3;\n }\n return out;\n}\n\n/**\n * Strip tags and return the visible text content of an HTML fragment.\n * Used by heading-empty and other text-presence rules.\n */\nexport function extractText(html: string): string {\n let text = \"\";\n const parser = new Parser({\n ontext(chunk) {\n text += chunk;\n },\n });\n parser.write(html);\n parser.end();\n return text.trim();\n}\n","import { isTitle } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\nimport { extractText } from \"../../html-utils\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.heading-empty\",\n severity: \"error\",\n};\n\nexport const headingEmpty: Rule = {\n meta,\n block(block) {\n if (!isTitle(block)) return null;\n const text = extractText(block.content ?? \"\");\n if (text !== \"\") return null;\n return { blockId: block.id };\n },\n};\n","import { isTitle, isSection } from \"@templatical/types\";\nimport type { Block, TitleBlock, TemplateContent } from \"@templatical/types\";\nimport type { Rule, RuleHit, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.heading-skip-level\",\n severity: \"error\",\n};\n\nfunction collectTitles(blocks: Block[], out: TitleBlock[]): void {\n for (const block of blocks) {\n if (isTitle(block)) {\n out.push(block);\n continue;\n }\n if (isSection(block)) {\n for (const column of block.children) {\n collectTitles(column, out);\n }\n }\n }\n}\n\nexport const headingSkipLevel: Rule = {\n meta,\n template(content: TemplateContent) {\n const titles: TitleBlock[] = [];\n collectTitles(content.blocks, titles);\n\n const hits: RuleHit[] = [];\n let lastLevel = 0;\n\n for (const title of titles) {\n if (lastLevel !== 0 && title.level > lastLevel + 1) {\n hits.push({\n blockId: title.id,\n params: { from: lastLevel, to: title.level },\n });\n }\n lastLevel = title.level;\n }\n\n return hits;\n },\n};\n","import { isTitle, isSection } from \"@templatical/types\";\nimport type { Block, TitleBlock, TemplateContent } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.heading-multiple-h1\",\n severity: \"warning\",\n};\n\nfunction collectTitles(blocks: Block[], out: TitleBlock[]): void {\n for (const block of blocks) {\n if (isTitle(block)) {\n out.push(block);\n continue;\n }\n if (isSection(block)) {\n for (const column of block.children) {\n collectTitles(column, out);\n }\n }\n }\n}\n\nexport const headingMultipleH1: Rule = {\n meta,\n template(content: TemplateContent) {\n const titles: TitleBlock[] = [];\n collectTitles(content.blocks, titles);\n const h1s = titles.filter((t) => t.level === 1);\n if (h1s.length <= 1) return [];\n return h1s.slice(1).map((title) => ({ blockId: title.id }));\n },\n};\n","import { isParagraph, isTitle } from \"@templatical/types\";\nimport type { Block } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\nimport { extractAnchors } from \"../../html-utils\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.link-empty\",\n severity: \"error\",\n};\n\nfunction getHtml(block: Block): string | null {\n if (isParagraph(block) || isTitle(block)) return block.content;\n return null;\n}\n\nexport const linkEmpty: Rule = {\n meta,\n block(block) {\n const html = getHtml(block);\n if (html === null) return null;\n\n const anchors = extractAnchors(html);\n const offender = anchors.find(\n (anchor) => anchor.text === \"\" && !anchor.hasImageWithAlt,\n );\n if (!offender) return null;\n\n return { blockId: block.id };\n },\n};\n","import { isParagraph, isTitle } from \"@templatical/types\";\nimport type { Block } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\nimport { extractAnchors } from \"../../html-utils\";\nimport { getDictionary, normalizeForMatch } from \"../dictionaries\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.link-vague-text\",\n severity: \"warning\",\n};\n\nfunction getHtml(block: Block): string | null {\n if (isParagraph(block) || isTitle(block)) return block.content;\n return null;\n}\n\nexport const linkVagueText: Rule = {\n meta,\n block(block, _ctx, opts) {\n const html = getHtml(block);\n if (html === null) return null;\n\n const phrases = getDictionary(opts.locale).vagueLinkText;\n const anchors = extractAnchors(html);\n const offender = anchors.find((a) => {\n const text = normalizeForMatch(a.text);\n return text !== \"\" && phrases.includes(text);\n });\n if (!offender) return null;\n\n return { blockId: block.id, params: { text: offender.text } };\n },\n};\n","import { isParagraph, isTitle } from \"@templatical/types\";\nimport type { Block } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\nimport { extractAnchors } from \"../../html-utils\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.link-href-empty\",\n severity: \"error\",\n};\n\nfunction getHtml(block: Block): string | null {\n if (isParagraph(block) || isTitle(block)) return block.content;\n return null;\n}\n\nexport const linkHrefEmpty: Rule = {\n meta,\n block(block) {\n const html = getHtml(block);\n if (html === null) return null;\n const anchors = extractAnchors(html);\n const offender = anchors.find((a) => {\n const href = a.href.trim();\n return href === \"\" || href === \"#\";\n });\n if (!offender) return null;\n return { blockId: block.id };\n },\n};\n","import { isParagraph, isTitle } from \"@templatical/types\";\nimport type { Block } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\nimport { extractAnchors } from \"../../html-utils\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.link-target-blank-no-rel\",\n severity: \"warning\",\n};\n\nfunction getHtml(block: Block): string | null {\n if (isParagraph(block) || isTitle(block)) return block.content;\n return null;\n}\n\nfunction hasSafeRel(rel: string | null): boolean {\n if (rel === null) return false;\n const tokens = rel.toLowerCase().split(/\\s+/);\n return tokens.includes(\"noopener\") || tokens.includes(\"noreferrer\");\n}\n\nexport const linkTargetBlankNoRel: Rule = {\n meta,\n block(block) {\n const html = getHtml(block);\n if (html === null) return null;\n const anchors = extractAnchors(html);\n const offender = anchors.find(\n (a) => a.target === \"_blank\" && !hasSafeRel(a.rel),\n );\n if (!offender) return null;\n\n return {\n blockId: block.id,\n fix: {\n description: 'Add rel=\"noopener\"',\n apply: (ctx) => {\n if (!isParagraph(block) && !isTitle(block)) return;\n const updated = addNoopenerToTargetBlank(block.content ?? \"\");\n ctx.updateBlock(block.id, { content: updated } as Partial<Block>);\n },\n },\n };\n },\n};\n\ninterface ParsedAttr {\n raw: string;\n name: string;\n value: string | null;\n /** Start offset of `raw` within the parent attrs string. */\n start: number;\n}\n\nconst ATTR_RE =\n /([^\\s\"'>/=]+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+)))?/g;\n\nfunction parseAttrs(attrs: string): ParsedAttr[] {\n const parsed: ParsedAttr[] = [];\n const re = new RegExp(ATTR_RE.source, ATTR_RE.flags);\n let match: RegExpExecArray | null;\n while ((match = re.exec(attrs)) !== null) {\n const value = match[2] ?? match[3] ?? match[4] ?? null;\n parsed.push({\n raw: match[0],\n name: match[1],\n value,\n start: match.index,\n });\n }\n return parsed;\n}\n\nfunction hasUnsafeTargetBlank(parsed: ParsedAttr[]): boolean {\n return parsed.some(\n (a) =>\n a.name.toLowerCase() === \"target\" &&\n a.value !== null &&\n a.value.toLowerCase() === \"_blank\",\n );\n}\n\nfunction addNoopenerToTargetBlank(html: string): string {\n return html.replace(/<a\\b([^>]*)>/gi, (match, attrs: string) => {\n const parsed = parseAttrs(attrs);\n if (!hasUnsafeTargetBlank(parsed)) return match;\n\n const relAttr = parsed.find((a) => a.name.toLowerCase() === \"rel\");\n if (relAttr) {\n const tokens = (relAttr.value ?? \"\").toLowerCase().split(/\\s+/);\n if (tokens.includes(\"noopener\") || tokens.includes(\"noreferrer\")) {\n return match;\n }\n const newRel = `${relAttr.value ?? \"\"} noopener`.trim();\n const before = attrs.slice(0, relAttr.start);\n const after = attrs.slice(relAttr.start + relAttr.raw.length);\n return `<a${before}rel=\"${newRel}\"${after}>`;\n }\n return `<a${attrs} rel=\"noopener\">`;\n });\n}\n","import { isParagraph, isTitle } from \"@templatical/types\";\nimport type { Block } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\nimport { hasNestedAnchors } from \"../../html-utils\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.link-nested-anchor\",\n severity: \"error\",\n};\n\nfunction getHtml(block: Block): string | null {\n if (isParagraph(block) || isTitle(block)) return block.content;\n return null;\n}\n\nexport const linkNestedAnchor: Rule = {\n meta,\n block(block) {\n const html = getHtml(block);\n if (html === null) return null;\n if (!hasNestedAnchors(html)) return null;\n return { blockId: block.id };\n },\n};\n","import { isParagraph, isTitle } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\nimport { extractText } from \"../../html-utils\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.text-all-caps\",\n severity: \"warning\",\n};\n\nexport const textAllCaps: Rule = {\n meta,\n block(block, _ctx, opts) {\n if (!isParagraph(block) && !isTitle(block)) return null;\n const text = extractText(block.content ?? \"\");\n const letters = text.replace(/[^\\p{L}]/gu, \"\");\n if (letters.length < opts.thresholds.allCapsMinLength) return null;\n if (letters !== letters.toLocaleUpperCase()) return null;\n return { blockId: block.id };\n },\n};\n","import { isParagraph } from \"@templatical/types\";\nimport type { Block } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.text-justified\",\n severity: \"warning\",\n};\n\n/**\n * Matches a `text-align` declaration and captures its value. Anchored on\n * the property name with a `(^|;)` prefix so it can only ever match a\n * declaration boundary.\n *\n * That anchor is the whole point: `justify-content: center` is common in\n * the `html` block's flexbox markup, and a rule that searched for the\n * bare token `justify` would flag every one of them. Matching the\n * property — never the value alone — is what makes the rule specific.\n *\n * Whitespace is permitted around `:` and the value because imported\n * templates aren't normalized; content that round-trips through TipTap\n * is always `text-align: <value>`, but content loaded via `init()` or an\n * importer can carry any spacing or casing.\n */\nconst TEXT_ALIGN_DECL = /(^|;)\\s*text-align\\s*:\\s*([^;]*)/i;\n\n/** Reads the `style` attribute value out of every tag in a fragment. */\nconst STYLE_ATTR = /\\sstyle\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/gi;\n\nfunction styleValues(html: string): string[] {\n const values: string[] = [];\n const re = new RegExp(STYLE_ATTR.source, STYLE_ATTR.flags);\n let match: RegExpExecArray | null;\n while ((match = re.exec(html)) !== null) {\n values.push(match[1] ?? match[2] ?? \"\");\n }\n return values;\n}\n\nfunction isJustified(style: string): boolean {\n const match = TEXT_ALIGN_DECL.exec(style);\n if (match === null) return false;\n return match[2].trim().toLowerCase() === \"justify\";\n}\n\n/**\n * Drop the `text-align` declaration from one style attribute value,\n * leaving every sibling declaration untouched.\n *\n * TipTap merges each extension's `renderHTML` contribution into a single\n * `style` attribute, so a justified paragraph that also carries a line\n * height is `style=\"text-align: justify; line-height: 1.8\"`. Removing the\n * attribute wholesale would silently discard the line height.\n */\nfunction removeTextAlign(style: string): string {\n return style\n .split(\";\")\n .filter((decl) => !/^\\s*text-align\\s*:/i.test(decl))\n .map((decl) => decl.trim())\n .filter((decl) => decl !== \"\")\n .join(\"; \");\n}\n\n/**\n * Rewrite every justified tag in the fragment, dropping the `style`\n * attribute entirely when `text-align` was its only declaration.\n *\n * Unsetting rather than forcing `left` is deliberate: an absent\n * declaration is how TipTap represents \"no alignment\" (its `renderHTML`\n * emits nothing when the attribute is null), so the paragraph goes back\n * to inheriting the document default — which is also the correct result\n * for right-to-left content, where a hardcoded `left` would be wrong.\n */\nfunction unjustify(html: string): string {\n return html.replace(\n /\\sstyle\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/gi,\n (match, double: string | undefined, single: string | undefined) => {\n const style = double ?? single ?? \"\";\n if (!isJustified(style)) return match;\n const remaining = removeTextAlign(style);\n return remaining === \"\" ? \"\" : ` style=\"${remaining}\"`;\n },\n );\n}\n\nexport const textJustified: Rule = {\n meta,\n block(block) {\n if (!isParagraph(block)) return null;\n const html = block.content ?? \"\";\n if (!styleValues(html).some(isJustified)) return null;\n\n return {\n blockId: block.id,\n fix: {\n description: \"Remove justified alignment\",\n apply: (ctx) => {\n if (!isParagraph(block)) return;\n ctx.updateBlock(block.id, {\n content: unjustify(block.content ?? \"\"),\n } as Partial<Block>);\n },\n },\n };\n },\n};\n","import { isTitle } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\nimport { getContrastRatio, isOpaqueHex } from \"../../contrast\";\nimport { HEADING_LEVEL_FONT_SIZE } from \"@templatical/types\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.text-low-contrast\",\n severity: \"error\",\n};\n\nexport const textLowContrast: Rule = {\n meta,\n block(block, ctx) {\n if (!isTitle(block)) return null;\n // Unset title color inherits the document text color, so check the\n // effective color against the background.\n const color = block.color ?? ctx.resolvedTextColor;\n if (!isOpaqueHex(color) || !isOpaqueHex(ctx.resolvedBackgroundColor)) {\n return null;\n }\n const fontSize = HEADING_LEVEL_FONT_SIZE[block.level];\n // WCAG large text = 18pt (~24px). Headings have no structured bold\n // flag in this codebase (TipTap stores it inline), so we conservatively\n // skip the 14pt-bold (~18.66px) relaxation and apply the px threshold.\n const required = fontSize >= 24 ? 3 : 4.5;\n const ratio = getContrastRatio(color, ctx.resolvedBackgroundColor);\n if (Number.isNaN(ratio) || ratio >= required) return null;\n return {\n blockId: block.id,\n params: { ratio: ratio.toFixed(2), required },\n };\n },\n};\n","import { isMenu, isTable } from \"@templatical/types\";\nimport type { Block } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.text-too-small\",\n severity: \"warning\",\n};\n\nfunction getFontSize(block: Block): number | null {\n if (isMenu(block) || isTable(block)) return block.fontSize;\n return null;\n}\n\nexport const textTooSmall: Rule = {\n meta,\n block(block, _ctx, opts) {\n const fontSize = getFontSize(block);\n if (fontSize === null) return null;\n if (fontSize >= opts.thresholds.minFontSize) return null;\n return {\n blockId: block.id,\n params: { size: fontSize, min: opts.thresholds.minFontSize },\n };\n },\n};\n","import { isButton } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\nimport { getDictionary, normalizeForMatch } from \"../dictionaries\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.button-vague-label\",\n severity: \"warning\",\n};\n\nexport const buttonVagueLabel: Rule = {\n meta,\n block(block, _ctx, opts) {\n if (!isButton(block)) return null;\n const text = normalizeForMatch(block.text ?? \"\");\n if (text === \"\") return null;\n const phrases = getDictionary(opts.locale).vagueButtonLabels;\n if (!phrases.includes(text)) return null;\n return { blockId: block.id, params: { text: block.text } };\n },\n};\n","import { isButton } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.button-touch-target\",\n severity: \"warning\",\n};\n\nexport const buttonTouchTarget: Rule = {\n meta,\n block(block, _ctx, opts) {\n if (!isButton(block)) return null;\n const padding = block.buttonPadding;\n if (!padding) return null;\n const estimatedHeight = block.fontSize * 1.4 + padding.top + padding.bottom;\n if (estimatedHeight >= opts.thresholds.minTouchTargetPx) return null;\n return {\n blockId: block.id,\n params: {\n height: Math.round(estimatedHeight),\n min: opts.thresholds.minTouchTargetPx,\n },\n };\n },\n};\n","import { isButton } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\nimport { getContrastRatio } from \"../../contrast\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.button-low-contrast\",\n severity: \"error\",\n};\n\nexport const buttonLowContrast: Rule = {\n meta,\n block(block) {\n if (!isButton(block)) return null;\n const ratio = getContrastRatio(block.textColor, block.backgroundColor);\n if (Number.isNaN(ratio)) return null;\n // WCAG large text = 18pt (~24px). Mirrors the heading rule's threshold.\n const required = block.fontSize >= 24 ? 3 : 4.5;\n if (ratio >= required) return null;\n return {\n blockId: block.id,\n params: { ratio: ratio.toFixed(2), required },\n };\n },\n};\n","import type { Rule, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"a11y.missing-preheader\",\n severity: \"info\",\n};\n\nexport const missingPreheader: Rule = {\n meta,\n template(content) {\n const text = content.settings.preheaderText?.trim() ?? \"\";\n if (text !== \"\") return [];\n return [{ blockId: null }];\n },\n};\n","import type { TemplateContent } from \"@templatical/types\";\nimport type { LintIssue, LintOptions, Rule } from \"../types\";\nimport { resolveAccessibilityOptions, runRules } from \"../run-rules\";\nimport { formatMessage, type RuleMessageId } from \"./messages\";\nimport { imgMissingAlt } from \"./rules/img-missing-alt\";\nimport { imgAltIsFilename } from \"./rules/img-alt-is-filename\";\nimport { imgAltTooLong } from \"./rules/img-alt-too-long\";\nimport { imgDecorativeNeedsEmptyAlt } from \"./rules/img-decorative-needs-empty-alt\";\nimport { imgLinkedNoContext } from \"./rules/img-linked-no-context\";\nimport { headingEmpty } from \"./rules/heading-empty\";\nimport { headingSkipLevel } from \"./rules/heading-skip-level\";\nimport { headingMultipleH1 } from \"./rules/heading-multiple-h1\";\nimport { linkEmpty } from \"./rules/link-empty\";\nimport { linkVagueText } from \"./rules/link-vague-text\";\nimport { linkHrefEmpty } from \"./rules/link-href-empty\";\nimport { linkTargetBlankNoRel } from \"./rules/link-target-blank-no-rel\";\nimport { linkNestedAnchor } from \"./rules/link-nested-anchor\";\nimport { textAllCaps } from \"./rules/text-all-caps\";\nimport { textJustified } from \"./rules/text-justified\";\nimport { textLowContrast } from \"./rules/text-low-contrast\";\nimport { textTooSmall } from \"./rules/text-too-small\";\nimport { buttonVagueLabel } from \"./rules/button-vague-label\";\nimport { buttonTouchTarget } from \"./rules/button-touch-target\";\nimport { buttonLowContrast } from \"./rules/button-low-contrast\";\nimport { missingPreheader } from \"./rules/missing-preheader\";\n\nexport const ACCESSIBILITY_RULES: Rule[] = [\n imgMissingAlt,\n imgAltIsFilename,\n imgAltTooLong,\n imgDecorativeNeedsEmptyAlt,\n imgLinkedNoContext,\n headingEmpty,\n headingSkipLevel,\n headingMultipleH1,\n linkEmpty,\n linkVagueText,\n linkHrefEmpty,\n linkTargetBlankNoRel,\n linkNestedAnchor,\n textAllCaps,\n textJustified,\n textLowContrast,\n textTooSmall,\n buttonVagueLabel,\n buttonTouchTarget,\n buttonLowContrast,\n missingPreheader,\n];\n\nexport function lintAccessibility(\n content: TemplateContent,\n options: LintOptions = {},\n): LintIssue[] {\n if (options.disabled === true || options.accessibility === false) return [];\n const tool = options.accessibility ?? {};\n const resolved = resolveAccessibilityOptions(\n options.locale,\n tool,\n ACCESSIBILITY_RULES,\n );\n return runRules(\n content,\n ACCESSIBILITY_RULES,\n resolved,\n (locale, id, params) => formatMessage(locale, id as RuleMessageId, params),\n );\n}\n","import type en from \"./en\";\n\nconst de: typeof en = {\n \"structure.duplicate-block-id\":\n \"Block-ID erscheint {count}-mal im Baum. Jeder Block muss eine eindeutige ID haben.\",\n \"structure.section-column-mismatch\":\n 'Sektion verwendet Layout „{layout}\" (erwartet {expected} Spalten), hat aber {actual}. Deutet auf beschädigten Zustand hin.',\n \"structure.nested-section\":\n \"Sektion ist in einer anderen Sektion verschachtelt. Sektionen können nicht verschachtelt werden – der Renderer wird sich falsch verhalten.\",\n \"structure.empty-section\":\n \"Sektion enthält keine Blöcke. Entferne sie oder füge Inhalt hinzu.\",\n \"structure.empty-column\":\n \"Spalte {columnIndex} dieser Sektion ist leer. Füge Inhalt hinzu oder reduziere die Spaltenanzahl.\",\n};\n\nexport default de;\n","/**\n * English structure-rule messages. The source of truth — other locales\n * annotate themselves `typeof en` so missing or extra keys fail typecheck.\n *\n * Templates use `{name}` placeholders, interpolated by `formatMessage`.\n */\nconst en = {\n \"structure.duplicate-block-id\":\n \"Block id appears {count} times in the tree. Each block must have a unique id.\",\n \"structure.section-column-mismatch\":\n 'Section uses layout \"{layout}\" (expects {expected} columns) but has {actual}. Indicates corrupted state.',\n \"structure.nested-section\":\n \"Section is nested inside another section. Sections cannot nest — the renderer will misbehave.\",\n \"structure.empty-section\": \"Section has no blocks. Remove it or add content.\",\n \"structure.empty-column\":\n \"Column {columnIndex} of this section is empty. Add content or reduce the column count.\",\n};\n\nexport default en;\n","import en from \"./en\";\n\nexport type StructureMessageMap = typeof en;\nexport type StructureRuleMessageId = keyof StructureMessageMap;\n\nconst modules = import.meta.glob<{ default: StructureMessageMap }>(\"./*.ts\", {\n eager: true,\n});\n\nconst MESSAGES: Record<string, StructureMessageMap> = {};\nfor (const path in modules) {\n const match = /\\.\\/([^/]+)\\.ts$/.exec(path);\n if (!match) continue;\n const locale = match[1];\n if (locale === \"index\") continue;\n MESSAGES[locale] = modules[path].default;\n}\n\nexport const SUPPORTED_STRUCTURE_MESSAGE_LOCALES = Object.keys(MESSAGES);\n\nexport function getStructureMessages(locale: string): StructureMessageMap {\n const base = locale.split(\"-\")[0]?.toLowerCase() ?? \"en\";\n return MESSAGES[base] ?? MESSAGES.en ?? en;\n}\n\nexport function formatStructureMessage(\n locale: string,\n ruleId: StructureRuleMessageId,\n params?: Record<string, string | number>,\n): string {\n const map = getStructureMessages(locale);\n const template = map[ruleId] ?? en[ruleId];\n if (!params) return template;\n return template.replace(/\\{(\\w+)\\}/g, (_, key: string) => {\n const value = params[key];\n return value === undefined ? `{${key}}` : String(value);\n });\n}\n","import type { Block, SectionBlock, TemplateContent } from \"@templatical/types\";\nimport { isSection } from \"@templatical/types\";\nimport type { Rule, RuleHit, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"structure.duplicate-block-id\",\n severity: \"error\",\n};\n\nfunction collectIds(blocks: Block[], counts: Map<string, number>): void {\n for (const block of blocks) {\n counts.set(block.id, (counts.get(block.id) ?? 0) + 1);\n if (isSection(block)) {\n for (const column of (block as SectionBlock).children) {\n collectIds(column, counts);\n }\n }\n }\n}\n\nexport const duplicateBlockId: Rule = {\n meta,\n template(content: TemplateContent): RuleHit[] {\n const counts = new Map<string, number>();\n collectIds(content.blocks, counts);\n\n const hits: RuleHit[] = [];\n for (const [id, count] of counts) {\n if (count > 1) {\n hits.push({ blockId: id, params: { count } });\n }\n }\n return hits;\n },\n};\n","import type { Block, SectionBlock, TemplateContent } from \"@templatical/types\";\nimport { isSection } from \"@templatical/types\";\nimport type { Rule, RuleHit, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"structure.empty-column\",\n severity: \"warning\",\n};\n\nfunction findEmptyColumns(blocks: Block[], hits: RuleHit[]): void {\n for (const block of blocks) {\n if (!isSection(block)) continue;\n const section = block as SectionBlock;\n if (section.children.length > 1) {\n section.children.forEach((column, columnIndex) => {\n if (column.length === 0) {\n hits.push({\n blockId: section.id,\n params: { columnIndex: columnIndex + 1 },\n });\n }\n });\n }\n for (const column of section.children) {\n findEmptyColumns(column, hits);\n }\n }\n}\n\nexport const emptyColumn: Rule = {\n meta,\n template(content: TemplateContent): RuleHit[] {\n const hits: RuleHit[] = [];\n findEmptyColumns(content.blocks, hits);\n return hits;\n },\n};\n","import type { SectionBlock } from \"@templatical/types\";\nimport { isSection } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"structure.empty-section\",\n severity: \"warning\",\n};\n\nfunction isSectionEmpty(section: SectionBlock): boolean {\n if (section.children.length === 0) return true;\n return section.children.every((column) => column.length === 0);\n}\n\nexport const emptySection: Rule = {\n meta,\n block(block) {\n if (!isSection(block)) return null;\n const section = block as SectionBlock;\n if (!isSectionEmpty(section)) return null;\n return {\n blockId: section.id,\n fix: {\n description: \"Remove the empty section\",\n apply: (ctx) => {\n ctx.removeBlock(section.id);\n },\n },\n };\n },\n};\n","import type { SectionBlock } from \"@templatical/types\";\nimport { isSection } from \"@templatical/types\";\nimport type { Rule, RuleMeta, WalkContext } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"structure.nested-section\",\n severity: \"error\",\n};\n\nexport const nestedSection: Rule = {\n meta,\n block(block, ctx: WalkContext) {\n if (!isSection(block)) return null;\n if (ctx.section === null) return null;\n const parentSection = ctx.section as SectionBlock;\n return {\n blockId: block.id,\n params: { parentId: parentSection.id },\n };\n },\n};\n","import type { ColumnLayout, SectionBlock } from \"@templatical/types\";\nimport { isSection } from \"@templatical/types\";\nimport type { Rule, RuleMeta } from \"../../types\";\n\nexport const meta: RuleMeta = {\n id: \"structure.section-column-mismatch\",\n severity: \"error\",\n};\n\nfunction expectedColumnCount(layout: ColumnLayout): number {\n if (layout === \"1\") return 1;\n if (layout === \"3\") return 3;\n return 2;\n}\n\nexport const sectionColumnMismatch: Rule = {\n meta,\n block(block) {\n if (!isSection(block)) return null;\n const section = block as SectionBlock;\n const expected = expectedColumnCount(section.columns);\n const actual = section.children.length;\n if (actual === expected) return null;\n return {\n blockId: section.id,\n params: { layout: section.columns, expected, actual },\n };\n },\n};\n","import type { TemplateContent } from \"@templatical/types\";\nimport type { LintIssue, LintOptions, Rule } from \"../types\";\nimport { resolveStructureOptions, runRules } from \"../run-rules\";\nimport {\n formatStructureMessage,\n type StructureRuleMessageId,\n} from \"./messages\";\nimport { duplicateBlockId } from \"./rules/duplicate-block-id\";\nimport { emptyColumn } from \"./rules/empty-column\";\nimport { emptySection } from \"./rules/empty-section\";\nimport { nestedSection } from \"./rules/nested-section\";\nimport { sectionColumnMismatch } from \"./rules/section-column-mismatch\";\n\nexport const STRUCTURE_RULES: Rule[] = [\n duplicateBlockId,\n emptySection,\n emptyColumn,\n nestedSection,\n sectionColumnMismatch,\n];\n\nexport function lintStructure(\n content: TemplateContent,\n options: LintOptions = {},\n): LintIssue[] {\n if (options.disabled === true || options.structure === false) return [];\n const tool = options.structure ?? {};\n const resolved = resolveStructureOptions(\n options.locale,\n tool,\n STRUCTURE_RULES,\n );\n return runRules(content, STRUCTURE_RULES, resolved, (locale, id, params) =>\n formatStructureMessage(locale, id as StructureRuleMessageId, params),\n );\n}\n","import type en from \"./en\";\n\nconst de: typeof en = {\n \"link.javascript-protocol\":\n 'Die URL verwendet das Protokoll „{protocol}:\", das beliebigen Skriptcode ausführen kann und aus Sicherheitsgründen beim Rendern entfernt wird. Ersetze sie durch eine echte URL oder entferne sie.',\n \"link.unsupported-protocol\":\n 'Die URL verwendet das Protokoll „{protocol}\", das von den meisten E-Mail-Clients nicht unterstützt wird. Verwende http, https, mailto, tel oder sms.',\n \"link.malformed-mailto\":\n \"Der mailto:-Link ist fehlerhaft. Erwartet wird eine einzelne Empfängeradresse vor einer eventuellen Querystring (z. B. mailto:hallo@example.com).\",\n \"link.malformed-tel\":\n \"Der tel:-Link enthält Zeichen, die keine Ziffern, +, Leerzeichen, Bindestriche, Klammern oder Punkte sind.\",\n \"link.localhost-or-staging\":\n 'Der URL-Host „{host}\" entspricht einem Nicht-Produktionsmuster. Ersetze ihn vor dem Versand durch die Produktions-URL.',\n};\n\nexport default de;\n","/**\n * English link-rule messages. The source of truth — other locales annotate\n * themselves `typeof en` so missing or extra keys fail typecheck.\n *\n * Templates use `{name}` placeholders, interpolated by `formatLinkMessage`.\n */\nconst en = {\n \"link.javascript-protocol\":\n 'URL uses the \"{protocol}:\" protocol, which can execute arbitrary script and is stripped at render time for safety. Replace it with a real link or remove the URL.',\n \"link.unsupported-protocol\":\n 'URL uses the \"{protocol}\" protocol, which most email clients do not support. Use http, https, mailto, tel, or sms.',\n \"link.malformed-mailto\":\n \"mailto: link is malformed. Expected a single recipient address before any query string (e.g. mailto:hello@example.com).\",\n \"link.malformed-tel\":\n \"tel: link contains characters that are not digits, +, spaces, dashes, parentheses, or dots.\",\n \"link.localhost-or-staging\":\n 'URL host \"{host}\" matches a non-production pattern. Replace with the production URL before sending.',\n};\n\nexport default en;\n","import en from \"./en\";\n\nexport type LinkMessageMap = typeof en;\nexport type LinkRuleMessageId = keyof LinkMessageMap;\n\nconst modules = import.meta.glob<{ default: LinkMessageMap }>(\"./*.ts\", {\n eager: true,\n});\n\nconst MESSAGES: Record<string, LinkMessageMap> = {};\nfor (const path in modules) {\n const match = /\\.\\/([^/]+)\\.ts$/.exec(path);\n if (!match) continue;\n const locale = match[1];\n if (locale === \"index\") continue;\n MESSAGES[locale] = modules[path].default;\n}\n\nexport const SUPPORTED_LINK_MESSAGE_LOCALES = Object.keys(MESSAGES);\n\nexport function getLinkMessages(locale: string): LinkMessageMap {\n const base = locale.split(\"-\")[0]?.toLowerCase() ?? \"en\";\n return MESSAGES[base] ?? MESSAGES.en ?? en;\n}\n\nexport function formatLinkMessage(\n locale: string,\n ruleId: LinkRuleMessageId,\n params?: Record<string, string | number>,\n): string {\n const map = getLinkMessages(locale);\n const template = map[ruleId] ?? en[ruleId];\n if (!params) return template;\n return template.replace(/\\{(\\w+)\\}/g, (_, key: string) => {\n const value = params[key];\n return value === undefined ? `{${key}}` : String(value);\n });\n}\n","import type { TemplateContent } from \"@templatical/types\";\nimport {\n isButton,\n isHtml,\n isImage,\n isMenu,\n isParagraph,\n isSocialIcons,\n isTitle,\n isVideo,\n} from \"@templatical/types\";\nimport { walkBlocks } from \"./walk\";\nimport { extractAnchors } from \"./html-utils\";\n\nexport type UrlSource =\n \"anchor\" | \"button\" | \"image-link\" | \"video\" | \"menu-item\" | \"social-icon\";\n\nexport interface UrlOccurrence {\n url: string;\n blockId: string;\n source: UrlSource;\n /** Anchor text or block-derived label, if applicable. */\n label?: string;\n}\n\n/**\n * Visit every URL-bearing field in the template tree.\n *\n * Sources covered:\n * - anchor — `<a href>` inside `title.content`, `paragraph.content`,\n * `html.content` (parsed via extractAnchors)\n * - button — `button.url`\n * - image-link — `image.linkUrl` (only when present + non-empty)\n * - video — `video.url`\n * - menu-item — `menu.items[i].url`\n * - social-icon — `social.icons[i].url`\n *\n * Each rule iterates this list once and decides per occurrence.\n */\nexport function walkUrls(content: TemplateContent): UrlOccurrence[] {\n const occurrences: UrlOccurrence[] = [];\n\n walkBlocks(content, (block) => {\n if (isTitle(block) || isParagraph(block) || isHtml(block)) {\n for (const anchor of extractAnchors(block.content)) {\n occurrences.push({\n url: anchor.href,\n blockId: block.id,\n source: \"anchor\",\n label: anchor.text,\n });\n }\n return;\n }\n\n if (isButton(block)) {\n occurrences.push({\n url: block.url,\n blockId: block.id,\n source: \"button\",\n label: block.text,\n });\n return;\n }\n\n if (isImage(block)) {\n if (block.linkUrl && block.linkUrl !== \"\") {\n occurrences.push({\n url: block.linkUrl,\n blockId: block.id,\n source: \"image-link\",\n label: block.alt || undefined,\n });\n }\n return;\n }\n\n if (isVideo(block)) {\n occurrences.push({\n url: block.url,\n blockId: block.id,\n source: \"video\",\n label: block.alt || undefined,\n });\n return;\n }\n\n if (isMenu(block)) {\n for (const item of block.items) {\n occurrences.push({\n url: item.url,\n blockId: block.id,\n source: \"menu-item\",\n label: item.text,\n });\n }\n return;\n }\n\n if (isSocialIcons(block)) {\n for (const icon of block.icons) {\n occurrences.push({\n url: icon.url,\n blockId: block.id,\n source: \"social-icon\",\n label: icon.platform,\n });\n }\n return;\n }\n });\n\n return occurrences;\n}\n","import type { Rule, RuleHit, RuleMeta } from \"../../types\";\nimport { walkUrls } from \"../../url-walker\";\n\nexport const meta: RuleMeta = {\n id: \"link.javascript-protocol\",\n severity: \"error\",\n};\n\n/**\n * The set of URL schemes that can encode executable script or arbitrary\n * payloads inside an `href`/`src` value. All three are in scope for this\n * rule because a sanitizer that strips `javascript:` but leaves `data:` or\n * `vbscript:` through is the textbook \"incomplete URL scheme check\" bug.\n *\n * Rule ID stays `link.javascript-protocol` for back-compat with consumer\n * severity overrides; the message is parameterized so users see the actual\n * matched protocol.\n */\nexport const DANGEROUS_SCRIPT_PROTOCOLS = [\n \"javascript\",\n \"data\",\n \"vbscript\",\n] as const;\n\ntype DangerousProtocol = (typeof DANGEROUS_SCRIPT_PROTOCOLS)[number];\n\n/**\n * Match dangerous-script schemes even when the value is whitespace-padded or\n * mixed-case. Mirrors what HTML attribute parsers see at insert time —\n * leading whitespace (spaces, tabs, newlines) is stripped before scheme\n * parsing, and unicode-percent-encoded whitespace inside the scheme is\n * rejected by browsers, so a literal-strip is sufficient.\n */\nfunction matchDangerousProtocol(url: string): DangerousProtocol | null {\n if (!url) return null;\n const stripped = url.replace(/\\s+/g, \"\");\n for (const proto of DANGEROUS_SCRIPT_PROTOCOLS) {\n if (new RegExp(`^${proto}:`, \"i\").test(stripped)) return proto;\n }\n return null;\n}\n\nexport const javascriptProtocol: Rule = {\n meta,\n template(content): RuleHit[] {\n const hits: RuleHit[] = [];\n for (const occ of walkUrls(content)) {\n const protocol = matchDangerousProtocol(occ.url);\n if (protocol !== null) {\n hits.push({ blockId: occ.blockId, params: { protocol } });\n }\n }\n return hits;\n },\n};\n","import type { Rule, RuleHit, RuleMeta } from \"../../types\";\nimport { walkUrls } from \"../../url-walker\";\nimport { DANGEROUS_SCRIPT_PROTOCOLS } from \"./javascript-protocol\";\n\nexport const meta: RuleMeta = {\n id: \"link.unsupported-protocol\",\n severity: \"warning\",\n};\n\nconst SUPPORTED = new Set([\"http\", \"https\", \"mailto\", \"tel\", \"sms\"]);\nconst DANGEROUS = new Set<string>(DANGEROUS_SCRIPT_PROTOCOLS);\n\n/**\n * Treat dangerous-script schemes (covered by `link.javascript-protocol`) and\n * bare/relative URLs as \"not unsupported\" — this rule fires only for\n * explicitly named schemes that email clients typically refuse.\n */\nfunction getProtocol(url: string): string | null {\n if (!url) return null;\n const trimmed = url.trim();\n const match = /^([a-z][a-z0-9+\\-.]*):/i.exec(trimmed);\n if (!match) return null;\n return match[1].toLowerCase();\n}\n\nexport const unsupportedProtocol: Rule = {\n meta,\n template(content): RuleHit[] {\n const hits: RuleHit[] = [];\n for (const occ of walkUrls(content)) {\n const protocol = getProtocol(occ.url);\n if (protocol === null) continue;\n if (DANGEROUS.has(protocol)) continue;\n if (SUPPORTED.has(protocol)) continue;\n hits.push({ blockId: occ.blockId, params: { protocol } });\n }\n return hits;\n },\n};\n","import type { Rule, RuleHit, RuleMeta } from \"../../types\";\nimport { walkUrls } from \"../../url-walker\";\n\nexport const meta: RuleMeta = {\n id: \"link.malformed-mailto\",\n severity: \"warning\",\n};\n\n/**\n * Pragmatic RFC-5321-ish sanity check, not a full validator. Splits on `?`,\n * requires the left side to contain exactly one `@` with a non-empty local\n * part and a domain that includes at least one dot.\n *\n * Multi-recipient `mailto:a@x.com,b@y.com` is accepted (commas pass through;\n * each recipient is validated individually).\n */\nfunction isMalformedMailto(url: string): boolean {\n const trimmed = url.trim();\n if (!/^mailto:/i.test(trimmed)) return false;\n const value = trimmed.slice(\"mailto:\".length);\n const [recipients] = value.split(\"?\", 2);\n if (recipients.trim() === \"\") return true;\n\n const list = recipients.split(\",\").map((r) => r.trim());\n for (const recipient of list) {\n if (recipient === \"\") return true;\n const at = recipient.split(\"@\");\n if (at.length !== 2) return true;\n const [local, domain] = at;\n if (local === \"\" || domain === \"\") return true;\n if (!domain.includes(\".\")) return true;\n }\n return false;\n}\n\nexport const malformedMailto: Rule = {\n meta,\n template(content): RuleHit[] {\n const hits: RuleHit[] = [];\n for (const occ of walkUrls(content)) {\n if (isMalformedMailto(occ.url)) {\n hits.push({ blockId: occ.blockId });\n }\n }\n return hits;\n },\n};\n","import type { Rule, RuleHit, RuleMeta } from \"../../types\";\nimport { walkUrls } from \"../../url-walker\";\n\nexport const meta: RuleMeta = {\n id: \"link.malformed-tel\",\n severity: \"warning\",\n};\n\nconst VALID_SUBSCRIBER_CHARS = /^[+0-9\\s().\\-]+$/;\n// RFC 3966 par = `;` pname [ \"=\" pvalue ]. pname is alphanum/`-`, pvalue is\n// 1+ paramchar. We accept anything non-empty on the right of `=` since email\n// clients don't validate it.\nconst VALID_PARAM = /^[A-Za-z0-9-]+(=[^;]+)?$/;\n\nfunction isMalformedTel(url: string): boolean {\n const trimmed = url.trim();\n if (!/^tel:/i.test(trimmed)) return false;\n const value = trimmed.slice(\"tel:\".length).trim();\n if (value === \"\") return true;\n const [subscriber, ...params] = value.split(\";\");\n if (!VALID_SUBSCRIBER_CHARS.test(subscriber)) return true;\n return params.some((p) => !VALID_PARAM.test(p));\n}\n\nexport const malformedTel: Rule = {\n meta,\n template(content): RuleHit[] {\n const hits: RuleHit[] = [];\n for (const occ of walkUrls(content)) {\n if (isMalformedTel(occ.url)) {\n hits.push({ blockId: occ.blockId });\n }\n }\n return hits;\n },\n};\n","import type { ResolvedOptions, Rule, RuleHit, RuleMeta } from \"../../types\";\nimport { walkUrls } from \"../../url-walker\";\n\nexport const meta: RuleMeta = {\n id: \"link.localhost-or-staging\",\n severity: \"warning\",\n};\n\n/**\n * Glob → RegExp for the `nonProductionHosts` pattern set. `*` is a wildcard\n * that matches any run of characters (including `.`) so `*.staging.*`\n * matches `app.staging.example.com` and `*.local` matches both `acme.local`\n * and `a.b.c.local`. Case-insensitive.\n */\nfunction globToRegex(pattern: string): RegExp {\n const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const expanded = escaped.replace(/\\*/g, \".*\");\n return new RegExp(`^${expanded}$`, \"i\");\n}\n\nfunction extractHost(url: string): string | null {\n if (!url) return null;\n const trimmed = url.trim();\n // mailto/tel/sms have no host concept worth matching.\n if (!/^(https?|ftps?):\\/\\//i.test(trimmed)) return null;\n try {\n return new URL(trimmed).hostname.toLowerCase();\n } catch {\n return null;\n }\n}\n\nexport const localhostOrStaging: Rule = {\n meta,\n template(content, opts: ResolvedOptions): RuleHit[] {\n const patterns = opts.links.nonProductionHosts;\n if (patterns.length === 0) return [];\n const regexes = patterns.map(globToRegex);\n const hits: RuleHit[] = [];\n\n for (const occ of walkUrls(content)) {\n const host = extractHost(occ.url);\n if (host === null) continue;\n if (regexes.some((re) => re.test(host))) {\n hits.push({ blockId: occ.blockId, params: { host } });\n }\n }\n return hits;\n },\n};\n","import type { TemplateContent } from \"@templatical/types\";\nimport type { LintIssue, LintOptions, Rule } from \"../types\";\nimport { resolveLinksOptions, runRules } from \"../run-rules\";\nimport { formatLinkMessage, type LinkRuleMessageId } from \"./messages\";\nimport { javascriptProtocol } from \"./rules/javascript-protocol\";\nimport { unsupportedProtocol } from \"./rules/unsupported-protocol\";\nimport { malformedMailto } from \"./rules/malformed-mailto\";\nimport { malformedTel } from \"./rules/malformed-tel\";\nimport { localhostOrStaging } from \"./rules/localhost-or-staging\";\n\nexport const LINK_RULES: Rule[] = [\n javascriptProtocol,\n unsupportedProtocol,\n malformedMailto,\n malformedTel,\n localhostOrStaging,\n];\n\nexport function lintLinks(\n content: TemplateContent,\n options: LintOptions = {},\n): LintIssue[] {\n if (options.disabled === true || options.links === false) return [];\n const tool = options.links ?? {};\n const resolved = resolveLinksOptions(options.locale, tool, LINK_RULES);\n return runRules(content, LINK_RULES, resolved, (locale, id, params) =>\n formatLinkMessage(locale, id as LinkRuleMessageId, params),\n );\n}\n","import type { LintOptions } from \"./types\";\n\n/**\n * `true` when no linter would run for the given options — either the\n * global `disabled` flag is set, or every per-tool key is `false`.\n *\n * The editor uses this to skip lazy-loading `@templatical/quality`, hide\n * the Issues sidebar tab, and suppress inline canvas badges. Headless\n * callers can use it to short-circuit before any linter call.\n */\nexport function isLintFullyDisabled(options: LintOptions | undefined): boolean {\n if (!options) return false;\n if (options.disabled === true) return true;\n return (\n options.accessibility === false &&\n options.structure === false &&\n options.links === false\n );\n}\n","import type { TemplateContent } from \"@templatical/types\";\nimport type { LintIssue, LintOptions } from \"./types\";\nimport { lintAccessibility } from \"./accessibility\";\nimport { lintStructure } from \"./structure\";\nimport { lintLinks } from \"./links\";\nimport { isLintFullyDisabled } from \"./util\";\n\n/**\n * Run every linter in the package — accessibility, structure, and links —\n * against `content` and return the merged issue list.\n *\n * This is the single entry point callers should prefer. CI guards, the\n * editor's live linter, and headless consumers all funnel through here, so\n * a new linter category is picked up everywhere by registering it in this\n * one fan-out — no consumer has to learn about the new function.\n *\n * Per-category options (`options.accessibility`, `options.structure`,\n * `options.links`) and the global `options.disabled` flag are forwarded\n * unchanged; each sub-linter already short-circuits when its category is\n * `false`, so the per-tool calls below are cheap no-ops when disabled.\n *\n * Issue order is stable: accessibility first, then structure, then links.\n */\nexport function lintTemplate(\n content: TemplateContent,\n options: LintOptions = {},\n): LintIssue[] {\n if (isLintFullyDisabled(options)) return [];\n return [\n ...lintAccessibility(content, options),\n ...lintStructure(content, options),\n ...lintLinks(content, options),\n ];\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"src-CLoCCDfs.js","names":[],"sources":["../../../../types/src/blocks.ts","../../../../types/src/social.ts","../../../../types/src/guards.ts","../../../../types/src/defaults.ts","../../../../types/src/template.ts","../../../../types/src/factory.ts","../../../../types/src/clone.ts","../../../../types/src/html-scan.ts","../../../../types/src/merge-tags.ts","../../../../types/src/preview-resolution.ts","../../../../types/src/config.ts"],"sourcesContent":["export interface SpacingValue {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\nexport interface BlockStyles {\n padding: SpacingValue;\n backgroundColor?: string;\n}\n\nexport interface BlockVisibility {\n desktop: boolean;\n mobile: boolean;\n}\n\nexport interface BaseBlock {\n id: string;\n type: string;\n styles: BlockStyles;\n visibility?: BlockVisibility;\n displayCondition?: {\n label: string;\n before: string;\n after: string;\n group?: string;\n description?: string;\n };\n}\n\nexport type ColumnLayout = \"1\" | \"2\" | \"3\" | \"2-1\" | \"1-2\";\n\n/**\n * Optional outer frame for a section. When present, the section is rendered\n * inside an `mj-wrapper` — a full-width band (its own background + padding)\n * that frames the section, e.g. a white card sitting on a colored band.\n */\nexport interface SectionWrapper {\n backgroundColor?: string;\n padding?: SpacingValue;\n /** Corner radius in px for the outer frame. Omitted/0 = square corners. */\n borderRadius?: number;\n}\n\nexport interface SectionBlock extends BaseBlock {\n type: \"section\";\n columns: ColumnLayout;\n children: Block[][];\n /**\n * Whether columns stack vertically on mobile. Absent or `true` keeps MJML's\n * default responsive behavior (columns stack below 480px). `false` renders\n * the columns inside an `<mj-group>` so they stay side-by-side on mobile,\n * proportionally shrunk to fit.\n */\n stackOnMobile?: boolean;\n /** Corner radius in px. Omitted/0 = square corners. */\n borderRadius?: number;\n /** Optional outer frame (rendered as an `mj-wrapper` around the section). */\n wrapper?: SectionWrapper;\n}\n\nexport type HeadingLevel = 1 | 2 | 3 | 4;\n\nexport const HEADING_LEVEL_FONT_SIZE: Record<HeadingLevel, number> = {\n 1: 36,\n 2: 28,\n 3: 22,\n 4: 18,\n};\n\n/**\n * Spacing (px) for the HTML a rich-text block stores — the `<p>`, `<ul>`,\n * `<ol>` and `<li>` elements TipTap produces.\n *\n * The editor canvas and the exported email must agree on these to the pixel,\n * so this is the one place they are stated. `@templatical/renderer` reads them\n * to build the global rule it emits; the editor's canvas CSS restates them as\n * literals (CSS cannot import a constant) and a parity test holds the two\n * together.\n *\n * Fixed px, never a `rem`- or base-size-derived scale: an email body is a\n * fixed-width document, so content spacing must not move when a consumer\n * scales the editor chrome.\n */\nexport const RICH_TEXT_SPACING = {\n /** Gap below every `<p>` except the last — the space between paragraphs. */\n paragraphGap: 8,\n /** Vertical margin on `<ul>` / `<ol>`. */\n listMarginY: 8,\n /** Left padding on `<ul>` / `<ol>` — the bullet indent. */\n listPaddingLeft: 24,\n /** Vertical margin on `<li>`. */\n listItemMarginY: 4,\n} as const;\n\nexport interface TitleBlock extends BaseBlock {\n type: \"title\";\n content: string;\n level: HeadingLevel;\n /** Text color. Unset = inherit the document-level `textColor`. */\n color?: string;\n textAlign: \"left\" | \"center\" | \"right\";\n fontFamily?: string;\n}\n\nexport interface ParagraphBlock extends BaseBlock {\n type: \"paragraph\";\n content: string;\n /**\n * Gap in px between this block's paragraphs — the space below every `<p>`\n * except the last. Absent means `RICH_TEXT_SPACING.paragraphGap`.\n *\n * Only affects a block holding more than one paragraph; a single `<p>` has no\n * internal gap, and the space around the block is `styles.padding`.\n *\n * `0` is a valid choice (paragraphs butted together) and is distinct from the\n * field being absent, so readers must test for `undefined` rather than\n * falsiness.\n */\n paragraphSpacing?: number;\n}\n\nexport interface ImageBlock extends BaseBlock {\n type: \"image\";\n src: string;\n alt: string;\n width: number | \"full\";\n /**\n * Height in pixels. Absent means the height is derived from the width, so the\n * image keeps its aspect ratio — setting both stretches it, since email\n * clients don't support `object-fit`.\n */\n height?: number;\n align: \"left\" | \"center\" | \"right\";\n /**\n * Corner radius in px. Omitted/0 = square corners. A radius of at least half\n * the rendered size rounds a square image to a circle, which is how avatar\n * and portrait layouts are built.\n */\n borderRadius?: number;\n linkUrl?: string;\n linkOpenInNewTab?: boolean;\n placeholderUrl?: string;\n decorative?: boolean;\n}\n\nexport interface ButtonBlock extends BaseBlock {\n type: \"button\";\n text: string;\n url: string;\n openInNewTab?: boolean;\n backgroundColor: string;\n textColor: string;\n borderRadius: number;\n fontSize: number;\n buttonPadding: SpacingValue;\n fontFamily?: string;\n width?: number | \"full\";\n /**\n * Placement of the button within its column. No visible effect when `width`\n * is `\"full\"`, since the button then spans the column.\n */\n align: \"left\" | \"center\" | \"right\";\n}\n\nexport interface DividerBlock extends BaseBlock {\n type: \"divider\";\n lineStyle: \"solid\" | \"dashed\" | \"dotted\";\n color: string;\n thickness: number;\n width: number | \"full\";\n}\n\nexport interface VideoBlock extends BaseBlock {\n type: \"video\";\n url: string;\n openInNewTab?: boolean;\n thumbnailUrl: string;\n alt: string;\n width: number | \"full\";\n /**\n * Height in pixels for the thumbnail. Absent means the height is derived from\n * the width, so the thumbnail keeps its aspect ratio — setting both stretches\n * it, since email clients don't support `object-fit`.\n */\n height?: number;\n align: \"left\" | \"center\" | \"right\";\n placeholderUrl?: string;\n}\n\nexport type SocialPlatform =\n | \"facebook\"\n | \"twitter\"\n | \"instagram\"\n | \"linkedin\"\n | \"youtube\"\n | \"tiktok\"\n | \"pinterest\"\n | \"email\"\n | \"whatsapp\"\n | \"telegram\"\n | \"discord\"\n | \"snapchat\"\n | \"reddit\"\n | \"github\"\n | \"dribbble\"\n | \"behance\"\n | \"website\";\n\nexport type SocialIconStyle =\n \"solid\" | \"outlined\" | \"rounded\" | \"square\" | \"circle\";\n\nexport type SocialIconSize = \"small\" | \"medium\" | \"large\";\n\nexport interface SocialIcon {\n id: string;\n platform: SocialPlatform;\n url: string;\n}\n\nexport interface SocialIconsBlock extends BaseBlock {\n type: \"social\";\n icons: SocialIcon[];\n iconStyle: SocialIconStyle;\n iconSize: SocialIconSize;\n spacing: number;\n align: \"left\" | \"center\" | \"right\";\n}\n\nexport interface SpacerBlock extends BaseBlock {\n type: \"spacer\";\n height: number;\n}\n\nexport interface HtmlBlock extends BaseBlock {\n type: \"html\";\n content: string;\n}\n\nexport interface MenuItemData {\n id: string;\n text: string;\n url: string;\n openInNewTab: boolean;\n bold: boolean;\n underline: boolean;\n color?: string;\n}\n\nexport interface MenuBlock extends BaseBlock {\n type: \"menu\";\n items: MenuItemData[];\n fontSize: number;\n fontFamily?: string;\n /** Base text/link color. Unset = inherit the document-level `textColor`. */\n color?: string;\n linkColor?: string;\n textAlign: \"left\" | \"center\" | \"right\";\n separator: string;\n separatorColor: string;\n spacing: number;\n}\n\nexport interface TableCellData {\n id: string;\n content: string;\n}\n\nexport interface TableRowData {\n id: string;\n cells: TableCellData[];\n}\n\nexport interface TableBlock extends BaseBlock {\n type: \"table\";\n rows: TableRowData[];\n hasHeaderRow: boolean;\n headerBackgroundColor?: string;\n borderColor: string;\n borderWidth: number;\n cellPadding: number;\n fontSize: number;\n fontFamily?: string;\n /** Text color. Unset = inherit the document-level `textColor`. */\n color?: string;\n textAlign: \"left\" | \"center\" | \"right\";\n}\n\nexport interface CountdownBlock extends BaseBlock {\n type: \"countdown\";\n targetDate: string;\n timezone: string;\n showDays: boolean;\n showHours: boolean;\n showMinutes: boolean;\n showSeconds: boolean;\n separator: \":\" | \"-\" | \" \";\n digitFontSize: number;\n digitColor: string;\n labelColor: string;\n labelFontSize: number;\n backgroundColor: string;\n fontFamily?: string;\n labelDays: string;\n labelHours: string;\n labelMinutes: string;\n labelSeconds: string;\n expiredMessage: string;\n expiredImageUrl: string;\n hideOnExpiry: boolean;\n}\n\nexport interface CustomBlock extends BaseBlock {\n type: \"custom\";\n customType: string;\n fieldValues: Record<string, unknown>;\n renderedHtml?: string;\n dataSourceFetched?: boolean;\n}\n\nexport type Block =\n | SectionBlock\n | TitleBlock\n | ParagraphBlock\n | ImageBlock\n | ButtonBlock\n | DividerBlock\n | VideoBlock\n | SocialIconsBlock\n | SpacerBlock\n | HtmlBlock\n | MenuBlock\n | TableBlock\n | CountdownBlock\n | CustomBlock;\n\nexport type BlockType = Block[\"type\"];\n","import type { SocialPlatform } from \"./blocks\";\n\n/**\n * SVG glyph (24×24 path) + brand color for each social platform.\n *\n * Single source of truth shared by two consumers that render icons in\n * different formats: the editor draws them as inline SVG (`SocialIconSvg.vue`)\n * and `@templatical/renderer` rasterizes them to PNGs\n * (`scripts/rasterize-social.mjs`) because Outlook's Word engine has no SVG\n * support. Typing as `Record<SocialPlatform, ...>` makes adding a platform to\n * the union a compile error until its glyph is supplied here, keeping both\n * consumers in lockstep.\n */\nexport interface SocialIconGlyph {\n /** SVG path data on a 24×24 viewBox. */\n path: string;\n /** Brand color as a 6-digit hex string. */\n color: string;\n}\n\nexport const SOCIAL_ICON_GLYPHS: Record<SocialPlatform, SocialIconGlyph> = {\n facebook: {\n color: \"#1877F2\",\n path: \"M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z\",\n },\n twitter: {\n color: \"#000000\",\n path: \"M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z\",\n },\n instagram: {\n color: \"#E4405F\",\n path: \"M12 0C8.74 0 8.333.015 7.053.072 5.775.132 4.905.333 4.14.63c-.789.306-1.459.717-2.126 1.384S.935 3.35.63 4.14C.333 4.905.131 5.775.072 7.053.012 8.333 0 8.74 0 12s.015 3.667.072 4.947c.06 1.277.261 2.148.558 2.913.306.788.717 1.459 1.384 2.126.667.666 1.336 1.079 2.126 1.384.766.296 1.636.499 2.913.558C8.333 23.988 8.74 24 12 24s3.667-.015 4.947-.072c1.277-.06 2.148-.262 2.913-.558.788-.306 1.459-.718 2.126-1.384.666-.667 1.079-1.335 1.384-2.126.296-.765.499-1.636.558-2.913.06-1.28.072-1.687.072-4.947s-.015-3.667-.072-4.947c-.06-1.277-.262-2.149-.558-2.913-.306-.789-.718-1.459-1.384-2.126C21.319 1.347 20.651.935 19.86.63c-.765-.297-1.636-.499-2.913-.558C15.667.012 15.26 0 12 0zm0 2.16c3.203 0 3.585.016 4.85.071 1.17.055 1.805.249 2.227.415.562.217.96.477 1.382.896.419.42.679.819.896 1.381.164.422.36 1.057.413 2.227.057 1.266.07 1.646.07 4.85s-.015 3.585-.074 4.85c-.061 1.17-.256 1.805-.421 2.227-.224.562-.479.96-.899 1.382-.419.419-.824.679-1.38.896-.42.164-1.065.36-2.235.413-1.274.057-1.649.07-4.859.07-3.211 0-3.586-.015-4.859-.074-1.171-.061-1.816-.256-2.236-.421-.569-.224-.96-.479-1.379-.899-.421-.419-.69-.824-.9-1.38-.165-.42-.359-1.065-.42-2.235-.045-1.26-.061-1.649-.061-4.844 0-3.196.016-3.586.061-4.861.061-1.17.255-1.814.42-2.234.21-.57.479-.96.9-1.381.419-.419.81-.689 1.379-.898.42-.166 1.051-.361 2.221-.421 1.275-.045 1.65-.06 4.859-.06l.045.03zm0 3.678c-3.405 0-6.162 2.76-6.162 6.162 0 3.405 2.76 6.162 6.162 6.162 3.405 0 6.162-2.76 6.162-6.162 0-3.405-2.76-6.162-6.162-6.162zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm7.846-10.405c0 .795-.646 1.44-1.44 1.44-.795 0-1.44-.646-1.44-1.44 0-.794.646-1.439 1.44-1.439.793-.001 1.44.645 1.44 1.439z\",\n },\n linkedin: {\n color: \"#0A66C2\",\n path: \"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z\",\n },\n youtube: {\n color: \"#FF0000\",\n path: \"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z\",\n },\n tiktok: {\n color: \"#000000\",\n path: \"M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z\",\n },\n pinterest: {\n color: \"#BD081C\",\n path: \"M12 0C5.373 0 0 5.372 0 12c0 5.084 3.163 9.426 7.627 11.174-.105-.949-.2-2.405.042-3.441.218-.937 1.407-5.965 1.407-5.965s-.359-.719-.359-1.782c0-1.668.967-2.914 2.171-2.914 1.023 0 1.518.769 1.518 1.69 0 1.029-.655 2.568-.994 3.995-.283 1.194.599 2.169 1.777 2.169 2.133 0 3.772-2.249 3.772-5.495 0-2.873-2.064-4.882-5.012-4.882-3.414 0-5.418 2.561-5.418 5.207 0 1.031.397 2.138.893 2.738.098.119.112.224.083.345l-.333 1.36c-.053.22-.174.267-.402.161-1.499-.698-2.436-2.889-2.436-4.649 0-3.785 2.75-7.262 7.929-7.262 4.163 0 7.398 2.967 7.398 6.931 0 4.136-2.607 7.464-6.227 7.464-1.216 0-2.359-.631-2.75-1.378l-.748 2.853c-.271 1.043-1.002 2.35-1.492 3.146C9.57 23.812 10.763 24 12 24c6.627 0 12-5.373 12-12 0-6.628-5.373-12-12-12z\",\n },\n email: {\n color: \"#6B7280\",\n path: \"M1.5 8.67v8.58a3 3 0 003 3h15a3 3 0 003-3V8.67l-8.928 5.493a3 3 0 01-3.144 0L1.5 8.67z M22.5 6.908V6.75a3 3 0 00-3-3h-15a3 3 0 00-3 3v.158l9.714 5.978a1.5 1.5 0 001.572 0L22.5 6.908z\",\n },\n website: {\n color: \"#6B7280\",\n path: \"M21.721 12.752a9.711 9.711 0 0 0-.945-5.003 12.754 12.754 0 0 1-4.339 2.708 18.991 18.991 0 0 1-.214 4.772 17.165 17.165 0 0 0 5.498-2.477ZM14.634 15.55a17.324 17.324 0 0 0 .332-4.647c-.952.227-1.945.347-2.966.347-1.021 0-2.014-.12-2.966-.347a17.515 17.515 0 0 0 .332 4.647 17.385 17.385 0 0 0 5.268 0ZM9.772 17.119a18.963 18.963 0 0 0 4.456 0A17.182 17.182 0 0 1 12 21.724a17.18 17.18 0 0 1-2.228-4.605ZM7.777 15.23a18.87 18.87 0 0 1-.214-4.774 12.753 12.753 0 0 1-4.34-2.708 9.711 9.711 0 0 0-.944 5.004 17.165 17.165 0 0 0 5.498 2.477ZM21.356 14.752a9.765 9.765 0 0 1-7.478 6.817 18.64 18.64 0 0 0 1.988-4.718 18.627 18.627 0 0 0 5.49-2.098ZM2.644 14.752c1.682.971 3.53 1.688 5.49 2.099a18.64 18.64 0 0 0 1.988 4.718 9.765 9.765 0 0 1-7.478-6.816ZM13.878 2.43a9.755 9.755 0 0 1 6.116 3.986 11.267 11.267 0 0 1-3.746 2.504 18.63 18.63 0 0 0-2.37-6.49ZM12 2.276a17.152 17.152 0 0 1 2.805 7.121c-.897.23-1.837.353-2.805.353-.968 0-1.908-.122-2.805-.353A17.151 17.151 0 0 1 12 2.276ZM10.122 2.43a18.629 18.629 0 0 0-2.37 6.49 11.266 11.266 0 0 1-3.746-2.504 9.754 9.754 0 0 1 6.116-3.985Z\",\n },\n whatsapp: {\n color: \"#25D366\",\n path: \"M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z\",\n },\n telegram: {\n color: \"#26A5E4\",\n path: \"M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z\",\n },\n discord: {\n color: \"#5865F2\",\n path: \"M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189z\",\n },\n snapchat: {\n color: \"#FFFC00\",\n path: \"M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.668.967-2.914 2.171-2.914 1.023 0 1.518.769 1.518 1.69 0 1.029-.655 2.568-.994 3.995-.283 1.194.599 2.169 1.777 2.169 2.133 0 3.772-2.249 3.772-5.495 0-2.873-2.064-4.882-5.012-4.882-3.414 0-5.418 2.561-5.418 5.207 0 1.031.397 2.138.893 2.738a.36.36 0 01.083.345l-.333 1.36c-.053.22-.174.267-.402.161-1.499-.698-2.436-2.889-2.436-4.649 0-3.785 2.75-7.262 7.929-7.262 4.163 0 7.398 2.967 7.398 6.931 0 4.136-2.607 7.464-6.227 7.464-1.216 0-2.359-.631-2.75-1.378l-.748 2.853c-.271 1.043-1.002 2.35-1.492 3.146 1.124.347 2.317.535 3.554.535 6.627 0 12.017-5.373 12.017-12.001C24.034 5.367 18.644 0 12.017 0z\",\n },\n reddit: {\n color: \"#FF4500\",\n path: \"M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z\",\n },\n github: {\n color: \"#181717\",\n path: \"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\",\n },\n dribbble: {\n color: \"#EA4C89\",\n path: \"M12 24C5.385 24 0 18.615 0 12S5.385 0 12 0s12 5.385 12 12-5.385 12-12 12zm10.12-10.358c-.35-.11-3.17-.953-6.384-.438 1.34 3.684 1.887 6.684 1.992 7.308 2.3-1.555 3.936-4.02 4.392-6.87zm-6.115 7.808c-.153-.9-.75-4.032-2.19-7.77l-.066.02c-5.79 2.015-7.86 6.025-8.04 6.4 1.73 1.358 3.92 2.166 6.29 2.166 1.42 0 2.77-.29 4-.814zm-11.62-2.58c.232-.4 3.045-5.055 8.332-6.765.135-.045.27-.084.405-.12-.26-.585-.54-1.167-.832-1.74C7.17 11.775 2.206 11.71 1.756 11.7l-.004.312c0 2.633.998 5.037 2.634 6.855zm-2.42-8.955c.46.008 4.683.026 9.477-1.248-1.698-3.018-3.53-5.558-3.8-5.928-2.868 1.35-5.01 3.99-5.676 7.17zM9.6 2.052c.282.38 2.145 2.914 3.822 6 3.645-1.365 5.19-3.44 5.373-3.702-1.81-1.61-4.19-2.586-6.795-2.586-.825 0-1.63.1-2.4.285zm10.335 3.483c-.218.29-1.935 2.493-5.724 4.04.24.49.47.985.68 1.486.08.18.15.36.22.53 3.41-.43 6.8.26 7.14.33-.02-2.42-.88-4.64-2.31-6.38z\",\n },\n behance: {\n color: \"#1769FF\",\n path: \"M22 7h-7V5h7v2zm1.726 10c-.442 1.297-2.029 3-5.101 3-3.074 0-5.564-1.729-5.564-5.675 0-3.91 2.325-5.92 5.466-5.92 3.082 0 4.964 1.782 5.375 4.426.078.506.109 1.188.095 2.14H15.97c.13 3.211 3.483 3.312 4.588 2.029h3.168zm-7.686-4h4.965c-.105-1.547-1.136-2.219-2.477-2.219-1.466 0-2.277.768-2.488 2.219zm-9.574 6.988H0V5.021h6.953c5.476.081 5.58 5.444 2.72 6.906 3.461 1.26 3.577 8.061-3.207 8.061zM3 11h3.584c2.508 0 2.906-3-.312-3H3v3zm3.391 3H3v3.016h3.341c3.055 0 2.868-3.016.05-3.016z\",\n },\n};\n","import type {\n Block,\n ButtonBlock,\n CountdownBlock,\n CustomBlock,\n DividerBlock,\n HtmlBlock,\n ImageBlock,\n MenuBlock,\n ParagraphBlock,\n SectionBlock,\n SocialIconsBlock,\n SpacerBlock,\n TableBlock,\n TitleBlock,\n VideoBlock,\n} from \"./blocks\";\n\nexport function isSection(block: Block): block is SectionBlock {\n return block.type === \"section\";\n}\n\nexport function isTitle(block: Block): block is TitleBlock {\n return block.type === \"title\";\n}\n\nexport function isParagraph(block: Block): block is ParagraphBlock {\n return block.type === \"paragraph\";\n}\n\nexport function isImage(block: Block): block is ImageBlock {\n return block.type === \"image\";\n}\n\nexport function isButton(block: Block): block is ButtonBlock {\n return block.type === \"button\";\n}\n\nexport function isDivider(block: Block): block is DividerBlock {\n return block.type === \"divider\";\n}\n\nexport function isVideo(block: Block): block is VideoBlock {\n return block.type === \"video\";\n}\n\nexport function isSocialIcons(block: Block): block is SocialIconsBlock {\n return block.type === \"social\";\n}\n\nexport function isSpacer(block: Block): block is SpacerBlock {\n return block.type === \"spacer\";\n}\n\nexport function isHtml(block: Block): block is HtmlBlock {\n return block.type === \"html\";\n}\n\nexport function isMenu(block: Block): block is MenuBlock {\n return block.type === \"menu\";\n}\n\nexport function isTable(block: Block): block is TableBlock {\n return block.type === \"table\";\n}\n\nexport function isCountdown(block: Block): block is CountdownBlock {\n return block.type === \"countdown\";\n}\n\nexport function isCustomBlock(block: Block): block is CustomBlock {\n return block.type === \"custom\";\n}\n","import type {\n ButtonBlock,\n CountdownBlock,\n DividerBlock,\n HtmlBlock,\n ImageBlock,\n MenuBlock,\n ParagraphBlock,\n SectionBlock,\n SocialIconsBlock,\n SpacerBlock,\n TableBlock,\n TitleBlock,\n VideoBlock,\n} from \"./blocks\";\nimport type { TemplateSettings } from \"./template\";\n\ntype BlockDefaultsFor<T> = Partial<Omit<T, \"id\" | \"type\">>;\n\nexport interface BlockDefaults {\n title?: BlockDefaultsFor<TitleBlock>;\n paragraph?: BlockDefaultsFor<ParagraphBlock>;\n image?: BlockDefaultsFor<ImageBlock>;\n button?: BlockDefaultsFor<ButtonBlock>;\n divider?: BlockDefaultsFor<DividerBlock>;\n section?: BlockDefaultsFor<SectionBlock>;\n video?: BlockDefaultsFor<VideoBlock>;\n social?: BlockDefaultsFor<SocialIconsBlock>;\n spacer?: BlockDefaultsFor<SpacerBlock>;\n html?: BlockDefaultsFor<HtmlBlock>;\n menu?: BlockDefaultsFor<MenuBlock>;\n table?: BlockDefaultsFor<TableBlock>;\n countdown?: BlockDefaultsFor<CountdownBlock>;\n}\n\nexport type TemplateDefaults = Partial<TemplateSettings>;\n\n// ---------------------------------------------------------------------------\n// Built-in default values — single source of truth for factories & consumers\n// ---------------------------------------------------------------------------\n\n// `color` is intentionally omitted: a new Title inherits the document\n// `textColor` (see `TemplateSettings`) unless the user sets a per-block color.\nexport const TITLE_BLOCK_DEFAULTS: BlockDefaultsFor<TitleBlock> = {\n content: \"<p>Enter your title</p>\",\n level: 2,\n textAlign: \"left\",\n};\n\nexport const PARAGRAPH_BLOCK_DEFAULTS: BlockDefaultsFor<ParagraphBlock> = {\n content: \"<p>Enter your text here</p>\",\n};\n\nexport const IMAGE_BLOCK_DEFAULTS: BlockDefaultsFor<ImageBlock> = {\n src: \"\",\n alt: \"\",\n width: \"full\",\n align: \"center\",\n};\n\nexport const BUTTON_BLOCK_DEFAULTS: BlockDefaultsFor<ButtonBlock> = {\n text: \"Click Here\",\n url: \"\",\n backgroundColor: \"#333333\",\n textColor: \"#ffffff\",\n borderRadius: 6,\n fontSize: 15,\n buttonPadding: { top: 12, right: 24, bottom: 12, left: 24 },\n align: \"center\",\n};\n\nexport const DIVIDER_BLOCK_DEFAULTS: BlockDefaultsFor<DividerBlock> = {\n lineStyle: \"solid\",\n color: \"#e0e0e0\",\n thickness: 1,\n width: \"full\",\n};\n\nexport const SECTION_BLOCK_DEFAULTS: BlockDefaultsFor<SectionBlock> = {\n columns: \"1\",\n};\n\nexport const VIDEO_BLOCK_DEFAULTS: BlockDefaultsFor<VideoBlock> = {\n url: \"\",\n thumbnailUrl: \"\",\n alt: \"Video\",\n width: \"full\",\n align: \"center\",\n};\n\nexport const SOCIAL_ICONS_BLOCK_DEFAULTS: BlockDefaultsFor<SocialIconsBlock> = {\n iconStyle: \"solid\",\n iconSize: \"medium\",\n spacing: 10,\n align: \"center\",\n};\n\nexport const SPACER_BLOCK_DEFAULTS: BlockDefaultsFor<SpacerBlock> = {\n height: 24,\n};\n\nexport const HTML_BLOCK_DEFAULTS: BlockDefaultsFor<HtmlBlock> = {\n content: \"\",\n};\n\n// `color` is intentionally omitted: a new Menu inherits the document\n// `textColor` unless the user sets a per-block color (or per-item `linkColor`).\nexport const MENU_BLOCK_DEFAULTS: BlockDefaultsFor<MenuBlock> = {\n fontSize: 15,\n textAlign: \"center\",\n separator: \"|\",\n separatorColor: \"#e0e0e0\",\n spacing: 10,\n};\n\n// `color` is intentionally omitted: a new Table inherits the document\n// `textColor` unless the user sets a per-block color.\nexport const TABLE_BLOCK_DEFAULTS: BlockDefaultsFor<TableBlock> = {\n hasHeaderRow: true,\n borderColor: \"#e0e0e0\",\n borderWidth: 1,\n cellPadding: 8,\n fontSize: 15,\n textAlign: \"left\",\n};\n\nexport const COUNTDOWN_BLOCK_DEFAULTS: BlockDefaultsFor<CountdownBlock> = {\n targetDate: \"\",\n timezone: \"UTC\",\n showDays: true,\n showHours: true,\n showMinutes: true,\n showSeconds: true,\n separator: \":\",\n digitFontSize: 32,\n digitColor: \"#1a1a1a\",\n labelColor: \"#6b7280\",\n labelFontSize: 12,\n backgroundColor: \"#ffffff\",\n labelDays: \"Days\",\n labelHours: \"Hours\",\n labelMinutes: \"Minutes\",\n labelSeconds: \"Seconds\",\n expiredMessage: \"This offer has expired\",\n expiredImageUrl: \"\",\n hideOnExpiry: false,\n};\n\nexport const DEFAULT_BLOCK_DEFAULTS: Required<BlockDefaults> = {\n title: TITLE_BLOCK_DEFAULTS,\n paragraph: PARAGRAPH_BLOCK_DEFAULTS,\n image: IMAGE_BLOCK_DEFAULTS,\n button: BUTTON_BLOCK_DEFAULTS,\n divider: DIVIDER_BLOCK_DEFAULTS,\n section: SECTION_BLOCK_DEFAULTS,\n video: VIDEO_BLOCK_DEFAULTS,\n social: SOCIAL_ICONS_BLOCK_DEFAULTS,\n spacer: SPACER_BLOCK_DEFAULTS,\n html: HTML_BLOCK_DEFAULTS,\n menu: MENU_BLOCK_DEFAULTS,\n table: TABLE_BLOCK_DEFAULTS,\n countdown: COUNTDOWN_BLOCK_DEFAULTS,\n};\n\nexport const DEFAULT_TEMPLATE_DEFAULTS: TemplateDefaults = {\n width: 600,\n backgroundColor: \"#ffffff\",\n textColor: \"#1a1a1a\",\n linkUnderline: true,\n fontFamily: \"Arial\",\n locale: \"en\",\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n Object.getPrototypeOf(value) === Object.prototype\n );\n}\n\nexport function deepMergeDefaults<T extends object>(\n base: T,\n overrides: Partial<T>,\n): T {\n const result = { ...base };\n\n for (const key of Object.keys(overrides) as Array<keyof T>) {\n const baseVal = base[key];\n const overrideVal = overrides[key];\n\n if (overrideVal === undefined) {\n continue;\n }\n\n if (isPlainObject(baseVal) && isPlainObject(overrideVal)) {\n result[key] = deepMergeDefaults(\n baseVal,\n overrideVal as Partial<typeof baseVal>,\n ) as T[keyof T];\n } else {\n result[key] = overrideVal as T[keyof T];\n }\n }\n\n return result;\n}\n","import type { Block } from \"./blocks\";\nimport { DEFAULT_TEMPLATE_DEFAULTS } from \"./defaults\";\nimport type { TemplateDefaults } from \"./defaults\";\n\nexport interface TemplateSettings {\n width: number;\n backgroundColor: string;\n /**\n * Document-level default text color: the `<mj-text>` default in the rendered\n * MJML, inherited by every text block (Title, Paragraph, Menu, Table) that\n * doesn't set its own `color`. Required, defaulting to `#1a1a1a` (see\n * `DEFAULT_TEMPLATE_DEFAULTS`); customize the default per-consumer via\n * `templateDefaults`. A block's explicit `color` or an inline text-color\n * mark overrides it.\n */\n textColor: string;\n /**\n * Document-level link color: emitted as the global `a { color }` rule in the\n * rendered MJML, so it cascades to every link — rich-text and menu alike.\n * Optional: when unset, links inherit the surrounding text color\n * (`color: inherit`), preserving the pre-#352 default. A per-block or\n * per-item color (a Menu item's `color`, `MenuBlock.linkColor`) still\n * overrides it.\n */\n linkColor?: string;\n /**\n * Whether links are underlined document-wide: drives the global\n * `a { text-decoration }` rule (`underline` when true, `none` when false).\n * Required, defaulting to `true` (see `DEFAULT_TEMPLATE_DEFAULTS`) — the\n * common, more accessible email default. Applies to body (rich-text) links;\n * buttons and menu items carry their own inline `text-decoration` and are\n * unaffected. Set `false` to render links without an underline.\n */\n linkUnderline: boolean;\n fontFamily: string;\n preheaderText?: string;\n /**\n * BCP-47 language code for the rendered email's `<html lang>`. Drives\n * screen-reader pronunciation. Default `'en'` via `DEFAULT_TEMPLATE_DEFAULTS`.\n */\n locale: string;\n}\n\nexport interface TemplateContent {\n blocks: Block[];\n settings: TemplateSettings;\n}\n\nexport function createDefaultTemplateContent(\n defaultFontFamily = \"Arial\",\n templateDefaults?: TemplateDefaults,\n): TemplateContent {\n return {\n blocks: [],\n settings: {\n ...DEFAULT_TEMPLATE_DEFAULTS,\n fontFamily: defaultFontFamily,\n ...templateDefaults,\n } as TemplateSettings,\n };\n}\n","import type {\n Block,\n BlockStyles,\n BlockType,\n ButtonBlock,\n CountdownBlock,\n CustomBlock,\n DividerBlock,\n HtmlBlock,\n ImageBlock,\n MenuBlock,\n ParagraphBlock,\n SectionBlock,\n SocialIconsBlock,\n SpacerBlock,\n SpacingValue,\n TableBlock,\n TableCellData,\n TableRowData,\n TitleBlock,\n VideoBlock,\n} from \"./blocks\";\nimport type { CustomBlockDefinition, CustomBlockField } from \"./custom-blocks\";\nimport type { BlockDefaults } from \"./defaults\";\nimport {\n BUTTON_BLOCK_DEFAULTS,\n COUNTDOWN_BLOCK_DEFAULTS,\n deepMergeDefaults,\n DIVIDER_BLOCK_DEFAULTS,\n HTML_BLOCK_DEFAULTS,\n IMAGE_BLOCK_DEFAULTS,\n MENU_BLOCK_DEFAULTS,\n PARAGRAPH_BLOCK_DEFAULTS,\n SECTION_BLOCK_DEFAULTS,\n SOCIAL_ICONS_BLOCK_DEFAULTS,\n SPACER_BLOCK_DEFAULTS,\n TABLE_BLOCK_DEFAULTS,\n TITLE_BLOCK_DEFAULTS,\n VIDEO_BLOCK_DEFAULTS,\n} from \"./defaults\";\n\nfunction applyDefaults<T extends object>(\n base: T,\n partial: Partial<T> | undefined,\n): T {\n if (!partial || Object.keys(partial).length === 0) return base;\n return deepMergeDefaults(base, partial);\n}\n\nexport function generateId(): string {\n return crypto.randomUUID();\n}\n\nfunction createDefaultSpacing(value = 0): SpacingValue {\n return { top: value, right: value, bottom: value, left: value };\n}\n\nfunction createDefaultStyles(padding = 10): BlockStyles {\n return {\n padding: createDefaultSpacing(padding),\n };\n}\n\nexport function createTitleBlock(\n partial: Partial<TitleBlock> = {},\n): TitleBlock {\n const base: TitleBlock = {\n id: generateId(),\n type: \"title\",\n ...TITLE_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as TitleBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createParagraphBlock(\n partial: Partial<ParagraphBlock> = {},\n): ParagraphBlock {\n const base: ParagraphBlock = {\n id: generateId(),\n type: \"paragraph\",\n ...PARAGRAPH_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as ParagraphBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createImageBlock(\n partial: Partial<ImageBlock> = {},\n): ImageBlock {\n const base: ImageBlock = {\n id: generateId(),\n type: \"image\",\n ...IMAGE_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as ImageBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createButtonBlock(\n partial: Partial<ButtonBlock> = {},\n): ButtonBlock {\n const base: ButtonBlock = {\n id: generateId(),\n type: \"button\",\n ...BUTTON_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as ButtonBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createDividerBlock(\n partial: Partial<DividerBlock> = {},\n): DividerBlock {\n const base: DividerBlock = {\n id: generateId(),\n type: \"divider\",\n ...DIVIDER_BLOCK_DEFAULTS,\n styles: createDefaultStyles(20),\n } as DividerBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createSectionBlock(\n partial: Partial<SectionBlock> = {},\n): SectionBlock {\n const base: SectionBlock = {\n id: generateId(),\n type: \"section\",\n ...SECTION_BLOCK_DEFAULTS,\n children: [[]],\n styles: createDefaultStyles(20),\n } as SectionBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createVideoBlock(\n partial: Partial<VideoBlock> = {},\n): VideoBlock {\n const base: VideoBlock = {\n id: generateId(),\n type: \"video\",\n ...VIDEO_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as VideoBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createSocialIconsBlock(\n partial: Partial<SocialIconsBlock> = {},\n): SocialIconsBlock {\n const base: SocialIconsBlock = {\n id: generateId(),\n type: \"social\",\n icons: [],\n ...SOCIAL_ICONS_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as SocialIconsBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createSpacerBlock(\n partial: Partial<SpacerBlock> = {},\n): SpacerBlock {\n const base: SpacerBlock = {\n id: generateId(),\n type: \"spacer\",\n ...SPACER_BLOCK_DEFAULTS,\n styles: createDefaultStyles(0),\n } as SpacerBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createHtmlBlock(partial: Partial<HtmlBlock> = {}): HtmlBlock {\n const base: HtmlBlock = {\n id: generateId(),\n type: \"html\",\n ...HTML_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as HtmlBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createMenuBlock(partial: Partial<MenuBlock> = {}): MenuBlock {\n const base: MenuBlock = {\n id: generateId(),\n type: \"menu\",\n items: [],\n ...MENU_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as MenuBlock;\n return applyDefaults(base, partial);\n}\n\nfunction createDefaultTableRows(columns: number, rows: number): TableRowData[] {\n return Array.from({ length: rows }, () => ({\n id: generateId(),\n cells: Array.from({ length: columns }, (): TableCellData => ({\n id: generateId(),\n content: \"\",\n })),\n }));\n}\n\nexport function createTableBlock(\n partial: Partial<TableBlock> = {},\n): TableBlock {\n const base: TableBlock = {\n id: generateId(),\n type: \"table\",\n rows: createDefaultTableRows(3, 3),\n ...TABLE_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as TableBlock;\n return applyDefaults(base, partial);\n}\n\nexport function createCountdownBlock(\n partial: Partial<CountdownBlock> = {},\n): CountdownBlock {\n const base: CountdownBlock = {\n id: generateId(),\n type: \"countdown\",\n ...COUNTDOWN_BLOCK_DEFAULTS,\n styles: createDefaultStyles(),\n } as CountdownBlock;\n return applyDefaults(base, partial);\n}\n\nfunction getFieldDefault(field: CustomBlockField): unknown {\n if (field.type === \"repeatable\") {\n return field.default ?? [];\n }\n if (field.type === \"boolean\") {\n return field.default ?? false;\n }\n if (field.type === \"number\") {\n return field.default ?? 0;\n }\n\n return field.default ?? \"\";\n}\n\nexport function createCustomBlock(\n definition: CustomBlockDefinition,\n): CustomBlock {\n const fieldValues: Record<string, unknown> = {};\n\n for (const field of definition.fields) {\n fieldValues[field.key] = getFieldDefault(field);\n }\n\n const styles = applyDefaults(createDefaultStyles(), definition.defaultStyles);\n\n return {\n id: generateId(),\n type: \"custom\",\n customType: definition.type,\n fieldValues,\n styles,\n ...(definition.dataSource ? { dataSourceFetched: false } : {}),\n };\n}\n\nexport function createBlock(\n type: BlockType,\n blockDefaults?: BlockDefaults,\n): Block {\n switch (type) {\n case \"section\":\n return createSectionBlock(blockDefaults?.section);\n case \"title\":\n return createTitleBlock(blockDefaults?.title);\n case \"paragraph\":\n return createParagraphBlock(blockDefaults?.paragraph);\n case \"image\":\n return createImageBlock(blockDefaults?.image);\n case \"button\":\n return createButtonBlock(blockDefaults?.button);\n case \"divider\":\n return createDividerBlock(blockDefaults?.divider);\n case \"video\":\n return createVideoBlock(blockDefaults?.video);\n case \"social\":\n return createSocialIconsBlock(blockDefaults?.social);\n case \"spacer\":\n return createSpacerBlock(blockDefaults?.spacer);\n case \"html\":\n return createHtmlBlock(blockDefaults?.html);\n case \"menu\":\n return createMenuBlock(blockDefaults?.menu);\n case \"table\":\n return createTableBlock(blockDefaults?.table);\n case \"countdown\":\n return createCountdownBlock(blockDefaults?.countdown);\n default:\n throw new Error(`Unknown block type: ${type}`);\n }\n}\n\nexport function cloneBlock(block: Block): Block {\n const cloned = JSON.parse(JSON.stringify(block)) as Block;\n cloned.id = generateId();\n\n if (cloned.type === \"section\") {\n cloned.children = cloned.children.map((column) =>\n column.map((child) => cloneBlock(child)),\n );\n }\n\n return cloned;\n}\n","/**\n * Cycle-safe deep clone via a JSON round-trip.\n *\n * A naked `JSON.stringify` throws `Converting circular structure to JSON`\n * if the tree is self-referencing — e.g. a DOM element carrying a Sortable\n * expando back-ref (`HTMLDivElement.SortableXXX -> instance -> el -> div`)\n * leaks into block data through a drag handler inside a section. Both the\n * editor's public `getContent()` export path and the history snapshot path\n * must tolerate that: we drop the offending back-ref from the clone rather\n * than throw. Losing a transient DOM expando is harmless; the block data is\n * intact.\n *\n * The `WeakSet` replacer omits any object already seen on the current path,\n * which covers every cyclic shape. Template content is tree-shaped (and is\n * serialized to JSON for storage anyway), so dropping repeated references\n * never costs real data.\n */\nexport function safeClone<T>(value: T): T {\n const seen = new WeakSet<object>();\n return JSON.parse(\n JSON.stringify(value, (_key, val) => {\n if (typeof val === \"object\" && val !== null) {\n if (seen.has(val)) return undefined;\n seen.add(val);\n }\n return val;\n }),\n ) as T;\n}\n","/**\n * Minimal, allocation-free primitives for reading an HTML open tag out of a\n * markup string. They exist because merge-tag markup is rewritten from a raw\n * string in two packages — `@templatical/types` (label / sample resolution for\n * the previews) and `@templatical/renderer` (converting spans back to tokens on\n * export) — and both need the same two answers: where does this open tag end,\n * and what is the value of one of its attributes.\n *\n * Both are hand-written character scans rather than regexes. A regex over an\n * attribute string with no closing quote backtracks across the whole input,\n * which is the ReDoS class the span scanners were rewritten to avoid; a scan\n * that only moves forward cannot.\n */\n\nconst QUOTES = new Set(['\"', \"'\"]);\n\n/**\n * Index of the `>` that closes the open tag whose name ends at `from`, or `-1`\n * when the tag never closes.\n *\n * Quote-aware: a `>` inside a quoted attribute value does not end the tag.\n * That matters because a consumer may configure a merge-tag syntax whose tag\n * values contain `<` / `>` — Smarty-style `<% $email %>` — and HTML attribute\n * serialization escapes only `&`, `\"` and U+00A0, so those characters reach the\n * markup literally. A plain `indexOf(\">\")` stops mid-attribute there and every\n * caller downstream sees a truncated attribute string.\n *\n * An unterminated quote yields `-1` rather than a guessed tag end: callers\n * treat that as \"not markup I can rewrite\" and emit the input untouched, which\n * is the safe answer for malformed HTML.\n */\nexport function findOpenTagEnd(html: string, from: number): number {\n let quote: string | null = null;\n for (let i = from; i < html.length; i++) {\n const char = html[i];\n if (quote !== null) {\n if (char === quote) quote = null;\n continue;\n }\n if (QUOTES.has(char)) {\n quote = char;\n continue;\n }\n if (char === \">\") return i;\n }\n return -1;\n}\n\nfunction isAttrNameChar(char: string): boolean {\n return (\n char !== \" \" &&\n char !== \"\\t\" &&\n char !== \"\\n\" &&\n char !== \"\\r\" &&\n char !== \"\\f\" &&\n char !== \"=\" &&\n char !== \">\" &&\n char !== \"/\"\n );\n}\n\nfunction isWhitespace(char: string): boolean {\n return (\n char === \" \" ||\n char === \"\\t\" ||\n char === \"\\n\" ||\n char === \"\\r\" ||\n char === \"\\f\"\n );\n}\n\nconst NAMED_ENTITIES: Record<string, string> = {\n amp: \"&\",\n lt: \"<\",\n gt: \">\",\n quot: '\"',\n apos: \"'\",\n nbsp: \" \",\n};\n\n/**\n * Decode the character references an HTML serializer writes into an attribute\n * value, so callers get the characters the author wrote.\n *\n * Deliberately small: the named set a serializer actually emits, plus numeric\n * references. An unknown or malformed reference is left as literal text, which\n * is what a browser does. Single-pass — `&amp;lt;` decodes to the text `&lt;`\n * and stops, never to `<`, so an escaped entity inside a consumer's tag value\n * survives as itself.\n */\nfunction decodeAttrEntities(value: string): string {\n if (!value.includes(\"&\")) return value;\n return value.replace(\n /&(#[0-9]+|#[xX][0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);/g,\n (match, ref: string) => {\n if (ref[0] === \"#\") {\n const code =\n ref[1] === \"x\" || ref[1] === \"X\"\n ? Number.parseInt(ref.slice(2), 16)\n : Number.parseInt(ref.slice(1), 10);\n // Surrogates and out-of-range code points would throw; leave them be.\n if (!Number.isFinite(code) || code < 1 || code > 0x10ffff) return match;\n if (code >= 0xd800 && code <= 0xdfff) return match;\n return String.fromCodePoint(code);\n }\n return NAMED_ENTITIES[ref.toLowerCase()] ?? match;\n },\n );\n}\n\n/**\n * The value of the `name` attribute within an open tag's attribute string (the\n * text between the tag name and its closing `>`), or `null` when the attribute\n * is absent or its quoted value is never closed.\n *\n * Handles double-quoted, single-quoted and unquoted values, and matches the\n * attribute name case-insensitively, as HTML does. The value is\n * entity-decoded, because that is what the attribute means: the editor\n * serializes a tag value of `<% $email %>` as\n * `data-merge-tag=\"&lt;% $email %&gt;\"`, and every caller compares the result\n * against a configured merge-tag token written in raw characters.\n */\nexport function getTagAttrValue(attrs: string, name: string): string | null {\n const target = name.toLowerCase();\n let i = 0;\n\n while (i < attrs.length) {\n while (i < attrs.length && (isWhitespace(attrs[i]) || attrs[i] === \"/\")) {\n i++;\n }\n if (i >= attrs.length) break;\n\n const nameStart = i;\n while (i < attrs.length && isAttrNameChar(attrs[i])) i++;\n const attrName = attrs.substring(nameStart, i).toLowerCase();\n if (attrName === \"\") {\n // Not a name character and not whitespace — e.g. a stray `=`. Step over\n // it so the scan always advances.\n i++;\n continue;\n }\n\n while (i < attrs.length && isWhitespace(attrs[i])) i++;\n if (attrs[i] !== \"=\") {\n // A boolean attribute (`hidden`); the cursor already sits on the next\n // attribute name, so don't consume anything here.\n continue;\n }\n i++; // past `=`\n while (i < attrs.length && isWhitespace(attrs[i])) i++;\n\n const quote = attrs[i];\n if (QUOTES.has(quote)) {\n const valueStart = i + 1;\n const valueEnd = attrs.indexOf(quote, valueStart);\n if (valueEnd === -1) return null; // unterminated value — nothing readable follows\n if (attrName === target)\n return decodeAttrEntities(attrs.substring(valueStart, valueEnd));\n i = valueEnd + 1;\n continue;\n }\n\n const valueStart = i;\n while (i < attrs.length && !isWhitespace(attrs[i]) && attrs[i] !== \">\") i++;\n if (attrName === target)\n return decodeAttrEntities(attrs.substring(valueStart, i));\n }\n\n return null;\n}\n","import type { MergeTag } from \"./config\";\nimport { findOpenTagEnd, getTagAttrValue } from \"./html-scan\";\n\n// --- Syntax Presets ---\n\nexport interface SyntaxPreset {\n value: RegExp;\n logic: RegExp;\n}\n\nexport type SyntaxPresetName =\n \"liquid\" | \"handlebars\" | \"mailchimp\" | \"ampscript\";\n\nexport const SYNTAX_PRESETS: Record<SyntaxPresetName, SyntaxPreset> = {\n liquid: { value: /\\{\\{.+?\\}\\}/g, logic: /\\{%-?\\s*(\\w+).*?-?%\\}/g },\n handlebars: {\n value: /\\{\\{\\{?.+?\\}?\\}\\}/g,\n logic: /\\{\\{[#/](\\w+).*?\\}\\}/g,\n },\n mailchimp: { value: /\\*\\|\\w+\\|\\*/g, logic: /\\*\\|(\\w+)[:|].*?\\|\\*/g },\n ampscript: { value: /%%=.+?=%%/g, logic: /%%\\[\\s*(\\w+).*?\\]%%/g },\n};\n\nconst SYNTAX_TRIGGER_CHARS: Record<SyntaxPresetName, string> = {\n liquid: \"{{\",\n handlebars: \"{{\",\n mailchimp: \"*|\",\n ampscript: \"%%=\",\n};\n\nconst SYNTAX_CLOSING_CHARS: Record<SyntaxPresetName, string> = {\n liquid: \"}}\",\n handlebars: \"}}\",\n mailchimp: \"|*\",\n ampscript: \"=%%\",\n};\n\n/**\n * Resolves the autocomplete trigger string for a syntax preset.\n * Returns null when the syntax doesn't match any built-in preset\n * (custom regex syntax — autocomplete cannot be enabled safely).\n */\nexport function getSyntaxTriggerChar(syntax: SyntaxPreset): string | null {\n for (const name of Object.keys(SYNTAX_PRESETS) as SyntaxPresetName[]) {\n if (SYNTAX_PRESETS[name].value.source === syntax.value.source) {\n return SYNTAX_TRIGGER_CHARS[name];\n }\n }\n return null;\n}\n\n/**\n * Resolves the closing delimiter for a syntax preset (e.g. `}}` for liquid,\n * `|*` for mailchimp). The autocomplete trigger detector uses it to tell an\n * open tag (`{{first`) from a completed one (`{{first_name}}`) so the popup\n * doesn't reappear over a finished tag. Returns null for custom syntaxes —\n * the same set for which `getSyntaxTriggerChar` returns null.\n */\nexport function getSyntaxClosingChar(syntax: SyntaxPreset): string | null {\n for (const name of Object.keys(SYNTAX_PRESETS) as SyntaxPresetName[]) {\n if (SYNTAX_PRESETS[name].value.source === syntax.value.source) {\n return SYNTAX_CLOSING_CHARS[name];\n }\n }\n return null;\n}\n\nexport function resolveSyntax(\n syntax?: SyntaxPresetName | SyntaxPreset,\n): SyntaxPreset {\n if (!syntax) {\n return SYNTAX_PRESETS.liquid;\n }\n\n if (typeof syntax === \"string\") {\n return SYNTAX_PRESETS[syntax] ?? SYNTAX_PRESETS.liquid;\n }\n\n return syntax;\n}\n\n// --- Merge Tag Utilities ---\n\nfunction anchoredRegex(pattern: RegExp): RegExp {\n const source = pattern.source;\n const flags = pattern.flags.replace(\"g\", \"\");\n return new RegExp(`^${source}$`, flags);\n}\n\nexport function isMergeTagValue(value: string, syntax: SyntaxPreset): boolean {\n const trimmed = value?.trim() || \"\";\n // Handlebars (and similar) value regex is liberal enough to also match\n // logic tags like `{{#each items}}`. Exclude logic-shaped tags so callers\n // that rely on this discriminator (UI segmentation, label rendering)\n // don't misclassify them.\n if (anchoredRegex(syntax.logic).test(trimmed)) {\n return false;\n }\n return anchoredRegex(syntax.value).test(trimmed);\n}\n\nexport function getMergeTagLabel(value: string, mergeTags: MergeTag[]): string {\n const found = mergeTags.find((p) => p.value === value);\n if (found) {\n return found.label;\n }\n return value;\n}\n\nexport function resolveHtmlMergeTagLabels(\n html: string,\n mergeTags: MergeTag[],\n): string {\n return rewriteSpanByAttr(html, \"data-merge-tag\", (value) =>\n getMergeTagLabel(value, mergeTags),\n );\n}\n\n/**\n * The example value configured for `value`, or `undefined` when the tag is\n * unknown or declares none. Unlike `getMergeTagLabel`, which falls back to the\n * raw token, absence is reported as `undefined` so callers can choose their own\n * fallback — Sample mode falls back to the label, never to a raw token.\n */\nexport function getMergeTagSample(\n value: string,\n mergeTags: MergeTag[],\n): string | undefined {\n return mergeTags.find((p) => p.value === value)?.sample;\n}\n\n/**\n * Rewrite rich-text HTML for the previews' Sample mode.\n *\n * A tag **with** a sample is replaced by that value as bare text, wrapper and\n * all — dropping the `<span>` is what removes the merge-tag highlight, so the\n * value reads as ordinary content. A tag **without** one keeps its span and\n * shows its label, so it stays visibly dynamic; that difference is deliberate,\n * and it doubles as a signal that a sample is still unset.\n *\n * The unwrapped output is inert — `data-merge-tag` is gone from it — so this\n * result must only ever reach a preview surface, never `getContent()`, an\n * export or a send.\n */\nexport function substituteHtmlMergeTagSamples(\n html: string,\n mergeTags: MergeTag[],\n): string {\n return rewriteSpanByAttr(\n html,\n \"data-merge-tag\",\n (value) =>\n escapeTagText(\n getMergeTagSample(value, mergeTags) ??\n getMergeTagLabel(value, mergeTags),\n ),\n { unwrap: (value) => getMergeTagSample(value, mergeTags) !== undefined },\n );\n}\n\n/**\n * Whether any configured tag declares a `sample`.\n *\n * Gates the whole feature: with no samples anywhere the previews' Sample view\n * would be identical to the Label view, so the editor defaults to Label and\n * renders no toggle. That keeps the feature invisible until a consumer opts in.\n */\nexport function hasMergeTagSamples(mergeTags: MergeTag[]): boolean {\n return mergeTags.some((tag) => tag.sample !== undefined);\n}\n\n/**\n * Replace declared tokens with their sample values in a plain string — used for\n * values that reach no label-resolving component, namely `html` block content\n * and custom-block field values.\n *\n * Matching is on the exact configured `value`, so consumer text cannot\n * false-positive and `href=\"{{link}}\"` resolves correctly. Tags without a\n * sample are left untouched, which is what keeps a missing sample visible.\n */\nexport function substituteTextMergeTagSamples(\n text: string,\n mergeTags: MergeTag[],\n): string {\n if (!text) return text;\n\n let out = text;\n for (const tag of mergeTags) {\n if (tag.sample === undefined) continue;\n // split/join rather than a RegExp: the token is a literal, so this needs no\n // escaping of regex metacharacters (`{`, `|`, `*` all appear in real syntaxes).\n out = out.split(tag.value).join(tag.sample);\n }\n return out;\n}\n\n/**\n * Escape a sample before it is inlined into rich-text HTML. Samples are\n * consumer-authored strings, so `<`, `&` and quotes would otherwise alter the\n * surrounding markup.\n */\nfunction escapeTagText(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nexport function containsMergeTag(value: string, syntax: SyntaxPreset): boolean {\n if (!value) return false;\n\n const valueRegex = new RegExp(syntax.value.source, syntax.value.flags);\n const logicRegex = new RegExp(syntax.logic.source, syntax.logic.flags);\n\n return valueRegex.test(value) || logicRegex.test(value);\n}\n\nexport function isLogicMergeTagValue(\n value: string,\n syntax: SyntaxPreset,\n): boolean {\n return anchoredRegex(syntax.logic).test(value?.trim() || \"\");\n}\n\nexport function getLogicMergeTagKeyword(\n value: string,\n syntax: SyntaxPreset,\n): string {\n const regex = new RegExp(\n syntax.logic.source,\n syntax.logic.flags.replace(\"g\", \"\"),\n );\n const match = value.match(regex);\n return match && match[1] ? match[1].toUpperCase() : value;\n}\n\nexport function resolveHtmlLogicMergeTagLabels(\n html: string,\n syntax: SyntaxPreset,\n): string {\n return rewriteSpanByAttr(html, \"data-logic-merge-tag\", (value) =>\n getLogicMergeTagKeyword(value, syntax),\n );\n}\n\n/**\n * Walk `html` and rewrite the inner text of every `<span … {attrName}=\"…\">…</span>`\n * by passing the attribute value through `relabel`. Linear in `html.length`:\n * each `indexOf` advances the cursor monotonically, and no regex backtracking\n * can run over the whole string.\n *\n * Replaces the original `/<span[^>]*…[^>]*>(.*?)<\\/span>/g` pattern, which\n * was polynomial-ReDoS over inputs that contained many `<span` starts with\n * no closing `>`.\n *\n * Tag boundaries and attribute values are resolved by `findOpenTagEnd` /\n * `getTagAttrValue`, which are quote-aware. A merge-tag value may itself\n * contain `<` / `>` when the consumer configures a custom syntax — Smarty-style\n * `<% $email %>` — and those characters survive attribute serialization\n * verbatim, so anything that treats the first `>` as the tag end reads a\n * truncated attribute and resolves nothing.\n */\nfunction rewriteSpanByAttr(\n html: string,\n attrName: string,\n relabel: (value: string) => string,\n options?: { unwrap?: (value: string) => boolean },\n): string {\n let out = \"\";\n let i = 0;\n while (i < html.length) {\n const open = html.indexOf(\"<span\", i);\n if (open === -1) {\n out += html.substring(i);\n break;\n }\n const afterTagName = html[open + 5];\n if (\n afterTagName !== \">\" &&\n afterTagName !== \" \" &&\n afterTagName !== \"\\t\" &&\n afterTagName !== \"\\n\" &&\n afterTagName !== \"\\r\" &&\n afterTagName !== \"/\"\n ) {\n out += html.substring(i, open + 5);\n i = open + 5;\n continue;\n }\n const openEnd = findOpenTagEnd(html, open + 5);\n if (openEnd === -1) {\n out += html.substring(i);\n break;\n }\n const closeStart = html.indexOf(\"</span>\", openEnd + 1);\n if (closeStart === -1) {\n out += html.substring(i);\n break;\n }\n const attrs = html.substring(open + 5, openEnd);\n const value = getTagAttrValue(attrs, attrName);\n if (value === null) {\n // This `<span>` isn't the one we're looking for — emit up to and\n // including the `<span` literal and let the next iteration scan\n // inward. Skipping straight to the matching `</span>` would swallow\n // any nested merge-tag span in the same loop iteration.\n out += html.substring(i, open + 5);\n i = open + 5;\n continue;\n }\n const newLabel = relabel(value);\n if (options?.unwrap?.(value)) {\n // Emit the text before the span, then the replacement alone — the open\n // and close tags are dropped, so the span's styling (the merge-tag\n // highlight) no longer applies to it. Decided per tag, so a tag that\n // resolved to a sample loses the highlight while one that fell back to\n // its label keeps it.\n out += html.substring(i, open);\n out += newLabel;\n } else {\n out += html.substring(i, openEnd + 1);\n out += newLabel;\n out += \"</span>\";\n }\n i = closeStart + 7;\n }\n return out;\n}\n","import type { TemplateContent } from \"./template\";\n\n/**\n * Context handed to `resolvePreview`.\n *\n * `recipient` is present only on surfaces that have one — the test-email\n * dialog — and absent in the editor's own preview mode. Treat its absence as\n * \"no particular recipient\", not as an error: a resolver should still return\n * something renderable, typically its own default or sample data.\n */\nexport interface PreviewResolveContext {\n /**\n * The template as it currently stands. Already a copy — mutating it has no\n * effect on the editor, and returning it unchanged is a valid no-op.\n */\n content: TemplateContent;\n /** The address the preview is being shown for, when the surface has one. */\n recipient?: string;\n}\n\n/**\n * Resolves a template for display on preview surfaces — typically substituting\n * merge tags and **evaluating logic tags** against real data.\n *\n * Logic tags are the reason this hook exists. `MergeTag.sample` already covers\n * value tags client-side, but branching (`{% if %}` … `{% endif %}`) cannot be\n * evaluated in a browser for every supported syntax — mailchimp and ampscript\n * logic are server-side dialects — so only the consumer's own backend can do it.\n *\n * Display-only. The returned content reaches preview surfaces and nothing else:\n * it is never written to the editor's state, never returned from\n * `getContent()`, never sent, and never exported. Rejecting is safe — the\n * preview falls back to the unresolved template and says so.\n */\nexport type ResolvePreview = (\n context: PreviewResolveContext,\n) => Promise<TemplateContent>;\n\n/**\n * Whether `value` is shaped enough like `TemplateContent` to render.\n *\n * A resolver is consumer code returning data that often came from an HTTP\n * response, so the declared return type guarantees nothing at runtime. A\n * mis-shaped result is treated as a resolution failure — the preview degrades\n * to the unresolved template — rather than throwing inside the render.\n */\nexport function isRenderableTemplateContent(\n value: unknown,\n): value is TemplateContent {\n if (typeof value !== \"object\" || value === null) return false;\n const candidate = value as { blocks?: unknown };\n return Array.isArray(candidate.blocks);\n}\n","import type { SyntaxPreset, SyntaxPresetName } from \"./merge-tags\";\nimport type { TemplateSettings } from \"./template\";\n\nexport type ViewportSize = \"desktop\" | \"mobile\";\n\nexport type UiTheme = \"light\" | \"dark\" | \"auto\";\n\nexport interface CustomFont {\n name: string;\n url: string;\n fallback?: string;\n}\n\nexport interface FontsConfig {\n defaultFallback?: string;\n defaultFont?: string;\n customFonts?: CustomFont[];\n /**\n * Restrict which of the seven built-in fonts the font picker offers.\n *\n * - `true` / omitted — every built-in is offered (the default).\n * - `false` — no built-ins; the picker lists only `customFonts`. Use this to\n * keep authors on an embedded brand kit's typefaces.\n * - `string[]` — an allowlist of built-in names to keep, matched\n * case-insensitively (`['Georgia', 'arial']`). A name that isn't a built-in\n * is logged with a warning and skipped — consistent with how\n * `paletteBlocks` treats an unknown entry — so a typo narrows the list\n * rather than silently offering all seven.\n *\n * Independent of `customFonts`: excluding the built-ins never removes a custom\n * font, and a custom font can still be the `defaultFont` when every built-in\n * is excluded. Filtering the picker never affects rendering — content already\n * using an excluded built-in still resolves to its proper fallback stack.\n */\n builtIns?: boolean | string[];\n}\n\nexport interface ColorsConfig {\n /**\n * Preset colors rendered as a clickable grid inside every color picker\n * popover. Each entry is a hex string (`'#0b5cff'`); clicking one applies it,\n * and the preset matching the current value is marked selected (matched\n * case-insensitively). The grid supplements the wheel and hex input unless\n * `allowCustom` is `false`.\n */\n presets?: string[];\n /**\n * Allow free-form color entry — the wheel and hex input — alongside the\n * presets. Defaults to `true`.\n *\n * Set to `false` together with `presets` to lock authors to the preset\n * palette: the popover then offers only the preset grid, with no wheel or hex\n * field (a white-label / brand-kit constraint). Ignored with a warning when\n * no `presets` are configured — hiding the only inputs would leave the picker\n * with no way to choose a color.\n */\n allowCustom?: boolean;\n}\n\nexport interface TemplateSettingsConfig {\n /**\n * Which template settings the Settings panel exposes.\n *\n * - `true` / omitted — every setting is editable (the default).\n * - `false` — none; the Settings tab itself stops rendering.\n * - `Array<keyof TemplateSettings>` — an allowlist of settings to keep\n * (`['width', 'backgroundColor', 'fontFamily']`). A card renders when at\n * least one of its settings survives, so hiding `locale` removes the\n * Language card and hiding `preheaderText` removes the Preheader card. An\n * empty array means none, the same as `false`.\n *\n * The list narrows, it never reorders — settings sit in fixed cards, so\n * unlike `paletteBlocks` there is no order to express. An entry that isn't a\n * `TemplateSettings` member is a compile error for TypeScript callers, and is\n * logged with a warning and skipped at runtime, so a typo narrows the panel\n * rather than silently restoring every setting.\n *\n * Presentation only. Hiding a setting never changes its value: whatever the\n * loaded content carries keeps rendering and keeps round-tripping through\n * `getContent()`. Set the values you hide from the content you hand the\n * editor — `init({ content })`, or your own `templates.load`.\n */\n fields?: boolean | Array<keyof TemplateSettings>;\n}\n\nexport interface ExportResult {\n html: string;\n mjml: string;\n}\n\nexport interface MergeTag {\n label: string;\n value: string;\n /**\n * Optional grouping label used by the built-in merge tag picker to\n * section the list. When no tag in the configured array carries\n * `group`, the picker renders a plain flat list with no headers.\n * Ignored by the renderer and by typing-autocomplete.\n */\n group?: string;\n /**\n * Optional helper text shown beneath the tag in the built-in merge\n * tag picker. Not rendered anywhere else (toolbar, autocomplete,\n * MJML output) and not stored on the inserted document node.\n */\n description?: string;\n /**\n * Example value shown in place of this tag on preview surfaces — e.g.\n * `\"Ada\"` for `{{first_name}}` — so a preview reads like a delivered\n * email instead of a list of field names.\n *\n * Display-only, and only in the previews' Sample mode: never written to\n * the template, never sent, never present in MJML output. Setting it is\n * the whole opt-in; there is no accompanying flag. A tag without a\n * sample keeps showing its `label`.\n */\n sample?: string;\n}\n\nexport interface MediaResult {\n url: string;\n alt?: string;\n}\n\nexport interface MergeTagsConfig {\n syntax?: SyntaxPresetName | SyntaxPreset;\n tags?: MergeTag[];\n onRequest?: () => Promise<MergeTag | null>;\n /**\n * Enables typing-based autocomplete in rich text blocks and in every\n * merge-tag-enabled input/textarea field (toolbars, template settings,\n * custom-block text fields). When the user types the syntax opener\n * (e.g. `{{`), a popup lists matching `tags`. Both surfaces share the\n * same popup, so behavior is identical.\n *\n * Defaults to `true`. Effective only when `tags` is non-empty AND\n * `syntax` matches a built-in preset (custom regex syntaxes cannot be\n * mapped to a trigger string and silently disable autocomplete).\n */\n autocomplete?: boolean;\n}\n\n/**\n * A standalone logic tag — a control-flow token (e.g. `{% else %}`) inserted\n * at the cursor. Distinct from data merge tags; rendered as a keyword badge.\n */\nexport interface LogicTag {\n label: string;\n value: string;\n /** Optional grouping label used to section the logic picker. */\n group?: string;\n /** Optional helper text shown beneath the tag in the picker. */\n description?: string;\n}\n\n/**\n * A paired logic construct — an opening tag (`before`) and its matching closer\n * (`after`), e.g. `{% if vip %}` … `{% endif %}`. Selecting one inserts both\n * pills at once, wrapping the selection (or with the caret placed between\n * them). Shares its `before`/`after` shape with `DisplayCondition`.\n */\nexport interface LogicPair {\n label: string;\n /** Opening logic tag, including delimiters (e.g. `{% if vip %}`). */\n before: string;\n /** Closing logic tag, including delimiters (e.g. `{% endif %}`). */\n after: string;\n /** Optional grouping label used to section the logic picker. */\n group?: string;\n /** Optional helper text shown beneath the pair in the picker. */\n description?: string;\n}\n\n/**\n * Standalone logic-tag configuration, independent of `mergeTags`. `tags` are\n * single control-flow tokens inserted at the cursor; `pairs` are open/close\n * constructs inserted around the selection. Both appear in the built-in logic\n * picker, grouped together by `group`. Typed/pasted logic highlighting is\n * always on regardless of this.\n */\nexport interface LogicTagsConfig {\n tags?: LogicTag[];\n pairs?: LogicPair[];\n /**\n * Consumer-owned picker. When set, the \"Insert logic\" affordance calls this\n * instead of the built-in logic picker — return the chosen tag/pair (or\n * `null` to cancel). Mirrors `MergeTagsConfig.onRequest`; lets consumers use\n * their own UI and custom syntax. Precedence: `onRequest` → built-in picker.\n */\n onRequest?: () => Promise<LogicTag | LogicPair | null>;\n}\n\nexport interface DisplayCondition {\n label: string;\n before: string;\n after: string;\n group?: string;\n description?: string;\n}\n\nexport interface DisplayConditionsConfig {\n conditions: DisplayCondition[];\n allowCustom?: boolean;\n}\n\nexport interface ThemeOverrides {\n bg?: string;\n bgElevated?: string;\n bgHover?: string;\n bgActive?: string;\n border?: string;\n borderLight?: string;\n text?: string;\n textMuted?: string;\n textDim?: string;\n primary?: string;\n primaryHover?: string;\n primaryLight?: string;\n secondary?: string;\n secondaryHover?: string;\n secondaryLight?: string;\n success?: string;\n successLight?: string;\n warning?: string;\n warningLight?: string;\n danger?: string;\n dangerLight?: string;\n canvasBg?: string;\n dark?: Omit<ThemeOverrides, \"dark\">;\n}\n\nexport class SdkError extends Error {\n constructor(\n message: string,\n public readonly statusCode?: number,\n ) {\n super(message);\n this.name = \"SdkError\";\n }\n\n get isNotFound(): boolean {\n return this.statusCode === 404;\n }\n\n get isUnauthorized(): boolean {\n return this.statusCode === 401;\n }\n\n get isServerError(): boolean {\n return this.statusCode !== undefined && this.statusCode >= 500;\n }\n}\n"],"mappings":";AAgEA,IAAa,IAAwD;CACnE,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;AACL,GAgBa,IAAoB;CAE/B,cAAc;CAEd,aAAa;CAEb,iBAAiB;CAEjB,iBAAiB;AACnB,GC1Ea,IAA8D;CACzE,UAAU;EACR,OAAO;EACP,MAAM;CACR;CACA,SAAS;EACP,OAAO;EACP,MAAM;CACR;CACA,WAAW;EACT,OAAO;EACP,MAAM;CACR;CACA,UAAU;EACR,OAAO;EACP,MAAM;CACR;CACA,SAAS;EACP,OAAO;EACP,MAAM;CACR;CACA,QAAQ;EACN,OAAO;EACP,MAAM;CACR;CACA,WAAW;EACT,OAAO;EACP,MAAM;CACR;CACA,OAAO;EACL,OAAO;EACP,MAAM;CACR;CACA,SAAS;EACP,OAAO;EACP,MAAM;CACR;CACA,UAAU;EACR,OAAO;EACP,MAAM;CACR;CACA,UAAU;EACR,OAAO;EACP,MAAM;CACR;CACA,SAAS;EACP,OAAO;EACP,MAAM;CACR;CACA,UAAU;EACR,OAAO;EACP,MAAM;CACR;CACA,QAAQ;EACN,OAAO;EACP,MAAM;CACR;CACA,QAAQ;EACN,OAAO;EACP,MAAM;CACR;CACA,UAAU;EACR,OAAO;EACP,MAAM;CACR;CACA,SAAS;EACP,OAAO;EACP,MAAM;CACR;AACF;;;ACvEA,SAAgB,EAAU,GAAqC;CAC7D,OAAO,EAAM,SAAS;AACxB;AAEA,SAAgB,EAAQ,GAAmC;CACzD,OAAO,EAAM,SAAS;AACxB;AAEA,SAAgB,EAAY,GAAuC;CACjE,OAAO,EAAM,SAAS;AACxB;AAEA,SAAgB,EAAQ,GAAmC;CACzD,OAAO,EAAM,SAAS;AACxB;AAEA,SAAgB,EAAS,GAAoC;CAC3D,OAAO,EAAM,SAAS;AACxB;AAEA,SAAgB,EAAU,GAAqC;CAC7D,OAAO,EAAM,SAAS;AACxB;AAEA,SAAgB,EAAQ,GAAmC;CACzD,OAAO,EAAM,SAAS;AACxB;AAEA,SAAgB,EAAc,GAAyC;CACrE,OAAO,EAAM,SAAS;AACxB;AAEA,SAAgB,EAAS,GAAoC;CAC3D,OAAO,EAAM,SAAS;AACxB;AAEA,SAAgB,GAAO,GAAkC;CACvD,OAAO,EAAM,SAAS;AACxB;AAEA,SAAgB,GAAO,GAAkC;CACvD,OAAO,EAAM,SAAS;AACxB;AAEA,SAAgB,GAAQ,GAAmC;CACzD,OAAO,EAAM,SAAS;AACxB;AAMA,SAAgB,GAAc,GAAoC;CAChE,OAAO,EAAM,SAAS;AACxB;;;AC7BA,IAAa,IAAqD;CAChE,SAAS;CACT,OAAO;CACP,WAAW;AACb,GAEa,IAA6D,EACxE,SAAS,8BACX,GAEa,IAAqD;CAChE,KAAK;CACL,KAAK;CACL,OAAO;CACP,OAAO;AACT,GAEa,IAAuD;CAClE,MAAM;CACN,KAAK;CACL,iBAAiB;CACjB,WAAW;CACX,cAAc;CACd,UAAU;CACV,eAAe;EAAE,KAAK;EAAI,OAAO;EAAI,QAAQ;EAAI,MAAM;CAAG;CAC1D,OAAO;AACT,GAEa,IAAyD;CACpE,WAAW;CACX,OAAO;CACP,WAAW;CACX,OAAO;AACT,GAEa,IAAyD,EACpE,SAAS,IACX,GAEa,IAAqD;CAChE,KAAK;CACL,cAAc;CACd,KAAK;CACL,OAAO;CACP,OAAO;AACT,GAEa,KAAkE;CAC7E,WAAW;CACX,UAAU;CACV,SAAS;CACT,OAAO;AACT,GAEa,IAAuD,EAClE,QAAQ,GACV,GAEa,IAAmD,EAC9D,SAAS,GACX,GAIa,IAAmD;CAC9D,UAAU;CACV,WAAW;CACX,WAAW;CACX,gBAAgB;CAChB,SAAS;AACX,GAIa,IAAqD;CAChE,cAAc;CACd,aAAa;CACb,aAAa;CACb,aAAa;CACb,UAAU;CACV,WAAW;AACb,GAEa,IAA6D;CACxE,YAAY;CACZ,UAAU;CACV,UAAU;CACV,WAAW;CACX,aAAa;CACb,aAAa;CACb,WAAW;CACX,eAAe;CACf,YAAY;CACZ,YAAY;CACZ,eAAe;CACf,iBAAiB;CACjB,WAAW;CACX,YAAY;CACZ,cAAc;CACd,cAAc;CACd,gBAAgB;CAChB,iBAAiB;CACjB,cAAc;AAChB,GAEa,KAAkD;CAC7D,OAAO;CACP,WAAW;CACX,OAAO;CACP,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,MAAM;CACN,MAAM;CACN,OAAO;CACP,WAAW;AACb,GAEa,IAA8C;CACzD,OAAO;CACP,iBAAiB;CACjB,WAAW;CACX,eAAe;CACf,YAAY;CACZ,QAAQ;AACV;AAEA,SAAS,EAAc,GAAkD;CACvE,OACE,OAAO,KAAU,cACjB,KACA,CAAC,MAAM,QAAQ,CAAK,KACpB,OAAO,eAAe,CAAK,MAAM,OAAO;AAE5C;AAEA,SAAgB,EACd,GACA,GACG;CACH,IAAM,IAAS,EAAE,GAAG,EAAK;CAEzB,KAAK,IAAM,KAAO,OAAO,KAAK,CAAS,GAAqB;EAC1D,IAAM,IAAU,EAAK,IACf,IAAc,EAAU;EAE1B,MAAgB,KAAA,MAIpB,AAME,EAAO,KANL,EAAc,CAAO,KAAK,EAAc,CAAW,IACvC,EACZ,GACA,CACF,IAEc;CAElB;CAEA,OAAO;AACT;;;AC/JA,SAAgB,GACd,IAAoB,SACpB,GACiB;CACjB,OAAO;EACL,QAAQ,CAAC;EACT,UAAU;GACR,GAAG;GACH,YAAY;GACZ,GAAG;EACL;CACF;AACF;;;ACnBA,SAAS,EACP,GACA,GACG;CAEH,OADI,CAAC,KAAW,OAAO,KAAK,CAAO,CAAC,CAAC,WAAW,IAAU,IACnD,EAAkB,GAAM,CAAO;AACxC;AAEA,SAAgB,IAAqB;CACnC,OAAO,OAAO,WAAW;AAC3B;AAEA,SAAS,GAAqB,IAAQ,GAAiB;CACrD,OAAO;EAAE,KAAK;EAAO,OAAO;EAAO,QAAQ;EAAO,MAAM;CAAM;AAChE;AAEA,SAAS,EAAoB,IAAU,IAAiB;CACtD,OAAO,EACL,SAAS,GAAqB,CAAO,EACvC;AACF;AAEA,SAAgB,EACd,IAA+B,CAAC,GACpB;CAOZ,OAAO,EAAc;EALnB,IAAI,EAAW;EACf,MAAM;EACN,GAAG;EACH,QAAQ,EAAoB;CAET,GAAM,CAAO;AACpC;AAEA,SAAgB,EACd,IAAmC,CAAC,GACpB;CAOhB,OAAO,EAAc;EALnB,IAAI,EAAW;EACf,MAAM;EACN,GAAG;EACH,QAAQ,EAAoB;CAET,GAAM,CAAO;AACpC;AAEA,SAAgB,EACd,IAA+B,CAAC,GACpB;CAOZ,OAAO,EAAc;EALnB,IAAI,EAAW;EACf,MAAM;EACN,GAAG;EACH,QAAQ,EAAoB;CAET,GAAM,CAAO;AACpC;AAEA,SAAgB,EACd,IAAgC,CAAC,GACpB;CAOb,OAAO,EAAc;EALnB,IAAI,EAAW;EACf,MAAM;EACN,GAAG;EACH,QAAQ,EAAoB;CAET,GAAM,CAAO;AACpC;AAEA,SAAgB,EACd,IAAiC,CAAC,GACpB;CAOd,OAAO,EAAc;EALnB,IAAI,EAAW;EACf,MAAM;EACN,GAAG;EACH,QAAQ,EAAoB,EAAE;CAEX,GAAM,CAAO;AACpC;AAEA,SAAgB,EACd,IAAiC,CAAC,GACpB;CAQd,OAAO,EAAc;EANnB,IAAI,EAAW;EACf,MAAM;EACN,GAAG;EACH,UAAU,CAAC,CAAC,CAAC;EACb,QAAQ,EAAoB,EAAE;CAEX,GAAM,CAAO;AACpC;AAEA,SAAgB,EACd,IAA+B,CAAC,GACpB;CAOZ,OAAO,EAAc;EALnB,IAAI,EAAW;EACf,MAAM;EACN,GAAG;EACH,QAAQ,EAAoB;CAET,GAAM,CAAO;AACpC;AAEA,SAAgB,EACd,IAAqC,CAAC,GACpB;CAQlB,OAAO,EAAc;EANnB,IAAI,EAAW;EACf,MAAM;EACN,OAAO,CAAC;EACR,GAAG;EACH,QAAQ,EAAoB;CAET,GAAM,CAAO;AACpC;AAEA,SAAgB,EACd,IAAgC,CAAC,GACpB;CAOb,OAAO,EAAc;EALnB,IAAI,EAAW;EACf,MAAM;EACN,GAAG;EACH,QAAQ,EAAoB,CAAC;CAEV,GAAM,CAAO;AACpC;AAEA,SAAgB,EAAgB,IAA8B,CAAC,GAAc;CAO3E,OAAO,EAAc;EALnB,IAAI,EAAW;EACf,MAAM;EACN,GAAG;EACH,QAAQ,EAAoB;CAET,GAAM,CAAO;AACpC;AAEA,SAAgB,EAAgB,IAA8B,CAAC,GAAc;CAQ3E,OAAO,EAAc;EANnB,IAAI,EAAW;EACf,MAAM;EACN,OAAO,CAAC;EACR,GAAG;EACH,QAAQ,EAAoB;CAET,GAAM,CAAO;AACpC;AAEA,SAAS,GAAuB,GAAiB,GAA8B;CAC7E,OAAO,MAAM,KAAK,EAAE,QAAQ,EAAK,UAAU;EACzC,IAAI,EAAW;EACf,OAAO,MAAM,KAAK,EAAE,QAAQ,EAAQ,UAAyB;GAC3D,IAAI,EAAW;GACf,SAAS;EACX,EAAE;CACJ,EAAE;AACJ;AAEA,SAAgB,EACd,IAA+B,CAAC,GACpB;CAQZ,OAAO,EAAc;EANnB,IAAI,EAAW;EACf,MAAM;EACN,MAAM,GAAuB,GAAG,CAAC;EACjC,GAAG;EACH,QAAQ,EAAoB;CAET,GAAM,CAAO;AACpC;AAEA,SAAgB,GACd,IAAmC,CAAC,GACpB;CAOhB,OAAO,EAAc;EALnB,IAAI,EAAW;EACf,MAAM;EACN,GAAG;EACH,QAAQ,EAAoB;CAET,GAAM,CAAO;AACpC;AAEA,SAAS,GAAgB,GAAkC;CAWzD,OAVI,EAAM,SAAS,eACV,EAAM,WAAW,CAAC,IAEvB,EAAM,SAAS,YACV,EAAM,WAAW,KAEtB,EAAM,SAAS,WACV,EAAM,WAAW,IAGnB,EAAM,WAAW;AAC1B;AAEA,SAAgB,GACd,GACa;CACb,IAAM,IAAuC,CAAC;CAE9C,KAAK,IAAM,KAAS,EAAW,QAC7B,EAAY,EAAM,OAAO,GAAgB,CAAK;CAGhD,IAAM,IAAS,EAAc,EAAoB,GAAG,EAAW,aAAa;CAE5E,OAAO;EACL,IAAI,EAAW;EACf,MAAM;EACN,YAAY,EAAW;EACvB;EACA;EACA,GAAI,EAAW,aAAa,EAAE,mBAAmB,GAAM,IAAI,CAAC;CAC9D;AACF;AAEA,SAAgB,GACd,GACA,GACO;CACP,QAAQ,GAAR;EACE,KAAK,WACH,OAAO,EAAmB,GAAe,OAAO;EAClD,KAAK,SACH,OAAO,EAAiB,GAAe,KAAK;EAC9C,KAAK,aACH,OAAO,EAAqB,GAAe,SAAS;EACtD,KAAK,SACH,OAAO,EAAiB,GAAe,KAAK;EAC9C,KAAK,UACH,OAAO,EAAkB,GAAe,MAAM;EAChD,KAAK,WACH,OAAO,EAAmB,GAAe,OAAO;EAClD,KAAK,SACH,OAAO,EAAiB,GAAe,KAAK;EAC9C,KAAK,UACH,OAAO,EAAuB,GAAe,MAAM;EACrD,KAAK,UACH,OAAO,EAAkB,GAAe,MAAM;EAChD,KAAK,QACH,OAAO,EAAgB,GAAe,IAAI;EAC5C,KAAK,QACH,OAAO,EAAgB,GAAe,IAAI;EAC5C,KAAK,SACH,OAAO,EAAiB,GAAe,KAAK;EAC9C,KAAK,aACH,OAAO,GAAqB,GAAe,SAAS;EACtD,SACE,MAAU,MAAM,uBAAuB,GAAM;CACjD;AACF;AAEA,SAAgB,EAAW,GAAqB;CAC9C,IAAM,IAAS,KAAK,MAAM,KAAK,UAAU,CAAK,CAAC;CAS/C,OARA,EAAO,KAAK,EAAW,GAEnB,EAAO,SAAS,cAClB,EAAO,WAAW,EAAO,SAAS,KAAK,MACrC,EAAO,KAAK,MAAU,EAAW,CAAK,CAAC,CACzC,IAGK;AACT;;;ACtSA,SAAgB,GAAa,GAAa;CACxC,IAAM,oBAAO,IAAI,QAAgB;CACjC,OAAO,KAAK,MACV,KAAK,UAAU,IAAQ,GAAM,MAAQ;EACnC,IAAI,OAAO,KAAQ,YAAY,GAAc;GAC3C,IAAI,EAAK,IAAI,CAAG,GAAG;GACnB,EAAK,IAAI,CAAG;EACd;EACA,OAAO;CACT,CAAC,CACH;AACF;;;ACdA,IAAM,oBAAS,IAAI,IAAI,CAAC,MAAK,GAAG,CAAC;AAiBjC,SAAgB,EAAe,GAAc,GAAsB;CACjE,IAAI,IAAuB;CAC3B,KAAK,IAAI,IAAI,GAAM,IAAI,EAAK,QAAQ,KAAK;EACvC,IAAM,IAAO,EAAK;EAClB,IAAI,MAAU,MAAM;GAClB,AAAI,MAAS,MAAO,IAAQ;GAC5B;EACF;EACA,IAAI,EAAO,IAAI,CAAI,GAAG;GACpB,IAAQ;GACR;EACF;EACA,IAAI,MAAS,KAAK,OAAO;CAC3B;CACA,OAAO;AACT;AAEA,SAAS,GAAe,GAAuB;CAC7C,OACE,MAAS,OACT,MAAS,OACT,MAAS,QACT,MAAS,QACT,MAAS,QACT,MAAS,OACT,MAAS,OACT,MAAS;AAEb;AAEA,SAAS,EAAa,GAAuB;CAC3C,OACE,MAAS,OACT,MAAS,OACT,MAAS,QACT,MAAS,QACT,MAAS;AAEb;AAEA,IAAM,KAAyC;CAC7C,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,MAAM;AACR;AAYA,SAAS,EAAmB,GAAuB;CAEjD,OADK,EAAM,SAAS,GAAG,IAChB,EAAM,QACX,wDACC,GAAO,MAAgB;EACtB,IAAI,EAAI,OAAO,KAAK;GAClB,IAAM,IACJ,EAAI,OAAO,OAAO,EAAI,OAAO,MACzB,OAAO,SAAS,EAAI,MAAM,CAAC,GAAG,EAAE,IAChC,OAAO,SAAS,EAAI,MAAM,CAAC,GAAG,EAAE;GAItC,OAFI,CAAC,OAAO,SAAS,CAAI,KAAK,IAAO,KAAK,IAAO,WAC7C,KAAQ,SAAU,KAAQ,QAAe,IACtC,OAAO,cAAc,CAAI;EAClC;EACA,OAAO,GAAe,EAAI,YAAY,MAAM;CAC9C,CACF,IAhBiC;AAiBnC;AAcA,SAAgB,EAAgB,GAAe,GAA6B;CAC1E,IAAM,IAAS,EAAK,YAAY,GAC5B,IAAI;CAER,OAAO,IAAI,EAAM,SAAQ;EACvB,OAAO,IAAI,EAAM,WAAW,EAAa,EAAM,EAAE,KAAK,EAAM,OAAO,OACjE;EAEF,IAAI,KAAK,EAAM,QAAQ;EAEvB,IAAM,IAAY;EAClB,OAAO,IAAI,EAAM,UAAU,GAAe,EAAM,EAAE,IAAG;EACrD,IAAM,IAAW,EAAM,UAAU,GAAW,CAAC,CAAC,CAAC,YAAY;EAC3D,IAAI,MAAa,IAAI;GAGnB;GACA;EACF;EAEA,OAAO,IAAI,EAAM,UAAU,EAAa,EAAM,EAAE,IAAG;EACnD,IAAI,EAAM,OAAO,KAGf;EAGF,KADA,KACO,IAAI,EAAM,UAAU,EAAa,EAAM,EAAE,IAAG;EAEnD,IAAM,IAAQ,EAAM;EACpB,IAAI,EAAO,IAAI,CAAK,GAAG;GACrB,IAAM,IAAa,IAAI,GACjB,IAAW,EAAM,QAAQ,GAAO,CAAU;GAChD,IAAI,MAAa,IAAI,OAAO;GAC5B,IAAI,MAAa,GACf,OAAO,EAAmB,EAAM,UAAU,GAAY,CAAQ,CAAC;GACjE,IAAI,IAAW;GACf;EACF;EAEA,IAAM,IAAa;EACnB,OAAO,IAAI,EAAM,UAAU,CAAC,EAAa,EAAM,EAAE,KAAK,EAAM,OAAO,MAAK;EACxE,IAAI,MAAa,GACf,OAAO,EAAmB,EAAM,UAAU,GAAY,CAAC,CAAC;CAC5D;CAEA,OAAO;AACT;;;AC5JA,IAAa,IAAyD;CACpE,QAAQ;EAAE,OAAO;EAAgB,OAAO;CAAyB;CACjE,YAAY;EACV,OAAO;EACP,OAAO;CACT;CACA,WAAW;EAAE,OAAO;EAAgB,OAAO;CAAwB;CACnE,WAAW;EAAE,OAAO;EAAc,OAAO;CAAuB;AAClE,GAEM,KAAyD;CAC7D,QAAQ;CACR,YAAY;CACZ,WAAW;CACX,WAAW;AACb,GAEM,KAAyD;CAC7D,QAAQ;CACR,YAAY;CACZ,WAAW;CACX,WAAW;AACb;AAOA,SAAgB,GAAqB,GAAqC;CACxE,KAAK,IAAM,KAAQ,OAAO,KAAK,CAAc,GAC3C,IAAI,EAAe,EAAK,CAAC,MAAM,WAAW,EAAO,MAAM,QACrD,OAAO,GAAqB;CAGhC,OAAO;AACT;AASA,SAAgB,GAAqB,GAAqC;CACxE,KAAK,IAAM,KAAQ,OAAO,KAAK,CAAc,GAC3C,IAAI,EAAe,EAAK,CAAC,MAAM,WAAW,EAAO,MAAM,QACrD,OAAO,GAAqB;CAGhC,OAAO;AACT;AAEA,SAAgB,GACd,GACc;CASd,OARK,IAID,OAAO,KAAW,WACb,EAAe,MAAW,EAAe,SAG3C,IAPE,EAAe;AAQ1B;AAIA,SAAS,EAAc,GAAyB;CAC9C,IAAM,IAAS,EAAQ,QACjB,IAAQ,EAAQ,MAAM,QAAQ,KAAK,EAAE;CAC3C,OAAW,OAAO,IAAI,EAAO,IAAI,CAAK;AACxC;AAEA,SAAgB,GAAgB,GAAe,GAA+B;CAC5E,IAAM,IAAU,GAAO,KAAK,KAAK;CAQjC,OAHA,CAAI,EAAc,EAAO,KAAK,CAAC,CAAC,KAAK,CAAO,KAGrC,EAAc,EAAO,KAAK,CAAC,CAAC,KAAK,CAAO;AACjD;AAEA,SAAgB,EAAiB,GAAe,GAA+B;CAC7E,IAAM,IAAQ,EAAU,MAAM,MAAM,EAAE,UAAU,CAAK;CAIrD,OAHI,IACK,EAAM,QAER;AACT;AAEA,SAAgB,GACd,GACA,GACQ;CACR,OAAO,EAAkB,GAAM,mBAAmB,MAChD,EAAiB,GAAO,CAAS,CACnC;AACF;AAQA,SAAgB,EACd,GACA,GACoB;CACpB,OAAO,EAAU,MAAM,MAAM,EAAE,UAAU,CAAK,CAAC,EAAE;AACnD;AAeA,SAAgB,GACd,GACA,GACQ;CACR,OAAO,EACL,GACA,mBACC,MACC,GACE,EAAkB,GAAO,CAAS,KAChC,EAAiB,GAAO,CAAS,CACrC,GACF,EAAE,SAAS,MAAU,EAAkB,GAAO,CAAS,MAAM,KAAA,EAAU,CACzE;AACF;AASA,SAAgB,GAAmB,GAAgC;CACjE,OAAO,EAAU,MAAM,MAAQ,EAAI,WAAW,KAAA,CAAS;AACzD;AAWA,SAAgB,GACd,GACA,GACQ;CACR,IAAI,CAAC,GAAM,OAAO;CAElB,IAAI,IAAM;CACV,KAAK,IAAM,KAAO,GACZ,EAAI,WAAW,KAAA,MAGnB,IAAM,EAAI,MAAM,EAAI,KAAK,CAAC,CAAC,KAAK,EAAI,MAAM;CAE5C,OAAO;AACT;AAOA,SAAS,GAAc,GAAsB;CAC3C,OAAO,EACJ,QAAQ,MAAM,OAAO,CAAC,CACtB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,QAAQ;AAC3B;AAEA,SAAgB,GAAiB,GAAe,GAA+B;CAC7E,IAAI,CAAC,GAAO,OAAO;CAEnB,IAAM,IAAa,IAAI,OAAO,EAAO,MAAM,QAAQ,EAAO,MAAM,KAAK,GAC/D,IAAa,IAAI,OAAO,EAAO,MAAM,QAAQ,EAAO,MAAM,KAAK;CAErE,OAAO,EAAW,KAAK,CAAK,KAAK,EAAW,KAAK,CAAK;AACxD;AAEA,SAAgB,GACd,GACA,GACS;CACT,OAAO,EAAc,EAAO,KAAK,CAAC,CAAC,KAAK,GAAO,KAAK,KAAK,EAAE;AAC7D;AAEA,SAAgB,EACd,GACA,GACQ;CACR,IAAM,IAAQ,IAAI,OAChB,EAAO,MAAM,QACb,EAAO,MAAM,MAAM,QAAQ,KAAK,EAAE,CACpC,GACM,IAAQ,EAAM,MAAM,CAAK;CAC/B,OAAO,KAAS,EAAM,KAAK,EAAM,EAAE,CAAC,YAAY,IAAI;AACtD;AAEA,SAAgB,GACd,GACA,GACQ;CACR,OAAO,EAAkB,GAAM,yBAAyB,MACtD,EAAwB,GAAO,CAAM,CACvC;AACF;AAmBA,SAAS,EACP,GACA,GACA,GACA,GACQ;CACR,IAAI,IAAM,IACN,IAAI;CACR,OAAO,IAAI,EAAK,SAAQ;EACtB,IAAM,IAAO,EAAK,QAAQ,SAAS,CAAC;EACpC,IAAI,MAAS,IAAI;GACf,KAAO,EAAK,UAAU,CAAC;GACvB;EACF;EACA,IAAM,IAAe,EAAK,IAAO;EACjC,IACE,MAAiB,OACjB,MAAiB,OACjB,MAAiB,OACjB,MAAiB,QACjB,MAAiB,QACjB,MAAiB,KACjB;GAEA,AADA,KAAO,EAAK,UAAU,GAAG,IAAO,CAAC,GACjC,IAAI,IAAO;GACX;EACF;EACA,IAAM,IAAU,EAAe,GAAM,IAAO,CAAC;EAC7C,IAAI,MAAY,IAAI;GAClB,KAAO,EAAK,UAAU,CAAC;GACvB;EACF;EACA,IAAM,IAAa,EAAK,QAAQ,WAAW,IAAU,CAAC;EACtD,IAAI,MAAe,IAAI;GACrB,KAAO,EAAK,UAAU,CAAC;GACvB;EACF;EAEA,IAAM,IAAQ,EADA,EAAK,UAAU,IAAO,GAAG,CACT,GAAO,CAAQ;EAC7C,IAAI,MAAU,MAAM;GAMlB,AADA,KAAO,EAAK,UAAU,GAAG,IAAO,CAAC,GACjC,IAAI,IAAO;GACX;EACF;EACA,IAAM,IAAW,EAAQ,CAAK;EAc9B,AAbI,GAAS,SAAS,CAAK,KAMzB,KAAO,EAAK,UAAU,GAAG,CAAI,GAC7B,KAAO,MAEP,KAAO,EAAK,UAAU,GAAG,IAAU,CAAC,GACpC,KAAO,GACP,KAAO,YAET,IAAI,IAAa;CACnB;CACA,OAAO;AACT;;;AC1RA,SAAgB,GACd,GAC0B;CAG1B,OAFI,OAAO,KAAU,aAAY,IAAuB,KAEjD,MAAM,QAAQ,EAAU,MAAM;AACvC;;;ACmLA,IAAa,KAAb,cAA8B,MAAM;CAGhB;CAFlB,YACE,GACA,GACA;EAEA,AADA,MAAM,CAAO,GAFG,KAAA,aAAA,GAGhB,KAAK,OAAO;CACd;CAEA,IAAI,aAAsB;EACxB,OAAO,KAAK,eAAe;CAC7B;CAEA,IAAI,iBAA0B;EAC5B,OAAO,KAAK,eAAe;CAC7B;CAEA,IAAI,gBAAyB;EAC3B,OAAO,KAAK,eAAe,KAAA,KAAa,KAAK,cAAc;CAC7D;AACF"}