@popcraft/kit 0.25.0 → 0.26.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 (229) hide show
  1. package/dist/chunks/{agent-45ZGYKTC.js → agent-NTEAEZBN.js} +1 -1
  2. package/dist/chunks/{build-K6QBTLT7.js → build-W5O2ZVN7.js} +2 -2
  3. package/dist/chunks/{builtin-ZS3HJHZL.js → builtin-KX2SGDND.js} +3 -3
  4. package/dist/chunks/{check-template-RZ7IO333.js → check-template-B2KKE6BW.js} +2 -2
  5. package/dist/chunks/{chunk-ZKHYC3QO.js → chunk-37WES5PM.js} +2 -2
  6. package/dist/chunks/chunk-45JABNLR.js +161 -0
  7. package/dist/chunks/chunk-4OGILN7Y.js +141 -0
  8. package/dist/chunks/{chunk-63X37RCC.js → chunk-5NWVUAWR.js} +4 -4
  9. package/dist/chunks/{chunk-QREG6HEK.js → chunk-67SLLJJB.js} +40 -40
  10. package/dist/chunks/chunk-6DV5JZ5M.js +121 -0
  11. package/dist/chunks/chunk-7Q67MXXL.js +140 -0
  12. package/dist/chunks/{chunk-R5CW4CNP.js → chunk-A6GPFR5O.js} +11 -11
  13. package/dist/chunks/chunk-AISWLDDO.js +895 -0
  14. package/dist/chunks/{chunk-ADZXK6SM.js → chunk-AQGECTRJ.js} +1 -1
  15. package/dist/chunks/chunk-ASWWZNTY.js +130 -0
  16. package/dist/chunks/chunk-BNEFANT2.js +134 -0
  17. package/dist/chunks/{chunk-REJO7GXO.js → chunk-CFEKYNGQ.js} +2 -2
  18. package/dist/chunks/{chunk-5HG4LWF5.js → chunk-D4HEDPVN.js} +161 -0
  19. package/dist/chunks/chunk-DRESZDOP.js +130 -0
  20. package/dist/chunks/{chunk-3W2MSVJA.js → chunk-EBLHS2EG.js} +5 -5
  21. package/dist/chunks/chunk-EHTLT6XJ.js +131 -0
  22. package/dist/chunks/{chunk-2AQK4IIV.js → chunk-EVFG2K4L.js} +1 -1
  23. package/dist/chunks/{chunk-6TJIEERG.js → chunk-F4NTIOTT.js} +26 -26
  24. package/dist/chunks/chunk-FC7B22QS.js +150 -0
  25. package/dist/chunks/{chunk-V6COW3DB.js → chunk-G6MIC23V.js} +1 -1
  26. package/dist/chunks/{chunk-4QBQ4RAA.js → chunk-GODXMHO3.js} +4 -4
  27. package/dist/chunks/chunk-GT22XGDH.js +152 -0
  28. package/dist/chunks/chunk-HL4M53ZR.js +143 -0
  29. package/dist/chunks/{chunk-MPI6SCA7.js → chunk-IDL5KKIC.js} +5 -5
  30. package/dist/chunks/{chunk-HR7HCFPX.js → chunk-IM6HORNC.js} +5 -5
  31. package/dist/chunks/{chunk-UVMU6CXR.js → chunk-IWCZDFXL.js} +17 -17
  32. package/dist/chunks/{chunk-OCSLBM6W.js → chunk-JXAJ6OST.js} +1 -1
  33. package/dist/chunks/{chunk-5WGMPGKY.js → chunk-KS7DVWDB.js} +1 -1
  34. package/dist/chunks/chunk-NHDBQ4G7.js +137 -0
  35. package/dist/chunks/chunk-NKPU5TF2.js +103 -0
  36. package/dist/chunks/chunk-NO734F3F.js +146 -0
  37. package/dist/chunks/{chunk-ASWAIYNC.js → chunk-NR2SFSJU.js} +3 -3
  38. package/dist/chunks/{chunk-CJY4TNOS.js → chunk-O3BEBIDN.js} +3 -1
  39. package/dist/chunks/{chunk-UDO3ZU5T.js → chunk-OR6HMJRM.js} +1 -1
  40. package/dist/chunks/chunk-OTICJQ4T.js +267 -0
  41. package/dist/chunks/chunk-PWZZRGWI.js +141 -0
  42. package/dist/chunks/{chunk-AE6S2M2B.js → chunk-QX4U3VTU.js} +25 -25
  43. package/dist/chunks/{chunk-TRUFIU5W.js → chunk-R3YD6VL6.js} +3 -3
  44. package/dist/chunks/{chunk-YSYZW4VZ.js → chunk-SFPM3UJA.js} +7 -7
  45. package/dist/chunks/chunk-SNXQM4UO.js +115 -0
  46. package/dist/chunks/chunk-T3LV66CQ.js +139 -0
  47. package/dist/chunks/chunk-TDGIO2S7.js +145 -0
  48. package/dist/chunks/chunk-TFNV2UP4.js +87 -0
  49. package/dist/chunks/chunk-UJZXKOTP.js +136 -0
  50. package/dist/chunks/{chunk-QAVWMRRM.js → chunk-V5HI55XO.js} +2 -2
  51. package/dist/chunks/chunk-VVNPBBPL.js +174 -0
  52. package/dist/chunks/{chunk-22RNC42G.js → chunk-YHA45NK5.js} +1 -1
  53. package/dist/chunks/{chunk-27EVM272.js → chunk-YPMT4Y4N.js} +2 -2
  54. package/dist/chunks/chunk-YRSDFRCX.js +122 -0
  55. package/dist/chunks/chunk-ZNARVSMD.js +140 -0
  56. package/dist/chunks/{chunk-3DWOQ4LW.js → chunk-ZTBKAGBN.js} +2 -2
  57. package/dist/chunks/{client-6DUSRTZG.js → client-25NJXQWL.js} +1 -1
  58. package/dist/chunks/{client-XYPBNSQ7.js → client-7AQTKZHV.js} +2 -2
  59. package/dist/chunks/{client-TRFNJJHQ.js → client-LWBTUXQK.js} +2 -2
  60. package/dist/chunks/{client-actions-SSUZUEG7.js → client-actions-3HIRRSOL.js} +4 -4
  61. package/dist/chunks/{dangerous-UX4DHJ7U.js → dangerous-XJOHEWL6.js} +3 -3
  62. package/dist/chunks/{editor-seams-V7ZP7XH3.js → editor-seams-4DREZRTT.js} +9 -9
  63. package/dist/chunks/{editor-store-OIHZT244.js → editor-store-C4VWC23J.js} +1 -1
  64. package/dist/chunks/{export-watermark-4NAP5RVM.js → export-watermark-OKGRZNOU.js} +5 -5
  65. package/dist/chunks/{eyedropper-QXEY7LVL.js → eyedropper-WZYSTUTM.js} +1 -1
  66. package/dist/chunks/{generate-media-JAEHWNF6.js → generate-media-DFSHOPOI.js} +2 -2
  67. package/dist/chunks/{host-IKFYVZS7.js → host-4ZE7SIWR.js} +6 -6
  68. package/dist/chunks/{integrations-U5X2BHVK.js → integrations-LPC35FKX.js} +1 -1
  69. package/dist/chunks/{kit-modules-BAUMECYQ.js → kit-modules-BAKTDLOZ.js} +1 -1
  70. package/dist/chunks/{libraries-Q5TCHWGI.js → libraries-ACERXG2O.js} +5 -5
  71. package/dist/chunks/{live-4XQEGNLA.js → live-ZIVBFX3G.js} +3 -3
  72. package/dist/chunks/{media-tool-OSQR45HY.js → media-tool-2ZPIQKJD.js} +1 -1
  73. package/dist/chunks/{new-file-VVLWHEJ4.js → new-file-XS3KCMGM.js} +3 -3
  74. package/dist/chunks/{plugin-44ZW5UEP.js → plugin-6RNMUP5I.js} +2 -2
  75. package/dist/chunks/{plugin-import-3ISLDX7T.js → plugin-import-UC5BHZFE.js} +13 -13
  76. package/dist/chunks/{presence-WVU4OIUA.js → presence-TLDOMBPD.js} +2 -2
  77. package/dist/chunks/{project-EG7XFEJU.js → project-NMPK4R5M.js} +1 -1
  78. package/dist/chunks/{publish-site-WFIX42D3.js → publish-site-R46ZILSS.js} +6 -6
  79. package/dist/chunks/{publish-store-6BS7OIP4.js → publish-store-MIPHEDLV.js} +4 -4
  80. package/dist/chunks/{raster-actions-37XUQSVZ.js → raster-actions-7EI5THLB.js} +1 -1
  81. package/dist/chunks/{runner-HZ7MU6ER.js → runner-I66TTKKZ.js} +16 -16
  82. package/dist/chunks/{store-PHWBXKNY.js → store-7WMR3JKP.js} +5 -5
  83. package/dist/chunks/{sync-IJNKHQRF.js → sync-OFINS45Q.js} +5 -5
  84. package/dist/chunks/{tabs-G6OTEGA5.js → tabs-PEH6VX3X.js} +8 -8
  85. package/dist/chunks/{team-M2EN4JT5.js → team-GGW3O32G.js} +1 -1
  86. package/dist/chunks/{tools-WDFCY4BH.js → tools-TKCA46ZS.js} +2 -2
  87. package/dist/chunks/{ui-commands-QHFGR33P.js → ui-commands-6K4PYKU5.js} +11 -11
  88. package/dist/chunks/{versions-S7COCZSQ.js → versions-KHRNQFGF.js} +5 -5
  89. package/dist/index.js +4 -4
  90. package/dist/seed/files.json +1 -1
  91. package/dist/templates/count.js +1 -1
  92. package/dist/templates/index.js +125 -101
  93. package/dist/templates/index.json +756 -0
  94. package/dist/templates/motion/landing-hero/index.js +4 -4
  95. package/dist/templates/motion/launch-trailer/document.js +125 -101
  96. package/dist/templates/motion/launch-trailer/index.js +4 -4
  97. package/dist/templates/motion/launch-trailer/scenes.js +3 -3
  98. package/dist/templates/motion/template-wall.js +2 -2
  99. package/dist/templates/print/holidays-christmas-church-newsletter.js +94 -0
  100. package/dist/templates/print/holidays-christmas-eve-invitation.js +94 -0
  101. package/dist/templates/print/holidays-christmas-gift-tags.js +94 -0
  102. package/dist/templates/print/holidays-diwali-party-invitation.js +95 -0
  103. package/dist/templates/print/holidays-easter-activity-sheet.js +94 -0
  104. package/dist/templates/print/holidays-easter-egg-hunt-flyer.js +94 -0
  105. package/dist/templates/print/holidays-eid-dinner-invitation.js +95 -0
  106. package/dist/templates/print/holidays-eid-gift-tags.js +94 -0
  107. package/dist/templates/print/holidays-halloween-party-invitation.js +95 -0
  108. package/dist/templates/print/holidays-halloween-treat-labels.js +94 -0
  109. package/dist/templates/print/holidays-halloween-trunk-or-treat-poster.js +94 -0
  110. package/dist/templates/print/holidays-hanukkah-activity-sheet.js +94 -0
  111. package/dist/templates/print/holidays-hanukkah-party-invitation.js +94 -0
  112. package/dist/templates/print/holidays-lunar-new-year-banquet-menu.js +95 -0
  113. package/dist/templates/print/holidays-new-years-eve-poster.js +94 -0
  114. package/dist/templates/print/holidays-thanksgiving-dinner-menu.js +94 -0
  115. package/dist/templates/print/holidays-thanksgiving-place-cards.js +94 -0
  116. package/dist/templates/print/holidays-valentines-dinner-menu.js +94 -0
  117. package/dist/templates/saas/email-decks.js +2 -2
  118. package/dist/templates/saas/index.js +37 -37
  119. package/dist/templates/saas/launch-kit.js +2 -2
  120. package/dist/templates/saas/launch-video.js +2 -2
  121. package/dist/templates/saas/stores.js +2 -2
  122. package/dist/templates/saas/style-bento.js +2 -2
  123. package/dist/templates/saas/style-dark.js +2 -2
  124. package/dist/templates/saas/style-editorial.js +2 -2
  125. package/dist/templates/saas/style-gradient.js +2 -2
  126. package/dist/templates/saas/style-pastel.js +2 -2
  127. package/dist/templates/saas/style-swiss.js +2 -2
  128. package/dist/templates/slideshow/index.js +3 -3
  129. package/dist/templates/social/holidays-diwali-sweet-shop-post.js +96 -0
  130. package/dist/templates/social/holidays-lunar-new-year-bakery-post.js +96 -0
  131. package/dist/templates/social/holidays-new-years-eve-shop-hours.js +96 -0
  132. package/dist/templates/social/holidays-thanksgiving-pie-orders-post.js +96 -0
  133. package/dist/templates/social/holidays-valentines-florist-post.js +96 -0
  134. package/dist/templates/themes/black-friday-coupons.js +2 -2
  135. package/dist/templates/themes/black-friday-neon.js +2 -2
  136. package/dist/templates/themes/blueprint.js +2 -2
  137. package/dist/templates/themes/broadsheet.js +2 -2
  138. package/dist/templates/themes/cyberpunk.js +2 -2
  139. package/dist/templates/themes/departure-board.js +2 -2
  140. package/dist/templates/themes/festival-presale-family-a.js +2 -2
  141. package/dist/templates/themes/festival-presale-family-b.js +2 -2
  142. package/dist/templates/themes/holidays-shared.js +172 -0
  143. package/dist/templates/themes/holidays.js +107 -1
  144. package/dist/templates/themes/index.js +72 -48
  145. package/dist/templates/themes/lunar-new-year.js +2 -2
  146. package/dist/templates/themes/noir.js +2 -2
  147. package/dist/templates/themes/psychedelic.js +2 -2
  148. package/dist/templates/themes/space-opera.js +2 -2
  149. package/dist/templates/themes/studio.js +3 -3
  150. package/dist/templates/themes/tour-tickets.js +2 -2
  151. package/dist/templates/themes/vaporwave.js +2 -2
  152. package/package.json +1 -1
  153. package/src/templates/print/holidays-christmas-church-newsletter.ts +246 -0
  154. package/src/templates/print/holidays-christmas-eve-invitation.ts +121 -0
  155. package/src/templates/print/holidays-christmas-gift-tags.ts +117 -0
  156. package/src/templates/print/holidays-diwali-party-invitation.ts +98 -0
  157. package/src/templates/print/holidays-easter-activity-sheet.ts +108 -0
  158. package/src/templates/print/holidays-easter-egg-hunt-flyer.ts +118 -0
  159. package/src/templates/print/holidays-eid-dinner-invitation.ts +104 -0
  160. package/src/templates/print/holidays-eid-gift-tags.ts +94 -0
  161. package/src/templates/print/holidays-halloween-party-invitation.ts +101 -0
  162. package/src/templates/print/holidays-halloween-treat-labels.ts +86 -0
  163. package/src/templates/print/holidays-halloween-trunk-or-treat-poster.ts +148 -0
  164. package/src/templates/print/holidays-hanukkah-activity-sheet.ts +112 -0
  165. package/src/templates/print/holidays-hanukkah-party-invitation.ts +112 -0
  166. package/src/templates/print/holidays-lunar-new-year-banquet-menu.ts +110 -0
  167. package/src/templates/print/holidays-new-years-eve-poster.ts +117 -0
  168. package/src/templates/print/holidays-thanksgiving-dinner-menu.ts +110 -0
  169. package/src/templates/print/holidays-thanksgiving-place-cards.ts +80 -0
  170. package/src/templates/print/holidays-valentines-dinner-menu.ts +95 -0
  171. package/src/templates/social/holidays-diwali-sweet-shop-post.ts +121 -0
  172. package/src/templates/social/holidays-lunar-new-year-bakery-post.ts +110 -0
  173. package/src/templates/social/holidays-new-years-eve-shop-hours.ts +118 -0
  174. package/src/templates/social/holidays-thanksgiving-pie-orders-post.ts +115 -0
  175. package/src/templates/social/holidays-valentines-florist-post.ts +102 -0
  176. package/src/templates/themes/holidays-shared.ts +903 -0
  177. package/src/templates/themes/holidays.ts +24 -1
  178. package/types/templates/src/print/holidays-christmas-church-newsletter.d.ts +2 -0
  179. package/types/templates/src/print/holidays-christmas-eve-invitation.d.ts +2 -0
  180. package/types/templates/src/print/holidays-christmas-gift-tags.d.ts +2 -0
  181. package/types/templates/src/print/holidays-diwali-party-invitation.d.ts +2 -0
  182. package/types/templates/src/print/holidays-easter-activity-sheet.d.ts +2 -0
  183. package/types/templates/src/print/holidays-easter-egg-hunt-flyer.d.ts +2 -0
  184. package/types/templates/src/print/holidays-eid-dinner-invitation.d.ts +2 -0
  185. package/types/templates/src/print/holidays-eid-gift-tags.d.ts +2 -0
  186. package/types/templates/src/print/holidays-halloween-party-invitation.d.ts +2 -0
  187. package/types/templates/src/print/holidays-halloween-treat-labels.d.ts +2 -0
  188. package/types/templates/src/print/holidays-halloween-trunk-or-treat-poster.d.ts +2 -0
  189. package/types/templates/src/print/holidays-hanukkah-activity-sheet.d.ts +2 -0
  190. package/types/templates/src/print/holidays-hanukkah-party-invitation.d.ts +2 -0
  191. package/types/templates/src/print/holidays-lunar-new-year-banquet-menu.d.ts +2 -0
  192. package/types/templates/src/print/holidays-new-years-eve-poster.d.ts +2 -0
  193. package/types/templates/src/print/holidays-thanksgiving-dinner-menu.d.ts +2 -0
  194. package/types/templates/src/print/holidays-thanksgiving-place-cards.d.ts +2 -0
  195. package/types/templates/src/print/holidays-valentines-dinner-menu.d.ts +2 -0
  196. package/types/templates/src/social/holidays-diwali-sweet-shop-post.d.ts +2 -0
  197. package/types/templates/src/social/holidays-lunar-new-year-bakery-post.d.ts +2 -0
  198. package/types/templates/src/social/holidays-new-years-eve-shop-hours.d.ts +2 -0
  199. package/types/templates/src/social/holidays-thanksgiving-pie-orders-post.d.ts +2 -0
  200. package/types/templates/src/social/holidays-valentines-florist-post.d.ts +2 -0
  201. package/types/templates/src/themes/holidays-shared.d.ts +374 -0
  202. package/dist/chunks/chunk-OWZLREWX.js +0 -18
  203. package/dist/chunks/{chunk-IFJTWD4O.js → chunk-2R3MZKA2.js} +3 -3
  204. package/dist/chunks/{chunk-P67L32AF.js → chunk-3DBNMCGN.js} +3 -3
  205. package/dist/chunks/{chunk-DZDTVMUO.js → chunk-3VHOXVMF.js} +3 -3
  206. package/dist/chunks/{chunk-2PQI2R5L.js → chunk-64RMGOTG.js} +0 -0
  207. package/dist/chunks/{chunk-7BDCMMSD.js → chunk-6Y46SDFE.js} +3 -3
  208. package/dist/chunks/{chunk-6VCQ4ZEG.js → chunk-AFARXTNQ.js} +3 -3
  209. package/dist/chunks/{chunk-ITSYENCT.js → chunk-BI3M4RUH.js} +3 -3
  210. package/dist/chunks/{chunk-25FM5JIR.js → chunk-BP7CHO7K.js} +3 -3
  211. package/dist/chunks/{chunk-D22534DX.js → chunk-DPTFZDOS.js} +3 -3
  212. package/dist/chunks/{chunk-JWLR6K62.js → chunk-FNUM5W3W.js} +3 -3
  213. package/dist/chunks/{chunk-JPH6HOYU.js → chunk-HJQU5POV.js} +3 -3
  214. package/dist/chunks/{chunk-HQYZ4URA.js → chunk-IJ65QZLL.js} +3 -3
  215. package/dist/chunks/{chunk-47VVF5TM.js → chunk-INT2OPPY.js} +8 -8
  216. package/dist/chunks/{chunk-AYVAHMWQ.js → chunk-JPMLUHZX.js} +3 -3
  217. package/dist/chunks/{chunk-LFYNAAAQ.js → chunk-LMRTRVPC.js} +3 -3
  218. package/dist/chunks/{chunk-HJIMF4NA.js → chunk-NGMCQZA6.js} +3 -3
  219. package/dist/chunks/{chunk-B3ASV6KS.js → chunk-PQBYL7AI.js} +3 -3
  220. package/dist/chunks/{chunk-NHXNUDC5.js → chunk-QD4QORNR.js} +3 -3
  221. package/dist/chunks/{chunk-P6TPDLAF.js → chunk-S3C6L5KF.js} +3 -3
  222. package/dist/chunks/{chunk-TIRPXUK2.js → chunk-S7L2D7NH.js} +3 -3
  223. package/dist/chunks/{chunk-QFGFR5FQ.js → chunk-TYOOL27R.js} +3 -3
  224. package/dist/chunks/{chunk-32V4D45K.js → chunk-TYW7EXV6.js} +3 -3
  225. package/dist/chunks/{chunk-3QIDCP4M.js → chunk-VOEAQIQT.js} +3 -3
  226. package/dist/chunks/{chunk-ASVDOX62.js → chunk-VVUQG4ME.js} +3 -3
  227. package/dist/chunks/{chunk-ZXZ22FKF.js → chunk-XTWKSMJC.js} +3 -3
  228. package/dist/chunks/{chunk-6NG5K7WN.js → chunk-YHEMML5Q.js} +3 -3
  229. package/dist/chunks/{chunk-MVFPEBMY.js → chunk-ZKRSVNY4.js} +3 -3
@@ -0,0 +1,117 @@
1
+ // A poster for a New Year's Eve dance at a neighbourhood ballroom, in black and gold with a little Art Deco: fireworks
2
+ // burst in gold, rose and silver across the top, "Midnight" is set huge in a serif italic over "at the Marlowe" in
3
+ // spaced capitals, three facts sit in a row (doors, the toast, last dance), and two coupes clink at the foot over a
4
+ // sunburst that rises from below the trim. 18 × 24 inches for the window and the noticeboard, with an eighth of an
5
+ // inch of bleed, and a US Letter flyer of the same design, its drawing worked out again at that size.
6
+
7
+ import { Kit, preset } from '../kit'
8
+ import { defineTemplate } from '../build'
9
+ import { SERIF } from '../shared/type'
10
+ import type { VectorPath } from '@/lib/document/types'
11
+ import { burst, coupe, disc, draw, flourishComponent, polyP, printVariant, sparkle, specksIn, type Drawn } from '../themes/holidays-shared'
12
+
13
+ const NAME = 'New Year’s Eve dance poster'
14
+
15
+ function art(w: number, h: number): Drawn[] {
16
+ const s = w / 1728, B = 12 * s * 2
17
+ const fw = [[0.2, 0.11, 0.15, 'gold', 22], [0.74, 0.09, 0.19, 'rose', 26], [0.48, 0.17, 0.08, 'silver', 16], [0.92, 0.19, 0.065, 'gold', 14], [0.07, 0.2, 0.06, 'silver', 12]] as const
18
+ const bursts = fw.map(([x, y, r, c, n], i) => ({ c, b: burst(w * x, h * y, w * r, n, 11 + i) }))
19
+ const by = (c: string) => bursts.filter(b => b.c === c).map(b => b.b)
20
+ // A sunburst rising from below the foot.
21
+ const cx = w / 2, oy = h + 40 * s
22
+ const rays: VectorPath[] = []
23
+ for (let i = 0; i < 26; i += 2) {
24
+ const a0 = Math.PI + (i * Math.PI) / 26, a1 = Math.PI + ((i + 1) * Math.PI) / 26, R = h * 0.36
25
+ rays.push(polyP([{ x: cx, y: oy }, { x: cx + Math.cos(a0) * R, y: oy + Math.sin(a0) * R }, { x: cx + Math.cos(a1) * R, y: oy + Math.sin(a1) * R }]))
26
+ }
27
+ const left = coupe(cx - 250 * s, h - 70 * s, 400 * s, 0.26), right = coupe(cx + 250 * s, h - 70 * s, 400 * s, -0.26)
28
+ const bubbles = [[-40, 420], [-10, 455], [30, 440], [60, 470], [5, 395], [-70, 445], [90, 420], [-20, 375]].map(([x, y], i) => disc(cx + x * s, h - y * s, (i % 3 ? 5 : 8) * s))
29
+ const fizz = [[-90, 466, 20], [110, 450, 24], [-480, 300, 16], [490, 330, 18]].map(([x, y, r]) => sparkle(cx + x * s, h - y * s, r * s))
30
+ return [
31
+ { name: 'Sunburst', paths: rays, color: 'gold', o: { opacity: 0.07 } },
32
+ ...[0, 1].map(i => ({ name: 'Stardust', paths: specksIn({ x: -B, y: -B + i * h * 0.13, w: w + 2 * B, h: h * 0.13 }, 1400 * s * s, 70 + i, 4 * s), color: 'silver', o: { opacity: 0.5 } })),
33
+ { name: 'Gold rays', paths: by('gold').flatMap(b => b.rays), color: 'gold', o: { strokeWidth: 5 * s } },
34
+ { name: 'Gold sparks', paths: by('gold').flatMap(b => b.sparks), color: 'gold' },
35
+ { name: 'Rose rays', paths: by('rose').flatMap(b => b.rays), color: 'rose', o: { strokeWidth: 5 * s } },
36
+ { name: 'Rose sparks', paths: by('rose').flatMap(b => b.sparks), color: 'rose' },
37
+ { name: 'Silver rays', paths: by('silver').flatMap(b => b.rays), color: 'silver', o: { strokeWidth: 4 * s } },
38
+ { name: 'Silver sparks', paths: by('silver').flatMap(b => b.sparks), color: 'silver' },
39
+ { name: 'Wine', paths: [...left.wine, ...right.wine], color: 'gold', o: { opacity: 0.85 } },
40
+ { name: 'Glasses', paths: [...left.glass, ...right.glass], color: 'silver', o: { strokeWidth: 6 * s } },
41
+ { name: 'Bubbles', paths: bubbles, color: 'gold' },
42
+ { name: 'Fizz', paths: fizz, color: 'gold' },
43
+ ]
44
+ }
45
+
46
+ const PAD = { top: 720, right: 140, bottom: 560, left: 140 }
47
+
48
+ export default defineTemplate({
49
+ id: 'print-holidays-new-years-eve-poster',
50
+ meta: {
51
+ name: NAME,
52
+ description: 'A black-and-gold poster for a New Year’s Eve dance at a neighbourhood ballroom: fireworks in gold, rose and silver, “Midnight” set huge in a serif italic over spaced capitals, doors, the toast and the last dance in a row, and two clinking coupes over an Art Deco sunburst. 18 × 24 inches with bleed, and a US Letter flyer',
53
+ category: 'poster', tags: ['new year’s eve', 'new year', 'nye', 'party', 'dance', 'ballroom', 'fireworks', 'champagne', 'art deco', 'gold', 'poster', 'flyer', 'print', 'event'],
54
+ platforms: ['print'], formats: ['poster', 'flyer'], useCases: ['event', 'announcement'],
55
+ created: '2026-10-10', updated: '2026-10-10',
56
+ },
57
+ stableNumbers: true,
58
+ build() {
59
+ const k = new Kit(NAME, preset('poster-18x24'))
60
+ k.quantizeGeometry = true
61
+ k.brand(
62
+ { paper: '#0F0E14', text: '#F7EDD6', onPaper: '#D9B66C', primary: '#D9B66C', onPrimary: '#0F0E14' },
63
+ { name: 'The Marlowe Ballroom', site: 'marloweballroom.com' },
64
+ )
65
+ k.palette({ gold: '#D9B66C', rose: '#E8939F', silver: '#CBD3E4' })
66
+ k.textStyle('Kicker 40', { size: 40, fontWeight: 600, letterSpacing: 12, lineHeight: 52 })
67
+ k.textStyle('Title 300', { ...SERIF, size: 300, fontWeight: 400, fontStyle: 'italic', letterSpacing: -9, lineHeight: 290 })
68
+ k.textStyle('Sub 76', { size: 76, fontWeight: 300, letterSpacing: 22, lineHeight: 92 })
69
+ k.textStyle('Line 46', { ...SERIF, size: 46, fontWeight: 400, lineHeight: 62 })
70
+ k.textStyle('Fact label 26', { size: 26, fontWeight: 700, letterSpacing: 6, lineHeight: 34 })
71
+ k.textStyle('Fact 56', { ...SERIF, size: 56, fontWeight: 600, lineHeight: 64 })
72
+ k.textStyle('Foot 34', { size: 34, fontWeight: 600, letterSpacing: 4, lineHeight: 44 })
73
+
74
+ flourishComponent(k, 'Diamond rule', 'primary', polyP([{ x: 0, y: 6 }, { x: 6, y: 0 }, { x: 12, y: 6 }, { x: 6, y: 12 }]), { rule: 120, size: 22, gap: 18 })
75
+
76
+ const sheet = k.sheet
77
+ const { width: W, height: H } = k.node(sheet)
78
+ k.flowSheet(sheet, 0, PAD, 'paper', 'MIN', 'CENTER')
79
+ draw(k, sheet, art(W, H))
80
+ const c = { align: 'CENTER' as const, width: 'FILL' as const }
81
+ k.text(sheet, 'NEW YEAR’S EVE · THURSDAY 31 DECEMBER', { style: 'Kicker 40', color: 'onPaper', ...c, name: 'Kicker' })
82
+ k.stack(sheet, { height: 40, name: 'Gap' })
83
+ k.text(sheet, 'Midnight', { style: 'Title 300', color: 'text', ...c, name: 'Title' })
84
+ k.text(sheet, 'AT THE MARLOWE', { style: 'Sub 76', color: 'text', ...c, name: 'Sub' })
85
+ k.stack(sheet, { height: 60, name: 'Gap' })
86
+ k.instance(sheet, 'Diamond rule', {}, { name: 'Divider' })
87
+ k.stack(sheet, { height: 60, name: 'Gap' })
88
+ k.text(sheet, 'Live swing from the Dellwood Seven, a dance floor that stays open till one, a midnight toast and a balloon drop. Black tie optional, dancing shoes essential.', { style: 'Line 46', color: 'text', ...c, name: 'Body' })
89
+ k.stack(sheet, { height: 70, name: 'Gap' })
90
+ const facts = k.stack(sheet, { direction: 'HORIZONTAL', gap: 40, width: 'HUG', name: 'Facts' })
91
+ for (const [l, v] of [['DOORS', '8 pm'], ['THE TOAST', 'Midnight'], ['TICKETS', '$45 · $25']]) {
92
+ const f = k.stack(facts, { gap: 6, width: 380, align: 'CENTER', name: 'Fact' })
93
+ k.text(f, l, { style: 'Fact label 26', color: 'onPaper', align: 'CENTER', width: 'FILL', name: 'Fact label' })
94
+ k.text(f, v, { style: 'Fact 56', color: 'text', align: 'CENTER', width: 'FILL', name: 'Fact value' })
95
+ }
96
+ k.stack(sheet, { height: 70, name: 'Gap' })
97
+ k.text(sheet, '{{brand.name}} · 214 Front Street · {{brand.site}}', { style: 'Foot 34', color: 'onPaper', ...c, name: 'Foot' })
98
+
99
+ k.textStyle('Kicker 19', { size: 19, fontWeight: 600, letterSpacing: 5.6, lineHeight: 25 })
100
+ k.textStyle('Title 140', { ...SERIF, size: 122, fontWeight: 400, fontStyle: 'italic', letterSpacing: -3.6, lineHeight: 116 })
101
+ k.textStyle('Sub 36', { size: 36, fontWeight: 300, letterSpacing: 10, lineHeight: 44 })
102
+ k.textStyle('Line 21', { ...SERIF, size: 21, fontWeight: 400, lineHeight: 29 })
103
+ k.textStyle('Foot 16', { size: 16, fontWeight: 600, letterSpacing: 1.8, lineHeight: 21 })
104
+ k.textStyle('Fact label 12', { size: 12, fontWeight: 700, letterSpacing: 2.8, lineHeight: 16 })
105
+ k.textStyle('Fact 26', { ...SERIF, size: 26, fontWeight: 600, lineHeight: 30 })
106
+ k.variants(sheet, [printVariant('us-letter', {
107
+ art, pad: PAD, masterWidth: W,
108
+ styles: [['Kicker', 'Kicker 19'], ['Title', 'Title 140'], ['Sub', 'Sub 36'], ['Body', 'Line 21'], ['Foot', 'Foot 16'], ['Fact label', 'Fact label 12'], ['Fact value', 'Fact 26']],
109
+ more: (kit, id, s) => {
110
+ kit.patch(id, { padding: { top: 280, right: Math.round(140 * s), bottom: Math.round(560 * s), left: Math.round(140 * s) } })
111
+ for (const n of kit.named(id, 'Fact')) kit.patch(n, { width: Math.round(380 * s) })
112
+ for (const n of kit.named(id, 'Facts')) kit.patch(n, { itemSpacing: Math.round(40 * s) })
113
+ },
114
+ })])
115
+ return k.finish()
116
+ },
117
+ })
@@ -0,0 +1,110 @@
1
+ // A menu card for a family Thanksgiving dinner, to stand at each place: a half-wreath of autumn leaves (maple, oak,
2
+ // wheat and acorns in rust, gold and olive) arches over the top of a warm cream card, the family's name and the date
3
+ // sit inside it, and the meal follows course by course in a serif with small-capital headings, ending on a line of
4
+ // thanks. 5 × 7 inches with the leaves running into the bleed.
5
+
6
+ import { Kit, preset } from '../kit'
7
+ import { defineTemplate } from '../build'
8
+ import { SERIF } from '../shared/type'
9
+ import { acorn, draw, flourishComponent, leaf, maple, mapleVeins, pumpkin, wheat, type Drawn } from '../themes/holidays-shared'
10
+
11
+ const NAME = 'Thanksgiving dinner menu'
12
+ const PAD = { top: 266, right: 52, bottom: 36, left: 52 }
13
+
14
+ const MENU = [
15
+ ['TO START', 'Butternut squash soup with toasted pepitas\nGrandma Mai’s spring rolls'],
16
+ ['THE TABLE', 'Herb-roasted turkey and gravy\nCornbread and sausage stuffing'],
17
+ ['ON THE SIDE', 'Garlic mashed potatoes · Green beans with crispy shallots\nCranberry-orange relish · Parker House rolls'],
18
+ ['SOMETHING SWEET', 'Pumpkin pie and pecan pie, with whipped cream'],
19
+ ] as const
20
+
21
+ function art(w: number, h: number): Drawn[] {
22
+ const s = w / 480, cx = w / 2, cy = 200 * s, R = 178 * s
23
+ const leaves: { m: Drawn['paths']; v: Drawn['paths'] }[] = []
24
+ const golds: Drawn['paths'] = [], olives: Drawn['paths'] = [], rusts: Drawn['paths'] = [], wheatS: Drawn['paths'] = [], wheatG: Drawn['paths'] = [], nuts: Drawn['paths'] = [], caps: Drawn['paths'] = []
25
+ // Round the top half of a circle, from left to right: maple leaves in two tones, pointed leaves, wheat and acorns.
26
+ const n = 21
27
+ for (let i = 0; i < n; i++) {
28
+ const a = Math.PI * 0.96 + (i * Math.PI * 1.08) / (n - 1), x = cx + Math.cos(a) * R, y = cy + Math.sin(a) * R * 0.92
29
+ const turn = a + Math.PI / 2 + (i % 2 ? 0.45 : -0.45)
30
+ const kind = i % 4
31
+ if (kind === 0) { rusts.push(...maple(x, y, 62 * s, turn)); leaves.push({ m: [], v: mapleVeins(x, y, 62 * s, turn) }) }
32
+ else if (kind === 2) { golds.push(...maple(x, y, 50 * s, turn + 0.5)); leaves.push({ m: [], v: mapleVeins(x, y, 50 * s, turn + 0.5) }) }
33
+ else if (kind === 1) { const wh = wheat(x, y + 26 * s, 70 * s, Math.cos(a) * 0.35); wheatS.push(...wh.stalk); wheatG.push(...wh.grains) }
34
+ else { olives.push(leaf({ x, y }, 46 * s, 18 * s, a + 0.9), leaf({ x, y }, 40 * s, 16 * s, a - 0.9)) }
35
+ if (i % 5 === 2) { const ac = acorn(x - Math.cos(a) * 30 * s, y - Math.sin(a) * 30 * s, 26 * s, turn); nuts.push(...ac.nut); caps.push(...ac.cap) }
36
+ olives.push(leaf({ x, y }, 34 * s, 13 * s, a + Math.PI + (i % 2 ? 0.7 : -0.7)))
37
+ }
38
+ // A pumpkin and a gourd sit inside the arch.
39
+ const pk = pumpkin(cx - 14 * s, 186 * s, 96 * s, 70 * s), gd = pumpkin(cx + 50 * s, 186 * s, 54 * s, 40 * s)
40
+ const sprigs = [leaf({ x: cx - 50 * s, y: 182 * s }, 46 * s, 18 * s, Math.PI + 0.3), leaf({ x: cx + 74 * s, y: 182 * s }, 40 * s, 16 * s, -0.25)]
41
+ return [
42
+ { name: 'Centre leaves', paths: sprigs, color: 'olive' },
43
+ { name: 'Centre pumpkin', paths: pk.lobes, color: 'rust' },
44
+ { name: 'Centre gourd', paths: gd.lobes, color: 'mustard' },
45
+ { name: 'Centre creases', paths: [...pk.creases, ...gd.creases], color: 'rustDeep', o: { strokeWidth: 1.4 * s } },
46
+ { name: 'Centre stems', paths: [...pk.stem, ...gd.stem], color: 'bark' },
47
+ { name: 'Olive leaves', paths: olives, color: 'olive' },
48
+ { name: 'Wheat stalks', paths: wheatS, color: 'gold', o: { strokeWidth: 1.6 * s } },
49
+ { name: 'Wheat', paths: wheatG, color: 'gold' },
50
+ { name: 'Gold maples', paths: golds, color: 'mustard' },
51
+ { name: 'Maple leaves', paths: rusts, color: 'rust' },
52
+ { name: 'Maple veins', paths: leaves.flatMap(l => l.v), color: 'rustDeep', o: { strokeWidth: 1.2 * s } },
53
+ { name: 'Acorns', paths: nuts, color: 'acorn' },
54
+ { name: 'Acorn caps', paths: caps, color: 'bark' },
55
+ ]
56
+ }
57
+
58
+ export default defineTemplate({
59
+ id: 'print-holidays-thanksgiving-dinner-menu',
60
+ meta: {
61
+ name: NAME,
62
+ description: 'A menu card for a family Thanksgiving dinner: a half-wreath of maple and oak leaves, wheat and acorns in rust, gold and olive arches over the family’s name and the date, then the meal course by course in a serif with small-capital headings and a line of thanks. 5 × 7 inches with bleed',
63
+ category: 'menu', tags: ['thanksgiving', 'menu', 'dinner', 'family', 'autumn', 'fall', 'harvest', 'leaves', 'wreath', 'friendsgiving', 'place setting', 'print', 'card'],
64
+ platforms: ['print'], formats: ['card'], useCases: ['menu', 'event'],
65
+ created: '2026-10-10', updated: '2026-10-10',
66
+ },
67
+ stableNumbers: true,
68
+ build() {
69
+ const k = new Kit(NAME, preset('invitation-5x7'))
70
+ k.quantizeGeometry = true
71
+ k.brand(
72
+ { paper: '#FBF4E7', text: '#3B2416', onPaper: '#8A4B24', primary: '#A8481F', onPrimary: '#FFF8EE', line: '#E2CDB0' },
73
+ { name: 'the Nguyen family' },
74
+ )
75
+ k.palette({ rust: '#B4512A', rustDeep: '#7E3216', mustard: '#D69A2D', gold: '#C9A04A', olive: '#7C8A4A', acorn: '#A86F3D', bark: '#5E3F26' })
76
+ k.textStyle('Kicker 10', { size: 10, fontWeight: 700, letterSpacing: 3, lineHeight: 14 })
77
+ k.textStyle('Title 40', { ...SERIF, size: 40, fontWeight: 600, fontStyle: 'italic', letterSpacing: -0.8, lineHeight: 42 })
78
+ k.textStyle('Host 13', { ...SERIF, size: 13, fontWeight: 400, lineHeight: 18 })
79
+ k.textStyle('Course 9', { size: 9, fontWeight: 800, letterSpacing: 2.6, lineHeight: 12 })
80
+ k.textStyle('Dish 12.5', { ...SERIF, size: 12.5, fontWeight: 400, lineHeight: 18 })
81
+ k.textStyle('Thanks 12', { ...SERIF, size: 12, fontWeight: 400, fontStyle: 'italic', lineHeight: 17 })
82
+
83
+ k.component('Course', { gap: 4, width: 376, align: 'CENTER', props: { Course: 'COURSE', Dishes: 'What we are eating' } }, id => {
84
+ k.propText(id, 'Course', { style: 'Course 9', color: 'onPaper', align: 'CENTER', width: 'FILL', name: 'Course name' })
85
+ k.propText(id, 'Dishes', { style: 'Dish 12.5', color: 'text', align: 'CENTER', width: 'FILL', name: 'Dishes' })
86
+ })
87
+ flourishComponent(k, 'Leaf rule', 'primary', maple(0, 0, 20)[0], { rule: 34, size: 11 })
88
+
89
+ const sheet = k.sheet
90
+ const { width: W, height: H } = k.node(sheet)
91
+ k.flowSheet(sheet, 0, PAD, 'paper', 'MIN', 'CENTER')
92
+ draw(k, sheet, art(W, H))
93
+ const c = { align: 'CENTER' as const, width: 'FILL' as const }
94
+ k.text(sheet, 'THURSDAY 26 NOVEMBER', { style: 'Kicker 10', color: 'onPaper', ...c, name: 'Kicker' })
95
+ k.stack(sheet, { height: 6, name: 'Gap' })
96
+ k.text(sheet, 'Thanksgiving', { style: 'Title 40', color: 'text', ...c, name: 'Title' })
97
+ k.text(sheet, 'at the table of {{brand.name}}', { style: 'Host 13', color: 'onPaper', ...c, name: 'Host' })
98
+ k.stack(sheet, { height: 12, name: 'Gap' })
99
+ for (const [i, [course, dishes]] of MENU.entries()) {
100
+ k.instance(sheet, 'Course', { Course: course, Dishes: dishes }, { name: 'Course', width: 'FILL' })
101
+ if (i < MENU.length - 1) k.stack(sheet, { height: 9, name: 'Gap' })
102
+ }
103
+ k.stack(sheet, { height: 16, name: 'Gap' })
104
+ k.instance(sheet, 'Leaf rule', {}, { name: 'Divider' })
105
+ k.stack(sheet, { height: 10, name: 'Gap' })
106
+ k.text(sheet, 'For the hands that made it and the people round it, we give thanks.', { style: 'Thanks 12', color: 'onPaper', ...c, name: 'Thanks' })
107
+
108
+ return k.finish()
109
+ },
110
+ })
@@ -0,0 +1,80 @@
1
+ // Six folding place cards for the Thanksgiving table on one sheet: cut each card out along the solid line, fold it on
2
+ // the dashed line, and it stands up with the guest's name in a serif italic on the front and a few autumn leaves on
3
+ // the back. Each card is an instance of one component, so a guest's name is typed once and a colour change reaches all
4
+ // six. US Letter on card stock, with the warm ground running into the bleed and the cut and fold lines marked, and an
5
+ // A4 copy.
6
+
7
+ import { Kit, preset } from '../kit'
8
+ import { defineTemplate } from '../build'
9
+ import { SERIF } from '../shared/type'
10
+ import { acorn, draw, leaf, maple, mapleVeins, printVariant, seg, specksIn, type Drawn } from '../themes/holidays-shared'
11
+
12
+ const NAME = 'Thanksgiving place cards'
13
+ const CW = 336, CH = 288
14
+ const PAD = { top: 42, right: 72, bottom: 34, left: 72 }
15
+
16
+ /** The leaves on a card: a spray on its back half (upside down, as it will stand) and a sprig by the name. */
17
+ function cardArt(): Drawn[] {
18
+ const back = [maple(CW * 0.3, 70, 54, Math.PI + 0.3), maple(CW * 0.7, 76, 46, Math.PI - 0.5)].flat()
19
+ const veins = [...mapleVeins(CW * 0.3, 70, 54, Math.PI + 0.3), ...mapleVeins(CW * 0.7, 76, 46, Math.PI - 0.5)]
20
+ const oaks = [leaf({ x: CW * 0.5, y: 100 }, 50, 20, -Math.PI / 2 - 0.5), leaf({ x: CW * 0.5, y: 100 }, 46, 18, -Math.PI / 2 + 0.6), leaf({ x: CW * 0.5, y: 100 }, 40, 16, Math.PI / 2 + 0.3)]
21
+ const ac = acorn(CW * 0.52, 50, 26, Math.PI)
22
+ const front = [maple(CW / 2, 182, 30, 0.2)].flat()
23
+ return [
24
+ { name: 'Back oak', paths: oaks, color: 'mustard' },
25
+ { name: 'Back maple', paths: back, color: 'rust' },
26
+ { name: 'Back veins', paths: veins, color: 'rustDeep', o: { strokeWidth: 1 } },
27
+ { name: 'Back acorn', paths: ac.nut, color: 'acorn' },
28
+ { name: 'Back acorn cap', paths: ac.cap, color: 'bark' },
29
+ { name: 'Fold', paths: [seg({ x: 0, y: CH / 2 }, { x: CW, y: CH / 2 })], color: 'line', o: { stroke: { color: 'line', width: 1, dash: [6, 5] } } as never },
30
+ { name: 'Front leaf', paths: front, color: 'rust' },
31
+ ]
32
+ }
33
+
34
+ function ground(w: number, h: number): Drawn[] {
35
+ const B = 12
36
+ return [0, 1, 2].map(i => ({ name: 'Fibre', paths: specksIn({ x: -B, y: -B + ((h + 2 * B) * i) / 3, w: w + 2 * B, h: (h + 2 * B) / 3 }, 500, 80 + i, 1.8), color: 'fibre', o: { opacity: 0.4 } }))
37
+ }
38
+
39
+ export default defineTemplate({
40
+ id: 'print-holidays-thanksgiving-place-cards',
41
+ meta: {
42
+ name: NAME,
43
+ description: 'Six folding place cards for the Thanksgiving table on one sheet: cut along the solid line, fold on the dashed one, and each stands with the guest’s name in a serif italic on the front and autumn leaves on the back. One component, so a colour change reaches all six. US Letter on card with bleed, cut and fold lines marked, and an A4 copy',
44
+ category: 'menu', tags: ['thanksgiving', 'place cards', 'name cards', 'tent cards', 'table setting', 'dinner', 'friendsgiving', 'autumn', 'fall', 'leaves', 'printable', 'diy', 'print'],
45
+ platforms: ['print'], formats: ['card'], useCases: ['event'],
46
+ created: '2026-10-10', updated: '2026-10-10',
47
+ },
48
+ stableNumbers: true,
49
+ build() {
50
+ const k = new Kit(NAME, preset('us-letter'))
51
+ k.quantizeGeometry = true
52
+ k.brand(
53
+ { paper: '#EFE3CF', text: '#3B2416', onPaper: '#6E4A2E', primary: '#A8481F', onPrimary: '#FFF8EE', card: '#FFFAF1', onCard: '#3B2416', line: '#B79C7C' },
54
+ { name: 'Thanksgiving 2026' },
55
+ )
56
+ k.palette({ rust: '#B4512A', rustDeep: '#7E3216', mustard: '#D69A2D', acorn: '#A86F3D', bark: '#5E3F26', fibre: '#A88A64' })
57
+ k.textStyle('Head 26', { ...SERIF, size: 26, fontWeight: 600, fontStyle: 'italic', lineHeight: 30 })
58
+ k.textStyle('Note 11', { size: 11, fontWeight: 500, lineHeight: 16 })
59
+ k.textStyle('Guest 32', { ...SERIF, size: 32, fontWeight: 500, fontStyle: 'italic', letterSpacing: -0.4, lineHeight: 36 })
60
+ k.textStyle('Card note 8', { size: 8.5, fontWeight: 700, letterSpacing: 2.2, lineHeight: 12 })
61
+
62
+ k.component('Place card', { width: CW, height: CH, fill: 'card', stroke: { color: 'line', width: 1 }, padding: { top: 206, right: 20, bottom: 20, left: 20 }, gap: 4, align: 'CENTER', props: { Name: 'Guest', Note: 'GRATEFUL FOR YOU' } }, id => {
63
+ draw(k, id, cardArt())
64
+ k.propText(id, 'Name', { style: 'Guest 32', color: 'onCard', align: 'CENTER', width: 'FILL', name: 'Guest name' })
65
+ k.propText(id, 'Note', { style: 'Card note 8', color: 'onCard', align: 'CENTER', width: 'FILL', name: 'Card note' })
66
+ })
67
+
68
+ const sheet = k.sheet
69
+ const { width: W, height: H } = k.node(sheet)
70
+ k.flowSheet(sheet, 16, PAD, 'paper', 'MIN', 'CENTER')
71
+ draw(k, sheet, ground(W, H))
72
+ k.text(sheet, 'Place cards for the table', { style: 'Head 26', color: 'text', align: 'CENTER', width: 'FILL', name: 'Title' })
73
+ const grid = k.stack(sheet, { direction: 'HORIZONTAL', wrap: true, gap: 0, width: CW * 2, name: 'Cards' })
74
+ for (const [n, note] of [['Grandma Mai', 'GRATEFUL FOR YOU'], ['Uncle Thanh', 'GRATEFUL FOR YOU'], ['Linh', 'PIE CAPTAIN'], ['Marcus', 'GRATEFUL FOR YOU'], ['Auntie Rose', 'GRAVY BOSS'], ['Baby Kai', 'FIRST THANKSGIVING']]) k.instance(grid, 'Place card', { Name: n, Note: note }, { name: 'Place card' })
75
+ k.text(sheet, 'Cut along the solid lines and fold on the dashed line, leaves to the back, so each name stands up. Card stock of 65 lb (176 gsm) or heavier works best. {{brand.name}}', { style: 'Note 11', color: 'text', align: 'CENTER', width: 'FILL', name: 'Note' })
76
+
77
+ k.variants(sheet, [printVariant('a4', { art: ground, pad: PAD, masterWidth: W })])
78
+ return k.finish()
79
+ },
80
+ })
@@ -0,0 +1,95 @@
1
+ // A printed menu for a small restaurant's Valentine's supper: a tall card in blush and claret, two roses seen from above
2
+ // climbing in from the top corner on a trailing stem, the evening's five courses set in a light serif with small
3
+ // capital course names between fine hairlines, and the price and wine pairing at the foot under a little heart. DL
4
+ // (one third of A4) to stand on each table, with the roses running into the bleed.
5
+
6
+ import { Kit, preset } from '../kit'
7
+ import { defineTemplate } from '../build'
8
+ import { SERIF } from '../shared/type'
9
+ import { disc, draw, flourishComponent, heart, leaf, quad, rose, type Drawn } from '../themes/holidays-shared'
10
+
11
+ const NAME = 'Valentine’s dinner menu'
12
+ const PAD = { top: 214, right: 40, bottom: 40, left: 40 }
13
+
14
+ const COURSES = [
15
+ ['APERITIVO', 'Blood-orange spritz', 'or a glass of Franciacorta rosé'],
16
+ ['ANTIPASTO', 'Burrata, roasted beets, pistachio', 'with warm focaccia from our oven'],
17
+ ['PRIMO', 'Heart-shaped ravioli of ricotta and lemon', 'brown butter, sage, a little black pepper'],
18
+ ['SECONDO', 'Slow-cooked short rib, soft polenta', 'or wild mushroom and chestnut risotto'],
19
+ ['DOLCE', 'Dark chocolate torta for two', 'raspberries, vanilla mascarpone'],
20
+ ] as const
21
+
22
+ function art(w: number, h: number): Drawn[] {
23
+ const s = w / 374, B = 12 * s
24
+ const r1 = rose(w - 70 * s, 64 * s, 44 * s, 3), r2 = rose(w - 158 * s, 22 * s, 32 * s, 7), bud = rose(56 * s, 40 * s, 18 * s, 9)
25
+ const stem = [quad({ x: w + B, y: 150 * s }, { x: w - 130 * s, y: 128 * s }, { x: 40 * s, y: 50 * s }, 24)]
26
+ const leaves = [leaf({ x: w - 40 * s, y: 132 * s }, 40 * s, 16 * s, 2.7), leaf({ x: w - 110 * s, y: 118 * s }, 36 * s, 15 * s, 3.5), leaf({ x: w - 200 * s, y: 104 * s }, 34 * s, 14 * s, 2.6), leaf({ x: w - 250 * s, y: 92 * s }, 30 * s, 12 * s, 3.6), leaf({ x: 110 * s, y: 66 * s }, 28 * s, 11 * s, 2.7)]
27
+ const petal = (x: number, y: number, r: number, t: number) => heart(x, y, r * 2 * s, t)
28
+ return [
29
+ { name: 'Stem', paths: stem, color: 'leaf', o: { strokeWidth: 2.2 * s } },
30
+ { name: 'Leaves', paths: leaves, color: 'leaf' },
31
+ { name: 'Rose petals outer', paths: [...r1.outer, ...r2.outer, ...bud.outer], color: 'rose' },
32
+ { name: 'Rose petals inner', paths: [...r1.inner, ...r2.inner, ...bud.inner], color: 'roseDeep' },
33
+ { name: 'Rose edges', paths: [...r1.outer, ...r2.outer], color: 'roseDeep', o: { strokeWidth: 0.8 * s, opacity: 0.5 } },
34
+ { name: 'Petals', paths: [petal(16 * s, h - 40 * s, 7, 0.4), petal(30 * s, h - 18 * s, 5, 1.2)], color: 'rose', o: { opacity: 0.7 } },
35
+ { name: 'Petals', paths: [petal(w - 18 * s, h - 30 * s, 6, -0.6), petal(w - 34 * s, h - 12 * s, 4, 0.2)], color: 'rose', o: { opacity: 0.55 } },
36
+ { name: 'Dots', paths: [disc(26 * s, 150 * s, 2 * s), disc(40 * s, 166 * s, 1.4 * s)], color: 'primary' },
37
+ ]
38
+ }
39
+
40
+ export default defineTemplate({
41
+ id: 'print-holidays-valentines-dinner-menu',
42
+ meta: {
43
+ name: NAME,
44
+ description: 'A table menu for a small restaurant’s Valentine’s supper: roses seen from above climb in from the corner of a blush card, the five courses are set in a light serif with small-capital course names between hairlines, and the price and wine pairing sit at the foot. DL to stand on each table, with bleed',
45
+ category: 'menu', tags: ['valentine’s day', 'valentines', 'menu', 'restaurant', 'dinner', 'prix fixe', 'tasting menu', 'roses', 'romantic', 'italian', 'small business', 'print'],
46
+ platforms: ['print'], formats: ['card'], useCases: ['menu', 'event'],
47
+ created: '2026-10-10', updated: '2026-10-10',
48
+ },
49
+ stableNumbers: true,
50
+ build() {
51
+ const k = new Kit(NAME, preset('dl-flyer'))
52
+ k.quantizeGeometry = true
53
+ k.brand(
54
+ { paper: '#F8E9E4', text: '#4A1722', onPaper: '#7B3A44', primary: '#9E2B3E', onPrimary: '#FFF4F1', line: '#E3BFC0' },
55
+ { name: 'Osteria Lina' },
56
+ )
57
+ k.palette({ rose: '#D9566E', roseDeep: '#A12E46', leaf: '#5F7A5A' })
58
+ k.textStyle('Kicker 9', { size: 9, fontWeight: 700, letterSpacing: 2.6, lineHeight: 13 })
59
+ k.textStyle('Title 34', { ...SERIF, size: 34, fontWeight: 300, fontStyle: 'italic', letterSpacing: -0.6, lineHeight: 36 })
60
+ k.textStyle('Date 11', { ...SERIF, size: 11, fontWeight: 400, lineHeight: 15 })
61
+ k.textStyle('Course 8', { size: 8, fontWeight: 700, letterSpacing: 2.4, lineHeight: 11 })
62
+ k.textStyle('Dish 14', { ...SERIF, size: 14, fontWeight: 500, lineHeight: 18 })
63
+ k.textStyle('Note 10', { ...SERIF, size: 10, fontWeight: 400, fontStyle: 'italic', lineHeight: 14 })
64
+ k.textStyle('Price 11', { size: 11, fontWeight: 600, letterSpacing: 0.4, lineHeight: 16 })
65
+
66
+ k.component('Course', { gap: 3, width: 294, align: 'CENTER', props: { Course: 'COURSE', Dish: 'Dish', Note: 'How it comes' } }, id => {
67
+ k.propText(id, 'Course', { style: 'Course 8', color: 'onPaper', align: 'CENTER', width: 'FILL', name: 'Course name' })
68
+ k.propText(id, 'Dish', { style: 'Dish 14', color: 'text', align: 'CENTER', width: 'FILL', name: 'Dish' })
69
+ k.propText(id, 'Note', { style: 'Note 10', color: 'onPaper', align: 'CENTER', width: 'FILL', name: 'Note' })
70
+ })
71
+ flourishComponent(k, 'Heart rule', 'primary', heart(0, 0, 10), { rule: 30, size: 8 })
72
+
73
+ const sheet = k.sheet
74
+ const { width: W, height: H } = k.node(sheet)
75
+ k.flowSheet(sheet, 0, PAD, 'paper', 'MIN', 'CENTER')
76
+ draw(k, sheet, art(W, H))
77
+ const c = { align: 'CENTER' as const, width: 'FILL' as const }
78
+ k.text(sheet, 'SATURDAY 13 & SUNDAY 14 FEBRUARY', { style: 'Kicker 9', color: 'onPaper', ...c, name: 'Kicker' })
79
+ k.stack(sheet, { height: 8, name: 'Gap' })
80
+ k.text(sheet, 'A supper for two', { style: 'Title 34', color: 'text', ...c, name: 'Title' })
81
+ k.text(sheet, 'Valentine’s at {{brand.name}}', { style: 'Date 11', color: 'onPaper', ...c, name: 'Sub' })
82
+ k.stack(sheet, { height: 18, name: 'Gap' })
83
+ for (const [i, [course, dish, note]] of COURSES.entries()) {
84
+ k.instance(sheet, 'Course', { Course: course, Dish: dish, Note: note }, { name: 'Course', width: 'FILL' })
85
+ if (i < COURSES.length - 1) { k.stack(sheet, { height: 10, name: 'Gap' }); k.rect(sheet, 40, 1, 'line', { name: 'Hairline' }); k.stack(sheet, { height: 10, name: 'Gap' }) }
86
+ }
87
+ k.stack(sheet, { height: 20, name: 'Gap' })
88
+ k.instance(sheet, 'Heart rule', {}, { name: 'Divider' })
89
+ k.stack(sheet, { height: 10, name: 'Gap' })
90
+ k.text(sheet, '$85 per person · wine pairing $40', { style: 'Price 11', color: 'text', ...c, name: 'Price' })
91
+ k.text(sheet, 'Please tell us about any allergies. Sittings at 6 and 8:30.', { style: 'Note 10', color: 'onPaper', ...c, name: 'Allergies' })
92
+
93
+ return k.finish()
94
+ },
95
+ })
@@ -0,0 +1,121 @@
1
+ // A sweet shop's Diwali mithai-box post: an open gift box seen from above, its compartments full of ladoos, diamonds
2
+ // of kaju katli with silver leaf and coils of jalebi, sits in the corner of a deep emerald ground under a border of
3
+ // scalloped arches and hanging diyas; "Diwali mithai boxes" is set big in a gold serif, the three boxes and their
4
+ // prices follow, and the order-by date. 4:5 for the feed and 9:16 for stories, the drawing worked out again for each.
5
+
6
+ import { Kit, preset, type VariantSpec } from '../kit'
7
+ import { defineTemplate } from '../build'
8
+ import { SERIF } from '../shared/type'
9
+ import type { VectorPath } from '@/lib/document/types'
10
+ import { verticalPadding } from './shared'
11
+ import { arc, disc, diya, draw, polyP, rangoli, rectPath, redraw, restyleAll, roundRect, scaleGaps, seg, type Drawn } from '../themes/holidays-shared'
12
+
13
+ const NAME = 'Diwali sweet shop mithai post'
14
+
15
+ /** A jalebi: a coil of `turns` round (cx, cy). */
16
+ const jalebi = (cx: number, cy: number, r: number): VectorPath => polyP(Array.from({ length: 40 }, (_, i) => { const t = i / 39, a = t * Math.PI * 5, rr = r * (0.15 + t * 0.85); return { x: cx + Math.cos(a) * rr, y: cy + Math.sin(a) * rr } }), false)
17
+
18
+ function art(w: number, h: number, safe = { top: 0, bottom: 0 }): Drawn[] {
19
+ const s = w / 1080
20
+ // The box: a lid edge, a 3 × 3 tray of compartments.
21
+ const bx = w - 470 * s, by = h - 470 * s - safe.bottom * 0.5, cell = 128 * s, gap = 10 * s
22
+ const tray = roundRect(bx - 22 * s, by - 22 * s, 3 * cell + 2 * gap + 44 * s, 3 * cell + 2 * gap + 44 * s, 18 * s)
23
+ const cells: VectorPath[] = [], ladoo: VectorPath[] = [], ladooDots: VectorPath[] = [], katli: VectorPath[] = [], silver: VectorPath[] = [], jal: VectorPath[] = []
24
+ for (let r = 0; r < 3; r++) for (let c = 0; c < 3; c++) {
25
+ const x = bx + c * (cell + gap), y = by + r * (cell + gap), mx = x + cell / 2, my = y + cell / 2
26
+ cells.push(roundRect(x, y, cell, cell, 10 * s))
27
+ const kind = (r + c) % 3
28
+ if (kind === 0) for (const [dx, dy] of [[-0.2, -0.2], [0.2, -0.2], [-0.2, 0.2], [0.2, 0.2]]) { ladoo.push(disc(mx + dx * cell, my + dy * cell, cell * 0.19)); ladooDots.push(disc(mx + dx * cell - 6 * s, my + dy * cell - 6 * s, 2.4 * s), disc(mx + dx * cell + 5 * s, my + dy * cell + 2 * s, 2 * s)) }
29
+ if (kind === 1) for (const [dx, dy] of [[-0.18, 0], [0.18, 0], [0, -0.26], [0, 0.26]]) { const q = (u: number, v: number) => ({ x: mx + dx * cell + u, y: my + dy * cell + v }), d = cell * 0.17; katli.push(polyP([q(0, -d), q(d * 1.2, 0), q(0, d), q(-d * 1.2, 0)])); silver.push(polyP([q(0, -d * 0.6), q(d * 0.7, 0), q(0, d * 0.6), q(-d * 0.7, 0)])) }
30
+ if (kind === 2) jal.push(jalebi(mx - cell * 0.18, my - cell * 0.12, cell * 0.2), jalebi(mx + cell * 0.18, my + cell * 0.14, cell * 0.2))
31
+ }
32
+ // Scalloped arches along the top, with a diya hanging in each.
33
+ const arches: VectorPath[] = [], chains: VectorPath[] = []
34
+ const n = 6, aw = w / n, top = safe.top * 0.6
35
+ for (let i = 0; i < n; i++) { const x = i * aw; arches.push(arc(x + aw / 2, top + 40 * s, aw / 2, Math.PI, 0, 18, 46 * s)); chains.push(seg({ x: x + aw / 2, y: top }, { x: x + aw / 2, y: top + 92 * s })) }
36
+ const lamps = Array.from({ length: n }, (_, i) => diya(i * aw + aw / 2 - 6 * s, top + 128 * s, 50 * s))
37
+ const rg = rangoli(w + 30 * s, top + 470 * s, 330 * s, 8)
38
+ return [
39
+ { name: 'Rangoli outer', paths: rg.outer, color: 'pink' },
40
+ { name: 'Rangoli middle', paths: rg.middle, color: 'saffron' },
41
+ { name: 'Rangoli inner', paths: rg.inner, color: 'gold' },
42
+ { name: 'Rangoli dots', paths: rg.dots, color: 'paperCup' },
43
+ { name: 'Rangoli heart', paths: rg.heart, color: 'clay' },
44
+ { name: 'Arches', paths: arches, color: 'gold', o: { strokeWidth: 4 * s } },
45
+ { name: 'Chains', paths: chains, color: 'gold', o: { strokeWidth: 2 * s } },
46
+ { name: 'Hanging diyas', paths: lamps.flatMap(d => d.bowl), color: 'clay' },
47
+ { name: 'Hanging diya rims', paths: lamps.flatMap(d => d.rim), color: 'gold' },
48
+ { name: 'Hanging flames', paths: lamps.flatMap(d => d.flame), color: 'saffron' },
49
+ { name: 'Box shadow', paths: [roundRect(bx - 4 * s, by + 4 * s, 3 * cell + 2 * gap + 44 * s, 3 * cell + 2 * gap + 44 * s, 18 * s)], color: 'shadow', o: { opacity: 0.35 } },
50
+ { name: 'Box', paths: [tray], color: 'pink' },
51
+ { name: 'Box trim', paths: [rectPath(bx - 22 * s, by + 3 * cell + 2 * gap + 10 * s, 3 * cell + 2 * gap + 44 * s, 8 * s)], color: 'gold' },
52
+ { name: 'Compartments', paths: cells, color: 'paperCup' },
53
+ { name: 'Ladoos', paths: ladoo, color: 'saffron' },
54
+ { name: 'Ladoo specks', paths: ladooDots, color: 'clay' },
55
+ { name: 'Kaju katli', paths: katli, color: 'katli' },
56
+ { name: 'Silver leaf', paths: silver, color: 'silver' },
57
+ { name: 'Jalebi', paths: jal, color: 'jalebi', o: { strokeWidth: 9 * s } },
58
+ ]
59
+ }
60
+
61
+ export default defineTemplate({
62
+ id: 'social-holidays-diwali-sweet-shop-post',
63
+ meta: {
64
+ name: NAME,
65
+ description: 'A sweet shop’s Diwali mithai-box post: an open box from above full of ladoos, silver-leafed kaju katli and coils of jalebi in the corner of a deep emerald ground, scalloped arches with hanging diyas along the top, “Diwali mithai boxes” in a gold serif, three boxes with prices and the order-by date. 4:5 for the feed and 9:16 for stories',
66
+ category: 'social', tags: ['diwali', 'deepavali', 'mithai', 'sweets', 'sweet shop', 'ladoo', 'kaju katli', 'jalebi', 'gift box', 'pre-order', 'small business', 'indian', 'instagram', 'story', 'post'],
67
+ platforms: ['instagram', 'facebook'], formats: ['portrait-post', 'story'], useCases: ['sale', 'announcement'],
68
+ created: '2026-10-10', updated: '2026-10-10',
69
+ },
70
+ stableNumbers: true,
71
+ build() {
72
+ const k = new Kit(NAME, preset('ig-portrait'))
73
+ k.quantizeGeometry = true
74
+ k.brand(
75
+ { paper: '#0E4B3B', text: '#FFF4DE', onPaper: '#F2C14E', primary: '#F2C14E', onPrimary: '#0E4B3B', line: '#2C6B58' },
76
+ { name: 'Sweet Saffron', site: 'sweetsaffron.nyc' },
77
+ )
78
+ k.palette({ gold: '#F2C14E', saffron: '#FFA41B', clay: '#B9572B', pink: '#D63D78', paperCup: '#FFF4DE', katli: '#EFE1C2', silver: '#C9CED6', jalebi: '#F28A1A', shadow: '#05261D' })
79
+ k.textStyle('Kicker 26', { size: 26, fontWeight: 700, letterSpacing: 5, lineHeight: 34 })
80
+ k.textStyle('Title 98', { ...SERIF, size: 98, fontWeight: 700, fontStyle: 'italic', letterSpacing: -2.4, lineHeight: 96 })
81
+ k.textStyle('Box 31', { ...SERIF, size: 31, fontWeight: 600, lineHeight: 38 })
82
+ k.textStyle('Price 31', { size: 31, fontWeight: 800, lineHeight: 38, fontFeatures: { tnum: 1 } })
83
+ k.textStyle('Body 26', { size: 26, fontWeight: 500, lineHeight: 35 })
84
+
85
+ k.component('Mithai box', { gap: 2, width: 520, padding: { top: 10, right: 0, bottom: 10, left: 0 }, props: { Box: 'Box', Price: '$0' } }, id => {
86
+ k.propText(id, 'Box', { style: 'Box 31', color: 'text', width: 'FILL', name: 'Box name' })
87
+ k.propText(id, 'Price', { style: 'Price 31', color: 'onPaper', width: 'FILL', name: 'Box price' })
88
+ })
89
+
90
+ const sheet = k.sheet
91
+ const { width: W, height: H } = k.node(sheet)
92
+ k.flowSheet(sheet, 0, { top: 236, right: 476, bottom: 80, left: 84 }, 'paper', 'MIN', 'MIN')
93
+ draw(k, sheet, art(W, H))
94
+ k.text(sheet, 'HAPPY DIWALI', { style: 'Kicker 26', color: 'text', width: 'FILL', name: 'Kicker' })
95
+ k.stack(sheet, { height: 14, name: 'Gap' })
96
+ k.text(sheet, 'Diwali mithai boxes', { style: 'Title 98', color: 'onPaper', width: 'FILL', name: 'Title' })
97
+ k.stack(sheet, { height: 22, name: 'Gap' })
98
+ const list = k.stack(sheet, { width: 520, name: 'Boxes' })
99
+ for (const [i, [n, p]] of [['Motichoor ladoo, 12', '$18'], ['Kaju katli, 500 g', '$22'], ['Mixed mithai, 1 kg', '$34']].entries()) {
100
+ k.instance(list, 'Mithai box', { Box: n, Price: p }, { name: 'Mithai box', width: 'FILL' })
101
+ if (i < 2) k.rect(list, 'FILL', 2, 'line', { name: 'Rule' })
102
+ }
103
+ k.stack(sheet, { height: 22, name: 'Gap' })
104
+ k.text(sheet, 'Order by Thursday 5 November. Pick up Friday to Sunday at {{brand.name}}, 74th Street, or {{brand.site}}', { style: 'Body 26', color: 'text', width: 'FILL', name: 'Body' })
105
+
106
+ k.textStyle('Title 110', { ...SERIF, size: 110, fontWeight: 700, fontStyle: 'italic', letterSpacing: -2.8, lineHeight: 106 })
107
+ const story: VariantSpec = {
108
+ preset: 'ig-story',
109
+ adjust: (kit, id) => {
110
+ const { width: w, height: h } = kit.node(id)
111
+ const safe = verticalPadding(84)
112
+ redraw(kit, id, art(w, h, { top: safe.top - 84, bottom: safe.bottom - 84 }))
113
+ kit.patch(id, { padding: { top: safe.top + 160, right: w - safe.left - 520, bottom: safe.bottom, left: safe.left } })
114
+ restyleAll(kit, id, [['Title', 'Title 110']])
115
+ scaleGaps(kit, id, 1.3)
116
+ },
117
+ }
118
+ k.variants(sheet, [story])
119
+ return k.finish()
120
+ },
121
+ })