@clay-design-system/email 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/README.md +48 -2
  2. package/dist/build/asset-scoring.d.ts +48 -0
  3. package/dist/build/asset-scoring.d.ts.map +1 -0
  4. package/dist/build/asset-scoring.js +57 -0
  5. package/dist/build/asset-scoring.js.map +1 -0
  6. package/dist/build/braze-transform.d.ts +20 -0
  7. package/dist/build/braze-transform.d.ts.map +1 -0
  8. package/dist/build/braze-transform.js +26 -0
  9. package/dist/build/braze-transform.js.map +1 -0
  10. package/dist/build/catalog.d.ts +25 -0
  11. package/dist/build/catalog.d.ts.map +1 -0
  12. package/dist/build/catalog.js +3 -0
  13. package/dist/build/catalog.js.map +1 -0
  14. package/dist/build/cloudinary-env.d.ts +15 -0
  15. package/dist/build/cloudinary-env.d.ts.map +1 -0
  16. package/dist/build/cloudinary-env.js +36 -0
  17. package/dist/build/cloudinary-env.js.map +1 -0
  18. package/dist/build/cloudinary-manifest.d.ts +33 -0
  19. package/dist/build/cloudinary-manifest.d.ts.map +1 -0
  20. package/dist/build/cloudinary-manifest.js +36 -0
  21. package/dist/build/cloudinary-manifest.js.map +1 -0
  22. package/dist/build/compile.d.ts +11 -0
  23. package/dist/build/compile.d.ts.map +1 -0
  24. package/dist/build/compile.js +234 -0
  25. package/dist/build/compile.js.map +1 -0
  26. package/dist/build/email-config.d.ts +26 -0
  27. package/dist/build/email-config.d.ts.map +1 -0
  28. package/dist/build/email-config.js +17 -0
  29. package/dist/build/email-config.js.map +1 -0
  30. package/dist/build/email-list.d.ts +6 -0
  31. package/dist/build/email-list.d.ts.map +1 -0
  32. package/dist/build/email-list.js +17 -0
  33. package/dist/build/email-list.js.map +1 -0
  34. package/dist/build/email-tree.d.ts +23 -0
  35. package/dist/build/email-tree.d.ts.map +1 -0
  36. package/dist/build/email-tree.js +46 -0
  37. package/dist/build/email-tree.js.map +1 -0
  38. package/dist/build/export-braze.d.ts +22 -0
  39. package/dist/build/export-braze.d.ts.map +1 -0
  40. package/dist/build/export-braze.js +92 -0
  41. package/dist/build/export-braze.js.map +1 -0
  42. package/dist/build/generate-stock-library-map.d.ts +13 -0
  43. package/dist/build/generate-stock-library-map.d.ts.map +1 -0
  44. package/dist/build/generate-stock-library-map.js +202 -0
  45. package/dist/build/generate-stock-library-map.js.map +1 -0
  46. package/dist/build/icon-registry-map.d.ts +34 -0
  47. package/dist/build/icon-registry-map.d.ts.map +1 -0
  48. package/dist/build/icon-registry-map.js +44 -0
  49. package/dist/build/icon-registry-map.js.map +1 -0
  50. package/dist/build/import-examples.d.ts +14 -0
  51. package/dist/build/import-examples.d.ts.map +1 -0
  52. package/dist/build/import-examples.js +65 -0
  53. package/dist/build/import-examples.js.map +1 -0
  54. package/dist/build/persona-character-map.d.ts +32 -0
  55. package/dist/build/persona-character-map.d.ts.map +1 -0
  56. package/dist/build/persona-character-map.js +90 -0
  57. package/dist/build/persona-character-map.js.map +1 -0
  58. package/dist/build/stock-library-map.d.ts +57 -0
  59. package/dist/build/stock-library-map.d.ts.map +1 -0
  60. package/dist/build/stock-library-map.js +1261 -0
  61. package/dist/build/stock-library-map.js.map +1 -0
  62. package/dist/build/upload-assets.d.ts +20 -0
  63. package/dist/build/upload-assets.d.ts.map +1 -0
  64. package/dist/build/upload-assets.js +153 -0
  65. package/dist/build/upload-assets.js.map +1 -0
  66. package/dist/build/upload-library-assets.d.ts +21 -0
  67. package/dist/build/upload-library-assets.d.ts.map +1 -0
  68. package/dist/build/upload-library-assets.js +182 -0
  69. package/dist/build/upload-library-assets.js.map +1 -0
  70. package/dist/build/visual-resolver.d.ts +70 -0
  71. package/dist/build/visual-resolver.d.ts.map +1 -0
  72. package/dist/build/visual-resolver.js +375 -0
  73. package/dist/build/visual-resolver.js.map +1 -0
  74. package/dist/catalog.json +467 -0
  75. package/dist/emails/component-buttons-icons.html +435 -0
  76. package/dist/emails/component-buttons-integration.html +256 -0
  77. package/dist/emails/component-buttons-primary.html +269 -0
  78. package/dist/emails/component-buttons-secondary.html +255 -0
  79. package/dist/emails/component-buttons-two-integrations.html +310 -0
  80. package/dist/emails/component-images-card-280h.html +281 -0
  81. package/dist/emails/component-images-card-320h.html +281 -0
  82. package/dist/emails/component-images-card.html +281 -0
  83. package/dist/emails/component-images-full-bleed-280h.html +279 -0
  84. package/dist/emails/component-images-full-bleed-400h.html +279 -0
  85. package/dist/emails/component-images-full-bleed.html +279 -0
  86. package/dist/emails/component-text-h1.html +254 -0
  87. package/dist/emails/component-text-h2.html +230 -0
  88. package/dist/emails/component-text-h3.html +255 -0
  89. package/dist/emails/component-text-h4.html +255 -0
  90. package/dist/emails/component-text-h5.html +254 -0
  91. package/dist/emails/component-text-p2.html +253 -0
  92. package/dist/emails/component-text-p3.html +254 -0
  93. package/dist/emails/component-text-p4.html +254 -0
  94. package/dist/emails/container.html +250 -0
  95. package/dist/emails/section-changelog-icon-list.html +1201 -0
  96. package/dist/emails/section-divider-line.html +340 -0
  97. package/dist/emails/section-feature-highlight-reversed.html +377 -0
  98. package/dist/emails/section-feature-highlight.html +384 -0
  99. package/dist/emails/section-feature-rows.html +328 -0
  100. package/dist/emails/section-feature-story-row-reversed.html +381 -0
  101. package/dist/emails/section-feature-story-row.html +382 -0
  102. package/dist/emails/section-footer-lifecycle.html +650 -0
  103. package/dist/emails/section-footer-two-column-minimal.html +318 -0
  104. package/dist/emails/section-footer.html +413 -0
  105. package/dist/emails/section-header.html +290 -0
  106. package/dist/emails/section-hero.html +276 -0
  107. package/dist/emails/section-icon-feature-list.html +554 -0
  108. package/dist/emails/section-image-caption-block.html +313 -0
  109. package/dist/emails/section-image-hero.html +341 -0
  110. package/dist/emails/section-lesson-card.html +628 -0
  111. package/dist/emails/section-list-action-items.html +590 -0
  112. package/dist/emails/section-list-feature-items.html +713 -0
  113. package/dist/emails/section-list-icon-items.html +701 -0
  114. package/dist/emails/section-list-numbered-items.html +733 -0
  115. package/dist/emails/section-logo-header.html +288 -0
  116. package/dist/emails/section-newsletter-hero.html +393 -0
  117. package/dist/emails/section-product-ui-hero.html +496 -0
  118. package/dist/emails/section-promo-banner.html +308 -0
  119. package/dist/emails/section-qa-block.html +440 -0
  120. package/dist/emails/section-resource-link-list.html +481 -0
  121. package/dist/emails/section-signoff.html +279 -0
  122. package/dist/emails/section-single-column-author.html +453 -0
  123. package/dist/emails/section-single-column-feature-card.html +312 -0
  124. package/dist/emails/section-single-column-product-card.html +393 -0
  125. package/dist/emails/section-single-column-promo-card.html +306 -0
  126. package/dist/emails/section-spacer-fill.html +281 -0
  127. package/dist/emails/section-testimonial-dark-card.html +363 -0
  128. package/dist/emails/section-text-h1-regular.html +254 -0
  129. package/dist/emails/section-text-h2-regular.html +254 -0
  130. package/dist/emails/section-text-h3-regular.html +254 -0
  131. package/dist/emails/section-text-h4-regular.html +254 -0
  132. package/dist/emails/section-text-h5-regular.html +254 -0
  133. package/dist/emails/section-text-mixed-heading.html +269 -0
  134. package/dist/emails/section-text-overline-heading.html +281 -0
  135. package/dist/emails/section-text-p1-bold.html +254 -0
  136. package/dist/emails/section-text-p1-regular.html +255 -0
  137. package/dist/emails/section-text-paragraph.html +358 -0
  138. package/dist/emails/section-text-quote-card.html +269 -0
  139. package/dist/emails/section-text-quote.html +313 -0
  140. package/dist/emails/section-text-small-headline.html +279 -0
  141. package/dist/emails/section-three-column-content-cards.html +499 -0
  142. package/dist/emails/section-three-column-icon-cards.html +510 -0
  143. package/dist/emails/section-three-column-image-grid.html +466 -0
  144. package/dist/emails/section-three-up-icon-benefits.html +464 -0
  145. package/dist/emails/section-two-column-feature-cards.html +708 -0
  146. package/dist/emails/section-two-column-grid.html +1131 -0
  147. package/dist/emails/section-two-column-img-text.html +331 -0
  148. package/dist/emails/section-two-column-live-tags.html +367 -0
  149. package/dist/emails/section-two-column-stacked.html +629 -0
  150. package/dist/emails/section-upsell-cta.html +597 -0
  151. package/dist/emails/section-value-bullet-list.html +313 -0
  152. package/dist/examples/manifest.d.ts +21 -0
  153. package/dist/examples/manifest.d.ts.map +1 -0
  154. package/dist/examples/manifest.js +84 -0
  155. package/dist/examples/manifest.js.map +1 -0
  156. package/dist/exports/contact-sales/Images/Contact_sales_Image_16f4175c-d08c-43f6-9d4c-a0606383271d.png +0 -0
  157. package/dist/exports/contact-sales/Images/Contact_sales_Image_659b3091-1da9-40c2-86b0-04250982e529.png +0 -0
  158. package/dist/exports/contact-sales/Images/Contact_sales_Image_79c651f4-5f0f-4a7f-8354-1f6f95ae86d9.png +0 -0
  159. package/dist/exports/contact-sales/index.braze.html +837 -0
  160. package/dist/exports/contact-sales/index.html +837 -0
  161. package/dist/exports/event-confirmation/Images/Confirmation_Image_11938de2-4161-4ae8-8969-90ea0d8b45cb.png +0 -0
  162. package/dist/exports/event-confirmation/Images/Confirmation_Image_3c52e81f-5d4f-452c-bad1-0471a27598f1.png +0 -0
  163. package/dist/exports/event-confirmation/Images/Confirmation_Image_3ec1fec8-bf46-4068-8835-633567a715f9.png +0 -0
  164. package/dist/exports/event-confirmation/Images/Confirmation_Image_c4980c01-c270-4384-a56c-0bae74814cf9.png +0 -0
  165. package/dist/exports/event-confirmation/Images/Confirmation_Image_cde93e8c-0f96-434d-acaf-1b86503c66f0.png +0 -0
  166. package/dist/exports/event-confirmation/index.braze.html +1055 -0
  167. package/dist/exports/event-confirmation/index.html +1055 -0
  168. package/dist/exports/legal-update/Images/v2_Image_07bbcc13-0ab8-46e4-ab94-98f215f85f69.png +0 -0
  169. package/dist/exports/legal-update/Images/v2_Image_34394d50-acdf-4375-b254-eef1f84aaacb.png +0 -0
  170. package/dist/exports/legal-update/Images/v2_Image_4a25f2bd-6270-439b-86c5-4a968a1b104f.png +0 -0
  171. package/dist/exports/legal-update/Images/v2_Image_7e8e814f-3112-4d8c-af5b-4f4616d84d9c.png +0 -0
  172. package/dist/exports/legal-update/Images/v2_Image_b24a04c8-4af0-42a1-be0f-cd3f66595e47.png +0 -0
  173. package/dist/exports/legal-update/Images/v2_Image_b709b9fb-836e-4c20-82e5-a9cb9d702392.png +0 -0
  174. package/dist/exports/legal-update/Images/v2_Image_ed7b924b-35e6-4536-be0c-92ddc8a34fc2.png +0 -0
  175. package/dist/exports/legal-update/index.braze.html +1731 -0
  176. package/dist/exports/legal-update/index.html +1731 -0
  177. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_09be22f6-1f30-4c58-bf33-1df0494b8fc2.png +0 -0
  178. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_2dfb595e-6fe2-4e6e-a73a-942a085b38ab.png +0 -0
  179. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_544c971b-b4b5-4c89-a25a-8f0d92644009.png +0 -0
  180. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_5b6a9576-1c33-40c5-81e3-637514fb6c78.png +0 -0
  181. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_680c6193-793a-4a24-8288-4bb4046f4f5e.png +0 -0
  182. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_75818ba9-026a-45d7-91e4-f992d4ee7690.png +0 -0
  183. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_785f18e6-816d-44d1-8efb-ecc82510b540.png +0 -0
  184. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_8bf9141d-9d63-4601-9e81-5c9d726e17f8.png +0 -0
  185. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_9097d6e0-65d0-4237-9661-963751812786.png +0 -0
  186. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_a4ef2f53-2eaf-438a-bfef-61455ac0915e.png +0 -0
  187. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_b7748f87-a7b7-4a91-981a-16567683e4b4.png +0 -0
  188. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_bc9830a4-2911-4768-808a-32cbbc39215e.png +0 -0
  189. package/dist/exports/newsletter/Images/Status_update_-_July_26_Image_ef0e8e92-9075-43a9-991f-09a77ef6d6d4.png +0 -0
  190. package/dist/exports/newsletter/index.braze.html +125 -0
  191. package/dist/exports/newsletter/index.html +125 -0
  192. package/dist/exports/newsletter-ai-june-july/Images/newsletter-ai-blocks.png +0 -0
  193. package/dist/exports/newsletter-ai-june-july/Images/newsletter-ai-hero-mcp.png +0 -0
  194. package/dist/exports/newsletter-ai-june-july/Images/newsletter-ai-magic.png +0 -0
  195. package/dist/exports/newsletter-ai-june-july/Images/newsletter-ai-mcp.png +0 -0
  196. package/dist/exports/newsletter-ai-june-july/Images/newsletter-ai-portfolio.png +0 -0
  197. package/dist/exports/newsletter-ai-june-july/index.braze.html +1640 -0
  198. package/dist/exports/newsletter-ai-june-july/index.html +1640 -0
  199. package/dist/exports/survey/Images/EmailLove_Image_3b6f4a6f-7f5f-418b-be71-9371b25c20ce.png +0 -0
  200. package/dist/exports/survey/Images/EmailLove_Image_f659c728-2401-485d-8d90-98c962d28c0c.png +0 -0
  201. package/dist/exports/survey/index.braze.html +515 -0
  202. package/dist/exports/survey/index.html +515 -0
  203. package/dist/exports/trial-01-welcome/Images/tp1_-_welcome_Before_115ccc58-a47f-4eca-a1f2-c0d86e1e1cf4.png +0 -0
  204. package/dist/exports/trial-01-welcome/Images/tp1_-_welcome_Before_aec4d0d3-6057-4b8a-8d8f-46fbd024f182.png +0 -0
  205. package/dist/exports/trial-01-welcome/Images/tp1_-_welcome_Before_d139966a-6051-4383-bf8f-1e5138e1b030.png +0 -0
  206. package/dist/exports/trial-01-welcome/Images/tp1_-_welcome_Image_080fa361-a3f2-4593-9ac0-03cf3a7972ff.png +0 -0
  207. package/dist/exports/trial-01-welcome/Images/tp1_-_welcome_Image_5ebade34-8f1d-4c2c-9d27-5f9e843ce502.png +0 -0
  208. package/dist/exports/trial-01-welcome/Images/tp1_-_welcome_Image_aa5e4144-9ec8-4525-b01e-077d72f0bab6.png +0 -0
  209. package/dist/exports/trial-01-welcome/index.braze.html +941 -0
  210. package/dist/exports/trial-01-welcome/index.html +941 -0
  211. package/dist/exports/trial-02/Images/tp2_Image_5b0d9bc4-2e38-4252-8f20-d9f27d471c21.png +0 -0
  212. package/dist/exports/trial-02/Images/tp2_Image_b3f77c7c-cc8e-4db8-a5d9-072f4f621e72.png +0 -0
  213. package/dist/exports/trial-02/Images/tp2_Image_cafd1a17-1915-47cd-80f2-1c2d0b133cfe.png +0 -0
  214. package/dist/exports/trial-02/index.braze.html +923 -0
  215. package/dist/exports/trial-02/index.html +923 -0
  216. package/dist/exports/trial-03/Images/tp3_Image_1b96630c-fc4d-4aec-91fb-3c1d4d8544b3.png +0 -0
  217. package/dist/exports/trial-03/Images/tp3_Image_58f3a342-2193-4b5c-b30a-96f7f3338dd8.png +0 -0
  218. package/dist/exports/trial-03/Images/tp3_Image_6c4f307a-bac2-40e7-b4f1-9aa623c9eb8e.png +0 -0
  219. package/dist/exports/trial-03/Images/tp3_Image_848917ce-3f89-4538-a826-558b21f5fea7.png +0 -0
  220. package/dist/exports/trial-03/Images/tp3_Image_aae8410f-556f-4fb9-88f9-f276d75bafd2.png +0 -0
  221. package/dist/exports/trial-03/Images/tp3_Image_b4d5be39-509e-4f22-9601-dd070e4c6561.png +0 -0
  222. package/dist/exports/trial-03/index.braze.html +1129 -0
  223. package/dist/exports/trial-03/index.html +1129 -0
  224. package/dist/exports/trial-04/Images/tp4_Image_03bd5a56-25e3-4dcc-bef5-143bf12312d5.png +0 -0
  225. package/dist/exports/trial-04/Images/tp4_Image_0facbfca-b735-4199-a0d9-05a59341a1e3.gif +0 -0
  226. package/dist/exports/trial-04/Images/tp4_Image_171cee5f-cd3d-42f0-a180-995893d3742a.png +0 -0
  227. package/dist/exports/trial-04/Images/tp4_Image_3509d89d-1825-4817-8c1e-130fa294d0e1.png +0 -0
  228. package/dist/exports/trial-04/Images/tp4_Image_763a0f40-2985-4140-859b-ddd34f20a520.png +0 -0
  229. package/dist/exports/trial-04/Images/tp4_Image_cc2db4f5-b41d-45d7-a798-7a79e7b8d4d8.png +0 -0
  230. package/dist/exports/trial-04/index.braze.html +885 -0
  231. package/dist/exports/trial-04/index.html +885 -0
  232. package/dist/exports/trial-05/Images/tp5_Image_0248a517-5c60-4e84-ad1c-449dcf4f6a64.png +0 -0
  233. package/dist/exports/trial-05/Images/tp5_Image_4c1a2d5a-47d3-4801-bb3a-01c22decc108.png +0 -0
  234. package/dist/exports/trial-05/Images/tp5_Image_63ae77e7-cec7-4b4a-91e0-603b5a80929f.png +0 -0
  235. package/dist/exports/trial-05/Images/tp5_Image_7aa43530-6707-4308-ab0e-e4c38fc1b77d.png +0 -0
  236. package/dist/exports/trial-05/Images/tp5_Image_7efe5b6d-b360-4179-bee1-72908ba569bc.png +0 -0
  237. package/dist/exports/trial-05/Images/tp5_Image_b142bd84-c40a-4a29-b9d5-715b52fdd937.png +0 -0
  238. package/dist/exports/trial-05/Images/tp5_Image_c0c896ea-5b68-4f58-9e27-cdb4a1f1b438.png +0 -0
  239. package/dist/exports/trial-05/index.braze.html +1023 -0
  240. package/dist/exports/trial-05/index.html +1023 -0
  241. package/dist/exports/trial-06/Images/tp6_Image_2267a268-8a80-4e0d-8813-d4c75dbab6f2.png +0 -0
  242. package/dist/exports/trial-06/Images/tp6_Image_637a97b7-837e-478d-8ed1-6e8c2739fdc7.png +0 -0
  243. package/dist/exports/trial-06/Images/tp6_Image_a78df270-ad6f-41e6-8822-ba8ec38313a3.png +0 -0
  244. package/dist/exports/trial-06/index.braze.html +773 -0
  245. package/dist/exports/trial-06/index.html +773 -0
  246. package/dist/exports/winback/Images/_managers_Image_4587ac22-460c-487a-bc37-9dbc487e95a4.png +0 -0
  247. package/dist/exports/winback/Images/_managers_Image_8a77ec2c-ed49-480d-a1a1-7a38abad232a.png +0 -0
  248. package/dist/exports/winback/Images/_managers_Image_bb643775-8a2f-4e28-9dfb-6d1838c3561f.png +0 -0
  249. package/dist/exports/winback/Images/_managers_Image_f7d1bca5-0fcb-4d4c-abcd-40ce2d453b07.png +0 -0
  250. package/dist/exports/winback/index.braze.html +605 -0
  251. package/dist/exports/winback/index.html +605 -0
  252. package/dist/generated/email-tokens.d.ts +101 -0
  253. package/dist/generated/email-tokens.d.ts.map +1 -0
  254. package/dist/generated/email-tokens.js +104 -0
  255. package/dist/generated/email-tokens.js.map +1 -0
  256. package/dist/index.d.ts +9 -0
  257. package/dist/index.d.ts.map +1 -0
  258. package/dist/index.js +8 -0
  259. package/dist/index.js.map +1 -0
  260. package/dist/tokens/build-email-assets.d.ts +19 -0
  261. package/dist/tokens/build-email-assets.d.ts.map +1 -0
  262. package/dist/tokens/build-email-assets.js +186 -0
  263. package/dist/tokens/build-email-assets.js.map +1 -0
  264. package/dist/tokens/build-email-tokens.d.ts +17 -0
  265. package/dist/tokens/build-email-tokens.d.ts.map +1 -0
  266. package/dist/tokens/build-email-tokens.js +229 -0
  267. package/dist/tokens/build-email-tokens.js.map +1 -0
  268. package/package.json +41 -3
package/README.md CHANGED
@@ -1,3 +1,49 @@
1
- # Temporary Holding Version
1
+ # @clay-design-system/email
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The **Lifecycle / Email** tier of the Clay Design System — responsive, Outlook-safe HTML emails
4
+ authored in **MJML** and composed from the shared Clay foundation tokens.
5
+
6
+ ## Why MJML + inlined tokens
7
+
8
+ Email clients (notably Outlook) do **not** support CSS custom properties (`var(--clay-*)`). So this
9
+ package never ships `var()` — instead a build step reads the resolved literal token values from
10
+ `@clay-design-system/tokens` (the `dist/tokens.js` ES export) and **inlines** them into MJML at build time.
11
+
12
+ ```
13
+ @clay-design-system/tokens (JS literals)
14
+ ↓ src/tokens/build-email-tokens.ts (generates)
15
+ src/generated/base-head.mjml ← <mj-head>: fonts, mj-class roles, dark-mode
16
+ src/generated/email-tokens.ts ← inline-able constants + px() helper
17
+ ↓ MJML sections + container
18
+ src/build/compile.ts (mjml2html)
19
+ ↓
20
+ dist/emails/<name>.html ← standalone, previewable
21
+ dist/emails/<name>.braze.html ← Braze full-template variant (unsubscribe token)
22
+ dist/emails/<name>.static.html ← hand-authored fallback (no MJML)
23
+ dist/catalog.json ← Storybook + docs source
24
+ ```
25
+
26
+ ## Structure
27
+
28
+ - `src/container/` — the shared email shell (page bg → 640px card → 560px inner, preheader,
29
+ responsive + dark/light). Every email reuses it.
30
+ - `src/sections/` — MJML section partials (logo-header, hero, feature-rows, icon-feature-list,
31
+ buttons-primary, footer).
32
+ - `src/emails/` — full email compositions (container + sections) plus a static-HTML fallback.
33
+ **Gitignored / local-only:** every composed email is a generation, so it stays on the machine
34
+ that made it and contributors never see each other's campaigns. The build globs this folder at
35
+ compile time, so generating into it works unchanged — it is simply never committed. A fresh clone
36
+ starts empty here; `src/examples/` holds the committed Email Love → Braze reference exports.
37
+ - `src/tokens/` — the token→email bridge codegen.
38
+ - `src/build/` — MJML compile + Braze transform + catalog.
39
+
40
+ ## Scripts
41
+
42
+ ```bash
43
+ pnpm --filter @clay-design-system/email build # tokens bridge → tsc → compile MJML
44
+ pnpm --filter @clay-design-system/email dev # watch + recompile MJML
45
+ pnpm --filter @clay-design-system/email tokens:check # fail if generated token artifacts drift
46
+ pnpm --filter @clay-design-system/email test # vitest
47
+ ```
48
+
49
+ Documented in Storybook under **Lifecycle/Email/**.
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Shared two-stage ranked-match algorithm for visual-resolver.ts's asset pickers
3
+ * (stock-library-map.ts, icon-registry-map.ts) — the "never guess between plausible candidates"
4
+ * rule, in exactly one place, extended to use more than one data column without weakening it.
5
+ *
6
+ * Stage 1 (content gate): a candidate must have positive `contentScore` to be considered at all —
7
+ * a secondary signal (aspect ratio, domain, category, ...) can never rescue a zero-content-overlap
8
+ * candidate, and can never outrank a candidate with a strictly higher content score.
9
+ * Stage 2 (tie-break): only among the candidates tied for the *maximum* content score, break the
10
+ * tie using `secondaryScore` — but only if that produces a unique, strictly-positive top scorer.
11
+ * A genuine tie (including an all-zero secondary tie) still returns null, same as before this
12
+ * module existed.
13
+ */
14
+ /**
15
+ * Word-boundary match, not substring — shared by every fuzzy text matcher in this build pipeline
16
+ * (visual-resolver.ts, icon-registry-map.ts, stock-library-map.ts) so the "whole word" rule can't
17
+ * drift between them.
18
+ */
19
+ export declare function containsWord(haystackLower: string, word: string): boolean;
20
+ /**
21
+ * A visual slot's rough layout shape — derived automatically from the slot spec (see
22
+ * visual-resolver.ts's deriveSlotShape), never hand-authored in a brief. Used only as a
23
+ * stage-2 tie-breaker (see pickRankedMatch below), never as a stage-1 filter.
24
+ */
25
+ export type SlotShape = 'hero' | 'card' | 'banner' | 'icon' | 'unknown';
26
+ /**
27
+ * Which of stock-library-source.csv's curated `ratio` values ('square' | 'wide' | 'super wide')
28
+ * count as a fit for each shape. Deliberately permissive (a shape can accept more than one ratio)
29
+ * — the real section survey behind this showed sections sharing a shape name still vary in actual
30
+ * aspect (e.g. product-ui-hero is 1.6:1 landscape, image-hero is ~1:1, newsletter-hero is 0.82:1
31
+ * portrait), so this is a coarse signal, not an exact-ratio requirement. `icon`/`unknown` never
32
+ * score — icons have no aspect axis, and `unknown` is the shape for every slot whose text doesn't
33
+ * name one (including every existing test's `slot: 'x'`), so it's a guaranteed no-op there.
34
+ */
35
+ export declare const SHAPE_ACCEPTABLE_RATIOS: Record<SlotShape, Set<string>>;
36
+ export interface RankOptions<T> {
37
+ contentScore(entry: T): number;
38
+ secondaryScore(entry: T): number;
39
+ /** Only called on the eventual tie-break winner, to explain which signals fired for it. */
40
+ explainSecondary?(entry: T): string[];
41
+ }
42
+ export interface RankedMatch<T> {
43
+ entry: T;
44
+ /** Empty when the winner was a unique content-score winner (identical to pre-ranking behavior). Populated only when a content tie was broken by secondaryScore. */
45
+ disambiguatedBy: string[];
46
+ }
47
+ export declare function pickRankedMatch<T>(candidates: T[], opts: RankOptions<T>): RankedMatch<T> | null;
48
+ //# sourceMappingURL=asset-scoring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-scoring.d.ts","sourceRoot":"","sources":["../../src/build/asset-scoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGzE;AAED;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAA;AAEvE;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAMlE,CAAA;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,YAAY,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM,CAAA;IAC9B,cAAc,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM,CAAA;IAChC,2FAA2F;IAC3F,gBAAgB,CAAC,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM,EAAE,CAAA;CACtC;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,KAAK,EAAE,CAAC,CAAA;IACR,mKAAmK;IACnK,eAAe,EAAE,MAAM,EAAE,CAAA;CAC1B;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI,CAgB/F"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Shared two-stage ranked-match algorithm for visual-resolver.ts's asset pickers
3
+ * (stock-library-map.ts, icon-registry-map.ts) — the "never guess between plausible candidates"
4
+ * rule, in exactly one place, extended to use more than one data column without weakening it.
5
+ *
6
+ * Stage 1 (content gate): a candidate must have positive `contentScore` to be considered at all —
7
+ * a secondary signal (aspect ratio, domain, category, ...) can never rescue a zero-content-overlap
8
+ * candidate, and can never outrank a candidate with a strictly higher content score.
9
+ * Stage 2 (tie-break): only among the candidates tied for the *maximum* content score, break the
10
+ * tie using `secondaryScore` — but only if that produces a unique, strictly-positive top scorer.
11
+ * A genuine tie (including an all-zero secondary tie) still returns null, same as before this
12
+ * module existed.
13
+ */
14
+ /**
15
+ * Word-boundary match, not substring — shared by every fuzzy text matcher in this build pipeline
16
+ * (visual-resolver.ts, icon-registry-map.ts, stock-library-map.ts) so the "whole word" rule can't
17
+ * drift between them.
18
+ */
19
+ export function containsWord(haystackLower, word) {
20
+ const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
21
+ return new RegExp(`\\b${escaped}\\b`).test(haystackLower);
22
+ }
23
+ /**
24
+ * Which of stock-library-source.csv's curated `ratio` values ('square' | 'wide' | 'super wide')
25
+ * count as a fit for each shape. Deliberately permissive (a shape can accept more than one ratio)
26
+ * — the real section survey behind this showed sections sharing a shape name still vary in actual
27
+ * aspect (e.g. product-ui-hero is 1.6:1 landscape, image-hero is ~1:1, newsletter-hero is 0.82:1
28
+ * portrait), so this is a coarse signal, not an exact-ratio requirement. `icon`/`unknown` never
29
+ * score — icons have no aspect axis, and `unknown` is the shape for every slot whose text doesn't
30
+ * name one (including every existing test's `slot: 'x'`), so it's a guaranteed no-op there.
31
+ */
32
+ export const SHAPE_ACCEPTABLE_RATIOS = {
33
+ hero: new Set(['wide', 'super wide']),
34
+ card: new Set(['square']),
35
+ banner: new Set(['super wide']),
36
+ icon: new Set([]),
37
+ unknown: new Set([]),
38
+ };
39
+ export function pickRankedMatch(candidates, opts) {
40
+ const scored = candidates.map((entry) => ({ entry, contentScore: opts.contentScore(entry) })).filter((s) => s.contentScore > 0);
41
+ if (!scored.length)
42
+ return null;
43
+ const maxContent = Math.max(...scored.map((s) => s.contentScore));
44
+ const contentWinners = scored.filter((s) => s.contentScore === maxContent);
45
+ if (contentWinners.length === 1)
46
+ return { entry: contentWinners[0].entry, disambiguatedBy: [] };
47
+ const withSecondary = contentWinners.map((s) => ({ entry: s.entry, secondaryScore: opts.secondaryScore(s.entry) }));
48
+ const maxSecondary = Math.max(...withSecondary.map((s) => s.secondaryScore));
49
+ if (maxSecondary <= 0)
50
+ return null;
51
+ const secondaryWinners = withSecondary.filter((s) => s.secondaryScore === maxSecondary);
52
+ if (secondaryWinners.length !== 1)
53
+ return null;
54
+ const winner = secondaryWinners[0].entry;
55
+ return { entry: winner, disambiguatedBy: opts.explainSecondary ? opts.explainSecondary(winner) : [] };
56
+ }
57
+ //# sourceMappingURL=asset-scoring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-scoring.js","sourceRoot":"","sources":["../../src/build/asset-scoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,aAAqB,EAAE,IAAY;IAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAA;IAC3D,OAAO,IAAI,MAAM,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;AAC3D,CAAC;AASD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAmC;IACrE,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACrC,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IACzB,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;IAC/B,IAAI,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC;IACjB,OAAO,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC;CACrB,CAAA;AAeD,MAAM,UAAU,eAAe,CAAI,UAAe,EAAE,IAAoB;IACtE,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,CAAA;IAC/H,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IAE/B,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAA;IACjE,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,UAAU,CAAC,CAAA;IAC1E,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,EAAE,CAAA;IAE/F,MAAM,aAAa,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;IACnH,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAA;IAC5E,IAAI,YAAY,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAElC,MAAM,gBAAgB,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,KAAK,YAAY,CAAC,CAAA;IACvF,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAC9C,MAAM,MAAM,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IACxC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;AACvG,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Braze export transform.
3
+ *
4
+ * Chunk 1 = **full-template import** mode: the whole HTML document is preserved (so head media-queries
5
+ * and the dark-mode block survive — Braze content-block export would strip the <head>). We only:
6
+ * 1. replace the footer unsubscribe placeholder with Braze's Liquid unsubscribe token, and
7
+ * 2. optionally wrap flagged strings in Liquid (off by default — plumbing for later chunks).
8
+ *
9
+ * Content-block mode (head-CSS re-injection) is deferred to a later chunk.
10
+ */
11
+ /** Placeholder token authored into the footer section; swapped for the ESP's real unsubscribe URL. */
12
+ export declare const UNSUBSCRIBE_PLACEHOLDER = "%%UNSUBSCRIBE_URL%%";
13
+ export interface BrazeConfig {
14
+ mode?: 'template' | 'content-block';
15
+ unsubscribe?: boolean;
16
+ /** map of placeholder → Liquid expression, applied verbatim (optional) */
17
+ liquid?: Record<string, string>;
18
+ }
19
+ export declare function brazeTransform(html: string, config?: BrazeConfig): string;
20
+ //# sourceMappingURL=braze-transform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"braze-transform.d.ts","sourceRoot":"","sources":["../../src/build/braze-transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,sGAAsG;AACtG,eAAO,MAAM,uBAAuB,wBAAwB,CAAA;AAK5D,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,UAAU,GAAG,eAAe,CAAA;IACnC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,WAAgB,GAAG,MAAM,CAa7E"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Braze export transform.
3
+ *
4
+ * Chunk 1 = **full-template import** mode: the whole HTML document is preserved (so head media-queries
5
+ * and the dark-mode block survive — Braze content-block export would strip the <head>). We only:
6
+ * 1. replace the footer unsubscribe placeholder with Braze's Liquid unsubscribe token, and
7
+ * 2. optionally wrap flagged strings in Liquid (off by default — plumbing for later chunks).
8
+ *
9
+ * Content-block mode (head-CSS re-injection) is deferred to a later chunk.
10
+ */
11
+ /** Placeholder token authored into the footer section; swapped for the ESP's real unsubscribe URL. */
12
+ export const UNSUBSCRIBE_PLACEHOLDER = '%%UNSUBSCRIBE_URL%%';
13
+ /** Braze Liquid personalization tag for the unsubscribe URL. */
14
+ const BRAZE_UNSUBSCRIBE = '{{${set_user_to_unsubscribed_url}}}';
15
+ export function brazeTransform(html, config = {}) {
16
+ const { unsubscribe = true, liquid = {} } = config;
17
+ let out = html;
18
+ if (unsubscribe) {
19
+ out = out.split(UNSUBSCRIBE_PLACEHOLDER).join(BRAZE_UNSUBSCRIBE);
20
+ }
21
+ for (const [placeholder, expr] of Object.entries(liquid)) {
22
+ out = out.split(placeholder).join(expr);
23
+ }
24
+ return out;
25
+ }
26
+ //# sourceMappingURL=braze-transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"braze-transform.js","sourceRoot":"","sources":["../../src/build/braze-transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,sGAAsG;AACtG,MAAM,CAAC,MAAM,uBAAuB,GAAG,qBAAqB,CAAA;AAE5D,gEAAgE;AAChE,MAAM,iBAAiB,GAAG,qCAAqC,CAAA;AAS/D,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,SAAsB,EAAE;IACnE,MAAM,EAAE,WAAW,GAAG,IAAI,EAAE,MAAM,GAAG,EAAE,EAAE,GAAG,MAAM,CAAA;IAClD,IAAI,GAAG,GAAG,IAAI,CAAA;IAEd,IAAI,WAAW,EAAE,CAAC;QAChB,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;IAClE,CAAC;IAED,KAAK,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACzD,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzC,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,25 @@
1
+ /** Catalog of compiled email artifacts — the source Storybook + docs read from. */
2
+ export type CatalogKind = 'container' | 'section' | 'component' | 'email';
3
+ export interface CatalogItem {
4
+ /** stable slug, e.g. "email-love-sample" or "hero" */
5
+ id: string;
6
+ /** human title, e.g. "Hero" */
7
+ title: string;
8
+ kind: CatalogKind;
9
+ /** compiled standalone HTML (MJML → HTML) */
10
+ html: string;
11
+ /** Braze full-template variant (emails only) */
12
+ brazeHtml?: string;
13
+ /** hand-authored static fallback (emails only) */
14
+ staticHtml?: string;
15
+ /** inbox subject line, derived from the compiled `<title>` (emails only) */
16
+ subject?: string;
17
+ /** inbox preview/preheader line, derived from the compiled hidden preheader div (emails only) */
18
+ preheader?: string;
19
+ }
20
+ export interface Catalog {
21
+ /** provenance note */
22
+ generatedFrom: string;
23
+ items: CatalogItem[];
24
+ }
25
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/build/catalog.ts"],"names":[],"mappings":"AAAA,mFAAmF;AAEnF,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAA;AAEzE,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,EAAE,EAAE,MAAM,CAAA;IACV,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,WAAW,CAAA;IACjB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iGAAiG;IACjG,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,sBAAsB;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,WAAW,EAAE,CAAA;CACrB"}
@@ -0,0 +1,3 @@
1
+ /** Catalog of compiled email artifacts — the source Storybook + docs read from. */
2
+ export {};
3
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/build/catalog.ts"],"names":[],"mappings":"AAAA,mFAAmF"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Cloudinary credential loader.
3
+ *
4
+ * Reads CLOUDINARY_CLOUD_NAME / CLOUDINARY_API_KEY / CLOUDINARY_API_SECRET from the repo-root
5
+ * `.env` (untracked — see skills/email-campaign/OPEN-QUESTIONS.md Q4). Never hardcode or log the
6
+ * raw secret values; this module only reports which *names* are missing.
7
+ */
8
+ export interface CloudinaryEnv {
9
+ cloudName: string;
10
+ apiKey: string;
11
+ apiSecret: string;
12
+ }
13
+ /** Throws a clear, actionable error (pointing at Q4) if any credential is missing. */
14
+ export declare function loadCloudinaryEnv(): CloudinaryEnv;
15
+ //# sourceMappingURL=cloudinary-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudinary-env.d.ts","sourceRoot":"","sources":["../../src/build/cloudinary-env.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,sFAAsF;AACtF,wBAAgB,iBAAiB,IAAI,aAAa,CA0BjD"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Cloudinary credential loader.
3
+ *
4
+ * Reads CLOUDINARY_CLOUD_NAME / CLOUDINARY_API_KEY / CLOUDINARY_API_SECRET from the repo-root
5
+ * `.env` (untracked — see skills/email-campaign/OPEN-QUESTIONS.md Q4). Never hardcode or log the
6
+ * raw secret values; this module only reports which *names* are missing.
7
+ */
8
+ import { existsSync } from 'node:fs';
9
+ import { dirname, resolve } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const repoRoot = resolve(__dirname, '../../../..');
13
+ const envPath = resolve(repoRoot, '.env');
14
+ /** Throws a clear, actionable error (pointing at Q4) if any credential is missing. */
15
+ export function loadCloudinaryEnv() {
16
+ if (existsSync(envPath)) {
17
+ process.loadEnvFile(envPath);
18
+ }
19
+ const cloudName = process.env.CLOUDINARY_CLOUD_NAME;
20
+ const apiKey = process.env.CLOUDINARY_API_KEY;
21
+ const apiSecret = process.env.CLOUDINARY_API_SECRET;
22
+ const missing = [
23
+ !cloudName && 'CLOUDINARY_CLOUD_NAME',
24
+ !apiKey && 'CLOUDINARY_API_KEY',
25
+ !apiSecret && 'CLOUDINARY_API_SECRET',
26
+ ].filter((v) => Boolean(v));
27
+ if (missing.length) {
28
+ throw new Error(`Missing Cloudinary credentials: ${missing.join(', ')}.\n` +
29
+ `Add them to an untracked .env at the repo root (${envPath}) — see ` +
30
+ `skills/email-campaign/OPEN-QUESTIONS.md Q4:\n` +
31
+ ` CLOUDINARY_CLOUD_NAME=...\n CLOUDINARY_API_KEY=...\n CLOUDINARY_API_SECRET=...\n` +
32
+ `Use --dry-run to preview what would be uploaded without credentials.`);
33
+ }
34
+ return { cloudName: cloudName, apiKey: apiKey, apiSecret: apiSecret };
35
+ }
36
+ //# sourceMappingURL=cloudinary-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudinary-env.js","sourceRoot":"","sources":["../../src/build/cloudinary-env.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAA;AAClD,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;AAQzC,sFAAsF;AACtF,MAAM,UAAU,iBAAiB;IAC/B,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;IAC9B,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAA;IACnD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAA;IAC7C,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAA;IAEnD,MAAM,OAAO,GAAG;QACd,CAAC,SAAS,IAAI,uBAAuB;QACrC,CAAC,MAAM,IAAI,oBAAoB;QAC/B,CAAC,SAAS,IAAI,uBAAuB;KACtC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IAExC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CACb,mCAAmC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;YACxD,mDAAmD,OAAO,UAAU;YACpE,+CAA+C;YAC/C,sFAAsF;YACtF,sEAAsE,CACzE,CAAA;IACH,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,SAAU,EAAE,MAAM,EAAE,MAAO,EAAE,SAAS,EAAE,SAAU,EAAE,CAAA;AAC1E,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Shared manifest shape + path for the Cloudinary upload pipeline.
3
+ *
4
+ * `upload-assets.ts` writes this file; `compile.ts` reads it (if present) to swap a campaign
5
+ * asset's `src` for its hosted URL instead of inlining it as base64. Both import from here so the
6
+ * path and shape can never drift between writer and reader.
7
+ */
8
+ /** dist/ is gitignored — this is local, regenerable build output, not committed. */
9
+ export declare const cloudinaryManifestPath: string;
10
+ export interface CloudinaryManifestEntry {
11
+ /** Path relative to src/assets/, e.g. "sales-agent-activation/tp1-library-hero.png". */
12
+ sourcePath: string;
13
+ /** Top-level dir under src/emails/ that referenced this asset. */
14
+ campaign: string;
15
+ /** The specific email (fileBaseName) that referenced this asset. */
16
+ email: string;
17
+ /** Cloudinary secure_url returned by the upload. */
18
+ url: string;
19
+ bytes: number;
20
+ /** sha256 of the local file content — lets re-runs skip an unchanged upload. */
21
+ contentHash: string;
22
+ uploadedAt: string;
23
+ }
24
+ export interface CloudinaryManifest {
25
+ generatedFrom: string;
26
+ cloudName: string;
27
+ /** Keyed by Cloudinary public_id, e.g. "lifecycle/sales-agent-activation/tp1a-library/tp1-library-hero". */
28
+ assets: Record<string, CloudinaryManifestEntry>;
29
+ }
30
+ export declare function loadCloudinaryManifest(): CloudinaryManifest | null;
31
+ /** Lookup keyed by `${campaign}/${email}/${filename}` for O(1) resolution during compile. */
32
+ export declare function indexManifestByCampaignEmailFile(manifest: CloudinaryManifest): Map<string, string>;
33
+ //# sourceMappingURL=cloudinary-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudinary-manifest.d.ts","sourceRoot":"","sources":["../../src/build/cloudinary-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,oFAAoF;AACpF,eAAO,MAAM,sBAAsB,QAAsD,CAAA;AAEzF,MAAM,WAAW,uBAAuB;IACtC,wFAAwF;IACxF,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,4GAA4G;IAC5G,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAA;CAChD;AAED,wBAAgB,sBAAsB,IAAI,kBAAkB,GAAG,IAAI,CAOlE;AAED,6FAA6F;AAC7F,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,kBAAkB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQlG"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Shared manifest shape + path for the Cloudinary upload pipeline.
3
+ *
4
+ * `upload-assets.ts` writes this file; `compile.ts` reads it (if present) to swap a campaign
5
+ * asset's `src` for its hosted URL instead of inlining it as base64. Both import from here so the
6
+ * path and shape can never drift between writer and reader.
7
+ */
8
+ import { existsSync, readFileSync } from 'node:fs';
9
+ import { dirname, resolve } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const pkgDir = resolve(__dirname, '..', '..');
13
+ /** dist/ is gitignored — this is local, regenerable build output, not committed. */
14
+ export const cloudinaryManifestPath = resolve(pkgDir, 'dist', 'cloudinary-manifest.json');
15
+ export function loadCloudinaryManifest() {
16
+ if (!existsSync(cloudinaryManifestPath))
17
+ return null;
18
+ try {
19
+ return JSON.parse(readFileSync(cloudinaryManifestPath, 'utf-8'));
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ /** Lookup keyed by `${campaign}/${email}/${filename}` for O(1) resolution during compile. */
26
+ export function indexManifestByCampaignEmailFile(manifest) {
27
+ const index = new Map();
28
+ for (const entry of Object.values(manifest.assets)) {
29
+ const filename = entry.sourcePath.split('/').pop();
30
+ if (!filename)
31
+ continue;
32
+ index.set(`${entry.campaign}/${entry.email}/${filename}`, entry.url);
33
+ }
34
+ return index;
35
+ }
36
+ //# sourceMappingURL=cloudinary-manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudinary-manifest.js","sourceRoot":"","sources":["../../src/build/cloudinary-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AACzD,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;AAE7C,oFAAoF;AACpF,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,0BAA0B,CAAC,CAAA;AAwBzF,MAAM,UAAU,sBAAsB;IACpC,IAAI,CAAC,UAAU,CAAC,sBAAsB,CAAC;QAAE,OAAO,IAAI,CAAA;IACpD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAuB,CAAA;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,gCAAgC,CAAC,QAA4B;IAC3E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAA;IACvC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAA;QAClD,IAAI,CAAC,QAAQ;YAAE,SAAQ;QACvB,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,KAAK,IAAI,QAAQ,EAAE,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;IACtE,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * MJML compile pipeline.
3
+ *
4
+ * Discovers the container, section previews, and full email compositions; compiles each with MJML;
5
+ * writes standalone HTML (+ Braze + static variants for emails) to dist/emails/, and a dist/catalog.json
6
+ * that Storybook and the docs read from.
7
+ *
8
+ * Run via tsx (not the emitted dist copy). Pass --watch to recompile on change.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=compile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/build/compile.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}