@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.
- package/dist/chunks/{agent-45ZGYKTC.js → agent-NTEAEZBN.js} +1 -1
- package/dist/chunks/{build-K6QBTLT7.js → build-W5O2ZVN7.js} +2 -2
- package/dist/chunks/{builtin-ZS3HJHZL.js → builtin-KX2SGDND.js} +3 -3
- package/dist/chunks/{check-template-RZ7IO333.js → check-template-B2KKE6BW.js} +2 -2
- package/dist/chunks/{chunk-ZKHYC3QO.js → chunk-37WES5PM.js} +2 -2
- package/dist/chunks/chunk-45JABNLR.js +161 -0
- package/dist/chunks/chunk-4OGILN7Y.js +141 -0
- package/dist/chunks/{chunk-63X37RCC.js → chunk-5NWVUAWR.js} +4 -4
- package/dist/chunks/{chunk-QREG6HEK.js → chunk-67SLLJJB.js} +40 -40
- package/dist/chunks/chunk-6DV5JZ5M.js +121 -0
- package/dist/chunks/chunk-7Q67MXXL.js +140 -0
- package/dist/chunks/{chunk-R5CW4CNP.js → chunk-A6GPFR5O.js} +11 -11
- package/dist/chunks/chunk-AISWLDDO.js +895 -0
- package/dist/chunks/{chunk-ADZXK6SM.js → chunk-AQGECTRJ.js} +1 -1
- package/dist/chunks/chunk-ASWWZNTY.js +130 -0
- package/dist/chunks/chunk-BNEFANT2.js +134 -0
- package/dist/chunks/{chunk-REJO7GXO.js → chunk-CFEKYNGQ.js} +2 -2
- package/dist/chunks/{chunk-5HG4LWF5.js → chunk-D4HEDPVN.js} +161 -0
- package/dist/chunks/chunk-DRESZDOP.js +130 -0
- package/dist/chunks/{chunk-3W2MSVJA.js → chunk-EBLHS2EG.js} +5 -5
- package/dist/chunks/chunk-EHTLT6XJ.js +131 -0
- package/dist/chunks/{chunk-2AQK4IIV.js → chunk-EVFG2K4L.js} +1 -1
- package/dist/chunks/{chunk-6TJIEERG.js → chunk-F4NTIOTT.js} +26 -26
- package/dist/chunks/chunk-FC7B22QS.js +150 -0
- package/dist/chunks/{chunk-V6COW3DB.js → chunk-G6MIC23V.js} +1 -1
- package/dist/chunks/{chunk-4QBQ4RAA.js → chunk-GODXMHO3.js} +4 -4
- package/dist/chunks/chunk-GT22XGDH.js +152 -0
- package/dist/chunks/chunk-HL4M53ZR.js +143 -0
- package/dist/chunks/{chunk-MPI6SCA7.js → chunk-IDL5KKIC.js} +5 -5
- package/dist/chunks/{chunk-HR7HCFPX.js → chunk-IM6HORNC.js} +5 -5
- package/dist/chunks/{chunk-UVMU6CXR.js → chunk-IWCZDFXL.js} +17 -17
- package/dist/chunks/{chunk-OCSLBM6W.js → chunk-JXAJ6OST.js} +1 -1
- package/dist/chunks/{chunk-5WGMPGKY.js → chunk-KS7DVWDB.js} +1 -1
- package/dist/chunks/chunk-NHDBQ4G7.js +137 -0
- package/dist/chunks/chunk-NKPU5TF2.js +103 -0
- package/dist/chunks/chunk-NO734F3F.js +146 -0
- package/dist/chunks/{chunk-ASWAIYNC.js → chunk-NR2SFSJU.js} +3 -3
- package/dist/chunks/{chunk-CJY4TNOS.js → chunk-O3BEBIDN.js} +3 -1
- package/dist/chunks/{chunk-UDO3ZU5T.js → chunk-OR6HMJRM.js} +1 -1
- package/dist/chunks/chunk-OTICJQ4T.js +267 -0
- package/dist/chunks/chunk-PWZZRGWI.js +141 -0
- package/dist/chunks/{chunk-AE6S2M2B.js → chunk-QX4U3VTU.js} +25 -25
- package/dist/chunks/{chunk-TRUFIU5W.js → chunk-R3YD6VL6.js} +3 -3
- package/dist/chunks/{chunk-YSYZW4VZ.js → chunk-SFPM3UJA.js} +7 -7
- package/dist/chunks/chunk-SNXQM4UO.js +115 -0
- package/dist/chunks/chunk-T3LV66CQ.js +139 -0
- package/dist/chunks/chunk-TDGIO2S7.js +145 -0
- package/dist/chunks/chunk-TFNV2UP4.js +87 -0
- package/dist/chunks/chunk-UJZXKOTP.js +136 -0
- package/dist/chunks/{chunk-QAVWMRRM.js → chunk-V5HI55XO.js} +2 -2
- package/dist/chunks/chunk-VVNPBBPL.js +174 -0
- package/dist/chunks/{chunk-22RNC42G.js → chunk-YHA45NK5.js} +1 -1
- package/dist/chunks/{chunk-27EVM272.js → chunk-YPMT4Y4N.js} +2 -2
- package/dist/chunks/chunk-YRSDFRCX.js +122 -0
- package/dist/chunks/chunk-ZNARVSMD.js +140 -0
- package/dist/chunks/{chunk-3DWOQ4LW.js → chunk-ZTBKAGBN.js} +2 -2
- package/dist/chunks/{client-6DUSRTZG.js → client-25NJXQWL.js} +1 -1
- package/dist/chunks/{client-XYPBNSQ7.js → client-7AQTKZHV.js} +2 -2
- package/dist/chunks/{client-TRFNJJHQ.js → client-LWBTUXQK.js} +2 -2
- package/dist/chunks/{client-actions-SSUZUEG7.js → client-actions-3HIRRSOL.js} +4 -4
- package/dist/chunks/{dangerous-UX4DHJ7U.js → dangerous-XJOHEWL6.js} +3 -3
- package/dist/chunks/{editor-seams-V7ZP7XH3.js → editor-seams-4DREZRTT.js} +9 -9
- package/dist/chunks/{editor-store-OIHZT244.js → editor-store-C4VWC23J.js} +1 -1
- package/dist/chunks/{export-watermark-4NAP5RVM.js → export-watermark-OKGRZNOU.js} +5 -5
- package/dist/chunks/{eyedropper-QXEY7LVL.js → eyedropper-WZYSTUTM.js} +1 -1
- package/dist/chunks/{generate-media-JAEHWNF6.js → generate-media-DFSHOPOI.js} +2 -2
- package/dist/chunks/{host-IKFYVZS7.js → host-4ZE7SIWR.js} +6 -6
- package/dist/chunks/{integrations-U5X2BHVK.js → integrations-LPC35FKX.js} +1 -1
- package/dist/chunks/{kit-modules-BAUMECYQ.js → kit-modules-BAKTDLOZ.js} +1 -1
- package/dist/chunks/{libraries-Q5TCHWGI.js → libraries-ACERXG2O.js} +5 -5
- package/dist/chunks/{live-4XQEGNLA.js → live-ZIVBFX3G.js} +3 -3
- package/dist/chunks/{media-tool-OSQR45HY.js → media-tool-2ZPIQKJD.js} +1 -1
- package/dist/chunks/{new-file-VVLWHEJ4.js → new-file-XS3KCMGM.js} +3 -3
- package/dist/chunks/{plugin-44ZW5UEP.js → plugin-6RNMUP5I.js} +2 -2
- package/dist/chunks/{plugin-import-3ISLDX7T.js → plugin-import-UC5BHZFE.js} +13 -13
- package/dist/chunks/{presence-WVU4OIUA.js → presence-TLDOMBPD.js} +2 -2
- package/dist/chunks/{project-EG7XFEJU.js → project-NMPK4R5M.js} +1 -1
- package/dist/chunks/{publish-site-WFIX42D3.js → publish-site-R46ZILSS.js} +6 -6
- package/dist/chunks/{publish-store-6BS7OIP4.js → publish-store-MIPHEDLV.js} +4 -4
- package/dist/chunks/{raster-actions-37XUQSVZ.js → raster-actions-7EI5THLB.js} +1 -1
- package/dist/chunks/{runner-HZ7MU6ER.js → runner-I66TTKKZ.js} +16 -16
- package/dist/chunks/{store-PHWBXKNY.js → store-7WMR3JKP.js} +5 -5
- package/dist/chunks/{sync-IJNKHQRF.js → sync-OFINS45Q.js} +5 -5
- package/dist/chunks/{tabs-G6OTEGA5.js → tabs-PEH6VX3X.js} +8 -8
- package/dist/chunks/{team-M2EN4JT5.js → team-GGW3O32G.js} +1 -1
- package/dist/chunks/{tools-WDFCY4BH.js → tools-TKCA46ZS.js} +2 -2
- package/dist/chunks/{ui-commands-QHFGR33P.js → ui-commands-6K4PYKU5.js} +11 -11
- package/dist/chunks/{versions-S7COCZSQ.js → versions-KHRNQFGF.js} +5 -5
- package/dist/index.js +4 -4
- package/dist/seed/files.json +1 -1
- package/dist/templates/count.js +1 -1
- package/dist/templates/index.js +125 -101
- package/dist/templates/index.json +756 -0
- package/dist/templates/motion/landing-hero/index.js +4 -4
- package/dist/templates/motion/launch-trailer/document.js +125 -101
- package/dist/templates/motion/launch-trailer/index.js +4 -4
- package/dist/templates/motion/launch-trailer/scenes.js +3 -3
- package/dist/templates/motion/template-wall.js +2 -2
- package/dist/templates/print/holidays-christmas-church-newsletter.js +94 -0
- package/dist/templates/print/holidays-christmas-eve-invitation.js +94 -0
- package/dist/templates/print/holidays-christmas-gift-tags.js +94 -0
- package/dist/templates/print/holidays-diwali-party-invitation.js +95 -0
- package/dist/templates/print/holidays-easter-activity-sheet.js +94 -0
- package/dist/templates/print/holidays-easter-egg-hunt-flyer.js +94 -0
- package/dist/templates/print/holidays-eid-dinner-invitation.js +95 -0
- package/dist/templates/print/holidays-eid-gift-tags.js +94 -0
- package/dist/templates/print/holidays-halloween-party-invitation.js +95 -0
- package/dist/templates/print/holidays-halloween-treat-labels.js +94 -0
- package/dist/templates/print/holidays-halloween-trunk-or-treat-poster.js +94 -0
- package/dist/templates/print/holidays-hanukkah-activity-sheet.js +94 -0
- package/dist/templates/print/holidays-hanukkah-party-invitation.js +94 -0
- package/dist/templates/print/holidays-lunar-new-year-banquet-menu.js +95 -0
- package/dist/templates/print/holidays-new-years-eve-poster.js +94 -0
- package/dist/templates/print/holidays-thanksgiving-dinner-menu.js +94 -0
- package/dist/templates/print/holidays-thanksgiving-place-cards.js +94 -0
- package/dist/templates/print/holidays-valentines-dinner-menu.js +94 -0
- package/dist/templates/saas/email-decks.js +2 -2
- package/dist/templates/saas/index.js +37 -37
- package/dist/templates/saas/launch-kit.js +2 -2
- package/dist/templates/saas/launch-video.js +2 -2
- package/dist/templates/saas/stores.js +2 -2
- package/dist/templates/saas/style-bento.js +2 -2
- package/dist/templates/saas/style-dark.js +2 -2
- package/dist/templates/saas/style-editorial.js +2 -2
- package/dist/templates/saas/style-gradient.js +2 -2
- package/dist/templates/saas/style-pastel.js +2 -2
- package/dist/templates/saas/style-swiss.js +2 -2
- package/dist/templates/slideshow/index.js +3 -3
- package/dist/templates/social/holidays-diwali-sweet-shop-post.js +96 -0
- package/dist/templates/social/holidays-lunar-new-year-bakery-post.js +96 -0
- package/dist/templates/social/holidays-new-years-eve-shop-hours.js +96 -0
- package/dist/templates/social/holidays-thanksgiving-pie-orders-post.js +96 -0
- package/dist/templates/social/holidays-valentines-florist-post.js +96 -0
- package/dist/templates/themes/black-friday-coupons.js +2 -2
- package/dist/templates/themes/black-friday-neon.js +2 -2
- package/dist/templates/themes/blueprint.js +2 -2
- package/dist/templates/themes/broadsheet.js +2 -2
- package/dist/templates/themes/cyberpunk.js +2 -2
- package/dist/templates/themes/departure-board.js +2 -2
- package/dist/templates/themes/festival-presale-family-a.js +2 -2
- package/dist/templates/themes/festival-presale-family-b.js +2 -2
- package/dist/templates/themes/holidays-shared.js +172 -0
- package/dist/templates/themes/holidays.js +107 -1
- package/dist/templates/themes/index.js +72 -48
- package/dist/templates/themes/lunar-new-year.js +2 -2
- package/dist/templates/themes/noir.js +2 -2
- package/dist/templates/themes/psychedelic.js +2 -2
- package/dist/templates/themes/space-opera.js +2 -2
- package/dist/templates/themes/studio.js +3 -3
- package/dist/templates/themes/tour-tickets.js +2 -2
- package/dist/templates/themes/vaporwave.js +2 -2
- package/package.json +1 -1
- package/src/templates/print/holidays-christmas-church-newsletter.ts +246 -0
- package/src/templates/print/holidays-christmas-eve-invitation.ts +121 -0
- package/src/templates/print/holidays-christmas-gift-tags.ts +117 -0
- package/src/templates/print/holidays-diwali-party-invitation.ts +98 -0
- package/src/templates/print/holidays-easter-activity-sheet.ts +108 -0
- package/src/templates/print/holidays-easter-egg-hunt-flyer.ts +118 -0
- package/src/templates/print/holidays-eid-dinner-invitation.ts +104 -0
- package/src/templates/print/holidays-eid-gift-tags.ts +94 -0
- package/src/templates/print/holidays-halloween-party-invitation.ts +101 -0
- package/src/templates/print/holidays-halloween-treat-labels.ts +86 -0
- package/src/templates/print/holidays-halloween-trunk-or-treat-poster.ts +148 -0
- package/src/templates/print/holidays-hanukkah-activity-sheet.ts +112 -0
- package/src/templates/print/holidays-hanukkah-party-invitation.ts +112 -0
- package/src/templates/print/holidays-lunar-new-year-banquet-menu.ts +110 -0
- package/src/templates/print/holidays-new-years-eve-poster.ts +117 -0
- package/src/templates/print/holidays-thanksgiving-dinner-menu.ts +110 -0
- package/src/templates/print/holidays-thanksgiving-place-cards.ts +80 -0
- package/src/templates/print/holidays-valentines-dinner-menu.ts +95 -0
- package/src/templates/social/holidays-diwali-sweet-shop-post.ts +121 -0
- package/src/templates/social/holidays-lunar-new-year-bakery-post.ts +110 -0
- package/src/templates/social/holidays-new-years-eve-shop-hours.ts +118 -0
- package/src/templates/social/holidays-thanksgiving-pie-orders-post.ts +115 -0
- package/src/templates/social/holidays-valentines-florist-post.ts +102 -0
- package/src/templates/themes/holidays-shared.ts +903 -0
- package/src/templates/themes/holidays.ts +24 -1
- package/types/templates/src/print/holidays-christmas-church-newsletter.d.ts +2 -0
- package/types/templates/src/print/holidays-christmas-eve-invitation.d.ts +2 -0
- package/types/templates/src/print/holidays-christmas-gift-tags.d.ts +2 -0
- package/types/templates/src/print/holidays-diwali-party-invitation.d.ts +2 -0
- package/types/templates/src/print/holidays-easter-activity-sheet.d.ts +2 -0
- package/types/templates/src/print/holidays-easter-egg-hunt-flyer.d.ts +2 -0
- package/types/templates/src/print/holidays-eid-dinner-invitation.d.ts +2 -0
- package/types/templates/src/print/holidays-eid-gift-tags.d.ts +2 -0
- package/types/templates/src/print/holidays-halloween-party-invitation.d.ts +2 -0
- package/types/templates/src/print/holidays-halloween-treat-labels.d.ts +2 -0
- package/types/templates/src/print/holidays-halloween-trunk-or-treat-poster.d.ts +2 -0
- package/types/templates/src/print/holidays-hanukkah-activity-sheet.d.ts +2 -0
- package/types/templates/src/print/holidays-hanukkah-party-invitation.d.ts +2 -0
- package/types/templates/src/print/holidays-lunar-new-year-banquet-menu.d.ts +2 -0
- package/types/templates/src/print/holidays-new-years-eve-poster.d.ts +2 -0
- package/types/templates/src/print/holidays-thanksgiving-dinner-menu.d.ts +2 -0
- package/types/templates/src/print/holidays-thanksgiving-place-cards.d.ts +2 -0
- package/types/templates/src/print/holidays-valentines-dinner-menu.d.ts +2 -0
- package/types/templates/src/social/holidays-diwali-sweet-shop-post.d.ts +2 -0
- package/types/templates/src/social/holidays-lunar-new-year-bakery-post.d.ts +2 -0
- package/types/templates/src/social/holidays-new-years-eve-shop-hours.d.ts +2 -0
- package/types/templates/src/social/holidays-thanksgiving-pie-orders-post.d.ts +2 -0
- package/types/templates/src/social/holidays-valentines-florist-post.d.ts +2 -0
- package/types/templates/src/themes/holidays-shared.d.ts +374 -0
- package/dist/chunks/chunk-OWZLREWX.js +0 -18
- package/dist/chunks/{chunk-IFJTWD4O.js → chunk-2R3MZKA2.js} +3 -3
- package/dist/chunks/{chunk-P67L32AF.js → chunk-3DBNMCGN.js} +3 -3
- package/dist/chunks/{chunk-DZDTVMUO.js → chunk-3VHOXVMF.js} +3 -3
- package/dist/chunks/{chunk-2PQI2R5L.js → chunk-64RMGOTG.js} +0 -0
- package/dist/chunks/{chunk-7BDCMMSD.js → chunk-6Y46SDFE.js} +3 -3
- package/dist/chunks/{chunk-6VCQ4ZEG.js → chunk-AFARXTNQ.js} +3 -3
- package/dist/chunks/{chunk-ITSYENCT.js → chunk-BI3M4RUH.js} +3 -3
- package/dist/chunks/{chunk-25FM5JIR.js → chunk-BP7CHO7K.js} +3 -3
- package/dist/chunks/{chunk-D22534DX.js → chunk-DPTFZDOS.js} +3 -3
- package/dist/chunks/{chunk-JWLR6K62.js → chunk-FNUM5W3W.js} +3 -3
- package/dist/chunks/{chunk-JPH6HOYU.js → chunk-HJQU5POV.js} +3 -3
- package/dist/chunks/{chunk-HQYZ4URA.js → chunk-IJ65QZLL.js} +3 -3
- package/dist/chunks/{chunk-47VVF5TM.js → chunk-INT2OPPY.js} +8 -8
- package/dist/chunks/{chunk-AYVAHMWQ.js → chunk-JPMLUHZX.js} +3 -3
- package/dist/chunks/{chunk-LFYNAAAQ.js → chunk-LMRTRVPC.js} +3 -3
- package/dist/chunks/{chunk-HJIMF4NA.js → chunk-NGMCQZA6.js} +3 -3
- package/dist/chunks/{chunk-B3ASV6KS.js → chunk-PQBYL7AI.js} +3 -3
- package/dist/chunks/{chunk-NHXNUDC5.js → chunk-QD4QORNR.js} +3 -3
- package/dist/chunks/{chunk-P6TPDLAF.js → chunk-S3C6L5KF.js} +3 -3
- package/dist/chunks/{chunk-TIRPXUK2.js → chunk-S7L2D7NH.js} +3 -3
- package/dist/chunks/{chunk-QFGFR5FQ.js → chunk-TYOOL27R.js} +3 -3
- package/dist/chunks/{chunk-32V4D45K.js → chunk-TYW7EXV6.js} +3 -3
- package/dist/chunks/{chunk-3QIDCP4M.js → chunk-VOEAQIQT.js} +3 -3
- package/dist/chunks/{chunk-ASVDOX62.js → chunk-VVUQG4ME.js} +3 -3
- package/dist/chunks/{chunk-ZXZ22FKF.js → chunk-XTWKSMJC.js} +3 -3
- package/dist/chunks/{chunk-6NG5K7WN.js → chunk-YHEMML5Q.js} +3 -3
- package/dist/chunks/{chunk-MVFPEBMY.js → chunk-ZKRSVNY4.js} +3 -3
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// A sheet of twelve Halloween labels for treat bags, jars and party favours, to print on sticker paper: four designs
|
|
2
|
+
// (a ghost, a jack-o'-lantern, a bat across the moon, candy corn) on purple, orange and lime squares with rounded
|
|
3
|
+
// corners, each with a line of its own ("Boo!", "Eat me if you dare") and a "From" line to fill in. One component set,
|
|
4
|
+
// so a colour change reaches every label. US Letter on a black ground with a spider's web in the corner, running into
|
|
5
|
+
// the bleed, and an A4 copy.
|
|
6
|
+
|
|
7
|
+
import { Kit, preset } from '../kit'
|
|
8
|
+
import { defineTemplate } from '../build'
|
|
9
|
+
import { SERIF } from '../shared/type'
|
|
10
|
+
import { bat, candyCorn, disc, draw, ghost, jackFace, printVariant, pumpkin, specksIn, web, type Drawn } from '../themes/holidays-shared'
|
|
11
|
+
|
|
12
|
+
const NAME = 'Halloween treat bag labels sticker sheet'
|
|
13
|
+
const PAD = { top: 52, right: 60, bottom: 40, left: 60 }
|
|
14
|
+
const D = 196
|
|
15
|
+
|
|
16
|
+
type Design = 'Ghost' | 'Pumpkin' | 'Bat' | 'Candy'
|
|
17
|
+
const DESIGNS: readonly Design[] = ['Ghost', 'Pumpkin', 'Bat', 'Candy']
|
|
18
|
+
const LOOK: Record<Design, { ground: string; words: string }> = {
|
|
19
|
+
Ghost: { ground: 'purple', words: 'bone' },
|
|
20
|
+
Pumpkin: { ground: 'bone', words: 'purple' },
|
|
21
|
+
Bat: { ground: 'orange', words: 'ink' },
|
|
22
|
+
Candy: { ground: 'lime', words: 'ink' },
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function picture(d: Design, cx: number, cy: number): Drawn[] {
|
|
26
|
+
if (d === 'Ghost') { const g = ghost(cx, cy - 46, 92); return [{ name: 'Ghost', paths: g.sheet, color: 'bone' }, { name: 'Ghost face', paths: g.face, color: 'ink' }] }
|
|
27
|
+
if (d === 'Pumpkin') { const p = pumpkin(cx, cy + 40, 104, 80); return [{ name: 'Pumpkin', paths: p.lobes, color: 'orange' }, { name: 'Pumpkin creases', paths: p.creases, color: 'orangeDeep', o: { strokeWidth: 2 } }, { name: 'Pumpkin stem', paths: p.stem, color: 'lime' }, { name: 'Pumpkin face', paths: jackFace(cx, cy + 2, 84), color: 'candle' }] }
|
|
28
|
+
if (d === 'Bat') return [{ name: 'Moon', paths: [disc(cx, cy, 44)], color: 'bone' }, { name: 'Bat', paths: [bat(cx, cy + 4, 110, 0.3)], color: 'ink' }]
|
|
29
|
+
const corns = [candyCorn(cx - 30, cy + 4, 62, -0.35), candyCorn(cx + 2, cy - 6, 72, 0.05), candyCorn(cx + 34, cy + 6, 60, 0.4)]
|
|
30
|
+
return [{ name: 'Corn base', paths: corns.flatMap(c => c.base), color: 'orange' }, { name: 'Corn middle', paths: corns.flatMap(c => c.mid), color: 'candle' }, { name: 'Corn tip', paths: corns.flatMap(c => c.tip), color: 'bone' }]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function ground(w: number, h: number): Drawn[] {
|
|
34
|
+
const B = 12
|
|
35
|
+
return [
|
|
36
|
+
{ name: 'Web', paths: web(w + 4, -4, 190, Math.PI / 2, Math.PI, 7, 7), color: 'bone', o: { strokeWidth: 1.2, opacity: 0.35 } },
|
|
37
|
+
{ name: 'Web', paths: web(-4, h + 4, 150, -Math.PI / 2, 0, 6, 6), color: 'bone', o: { strokeWidth: 1.2, opacity: 0.3 } },
|
|
38
|
+
...[0, 1, 2].map(i => ({ name: 'Dust', paths: specksIn({ x: -B, y: -B + ((h + 2 * B) * i) / 3, w: w + 2 * B, h: (h + 2 * B) / 3 }, 900, 50 + i, 2), color: 'purple', o: { opacity: 0.6 } })),
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default defineTemplate({
|
|
43
|
+
id: 'print-holidays-halloween-treat-labels',
|
|
44
|
+
meta: {
|
|
45
|
+
name: NAME,
|
|
46
|
+
description: 'Twelve Halloween labels for treat bags, jars and party favours on one sticker sheet: a ghost, a jack-o’-lantern, a bat across the moon and candy corn on purple, orange and lime rounded squares, each with its own line and a “From” line. One component set, so a colour change reaches every label. US Letter on black with bleed, and an A4 copy',
|
|
47
|
+
category: 'sticker', tags: ['halloween', 'labels', 'stickers', 'sticker sheet', 'treat bags', 'party favors', 'trick or treat', 'ghost', 'pumpkin', 'candy corn', 'printable', 'diy', 'print'],
|
|
48
|
+
platforms: ['print'], formats: ['card'], useCases: ['event', 'greeting'],
|
|
49
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
50
|
+
},
|
|
51
|
+
stableNumbers: true,
|
|
52
|
+
build() {
|
|
53
|
+
const k = new Kit(NAME, preset('us-letter'))
|
|
54
|
+
k.quantizeGeometry = true
|
|
55
|
+
k.brand(
|
|
56
|
+
{ paper: '#121016', text: '#F3EBDD', onPaper: '#C9B8E8', primary: '#FF8A1F', onPrimary: '#121016' },
|
|
57
|
+
{ name: 'The Ramirez kids' },
|
|
58
|
+
)
|
|
59
|
+
k.palette({ purple: '#5B3A8E', orange: '#FF8A1F', orangeDeep: '#B85A0C', lime: '#B6E05A', bone: '#F3EBDD', ink: '#121016', candle: '#FFD24D' })
|
|
60
|
+
k.textStyle('Head 34', { size: 34, fontWeight: 900, letterSpacing: -1, lineHeight: 38 })
|
|
61
|
+
k.textStyle('Note 11', { size: 11, fontWeight: 500, letterSpacing: 0.4, lineHeight: 16 })
|
|
62
|
+
k.textStyle('Label 17', { ...SERIF, size: 17, fontWeight: 800, fontStyle: 'italic', lineHeight: 20 })
|
|
63
|
+
k.textStyle('From 9', { size: 9, fontWeight: 700, letterSpacing: 2, lineHeight: 12 })
|
|
64
|
+
|
|
65
|
+
k.variantSet('Treat label', 'Design', DESIGNS, { width: D, height: D, radius: 30, padding: { top: 132, right: 14, bottom: 14, left: 14 }, gap: 4, align: 'CENTER', props: { Line: 'Boo!', From: 'FROM: ____________' } }, (d, id) => {
|
|
66
|
+
const L = LOOK[d as Design]
|
|
67
|
+
draw(k, id, picture(d as Design, D / 2, 70))
|
|
68
|
+
k.propText(id, 'Line', { style: 'Label 17', color: L.words, align: 'CENTER', width: 'FILL', name: 'Line' })
|
|
69
|
+
k.propText(id, 'From', { style: 'From 9', color: L.words, align: 'CENTER', width: 'FILL', name: 'From' })
|
|
70
|
+
}, d => ({ fill: LOOK[d as Design].ground }))
|
|
71
|
+
|
|
72
|
+
const sheet = k.sheet
|
|
73
|
+
const { width: W, height: H } = k.node(sheet)
|
|
74
|
+
k.flowSheet(sheet, 18, PAD, 'paper', 'MIN', 'CENTER')
|
|
75
|
+
draw(k, sheet, ground(W, H))
|
|
76
|
+
k.text(sheet, 'Spooky treat labels', { style: 'Head 34', color: 'text', align: 'CENTER', width: 'FILL', name: 'Title' })
|
|
77
|
+
const grid = k.stack(sheet, { direction: 'HORIZONTAL', wrap: true, gap: 22, width: 'FILL', justify: 'CENTER', name: 'Labels' })
|
|
78
|
+
k.patch(grid, { counterAxisSpacing: 22 })
|
|
79
|
+
const lines = ['Boo!', 'Trick or treat!', 'Eat me if you dare', 'Spooky snacks', 'Happy Halloween', 'Witch’s stash', 'Ghoulishly good', 'Fang-tastic!', 'Bat-ter up', 'Sweet & scary', 'Hocus pocus', 'Candy for you']
|
|
80
|
+
lines.forEach((l, i) => k.instance(grid, `Treat label/${DESIGNS[i % 4]}`, { Line: l }, { name: 'Treat label' }))
|
|
81
|
+
k.text(sheet, 'Print on full-sheet sticker paper, cut round each square, and stick on bags, jars and boxes · {{brand.name}}', { style: 'Note 11', color: 'onPaper', align: 'CENTER', width: 'FILL', name: 'Note' })
|
|
82
|
+
|
|
83
|
+
k.variants(sheet, [printVariant('a4', { art: ground, pad: PAD, masterWidth: W })])
|
|
84
|
+
return k.finish()
|
|
85
|
+
},
|
|
86
|
+
})
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// A poster for a church's trunk-or-treat in its car park, printed like a two-colour screenprint: pumpkin orange and
|
|
2
|
+
// night purple, a halftone sky behind "Trunk or Treat" in huge heavy capitals, a hatchback with its boot open and
|
|
3
|
+
// spilling pumpkins, candy and bunting, and the details below in two columns: when and where, the costume parade,
|
|
4
|
+
// the prize for the best-dressed trunk, and the teal-pumpkin table of allergy-friendly treats. Tabloid (11 × 17
|
|
5
|
+
// inches) for the noticeboard, the colour running into the bleed, and a US Letter flyer worked out again.
|
|
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 { along, bat, candyCorn, disc, draw, egg, jackFace, polyP, quad, printVariant, pumpkin, rectPath, roundRect, type Drawn } from '../themes/holidays-shared'
|
|
12
|
+
|
|
13
|
+
const NAME = 'Trunk-or-treat poster'
|
|
14
|
+
const PAD = { top: 80, right: 72, bottom: 64, left: 72 }
|
|
15
|
+
|
|
16
|
+
/** A hatchback seen from the side, its tailgate lifted, standing on `ground` with its back at `x`, `s` the scale. */
|
|
17
|
+
function car(x: number, ground: number, s: number) {
|
|
18
|
+
const u = (v: number) => v * s
|
|
19
|
+
const body = [roundRect(x, ground - u(150), u(520), u(110), u(30)), polyP([{ x: x + u(150), y: ground - u(146) }, { x: x + u(210), y: ground - u(250) }, { x: x + u(420), y: ground - u(250) }, { x: x + u(500), y: ground - u(146) }])]
|
|
20
|
+
const windows = [polyP([{ x: x + u(232), y: ground - u(232) }, { x: x + u(318), y: ground - u(232) }, { x: x + u(318), y: ground - u(160) }, { x: x + u(196), y: ground - u(160) }]), polyP([{ x: x + u(332), y: ground - u(232) }, { x: x + u(412), y: ground - u(232) }, { x: x + u(472), y: ground - u(160) }, { x: x + u(332), y: ground - u(160) }])]
|
|
21
|
+
const wheels = [disc(x + u(110), ground - u(36), u(46)), disc(x + u(420), ground - u(36), u(46))]
|
|
22
|
+
const hubs = [disc(x + u(110), ground - u(36), u(18)), disc(x + u(420), ground - u(36), u(18))]
|
|
23
|
+
// The tailgate swung up and back from the roof's rear edge, and the open boot under it.
|
|
24
|
+
const gate = [polyP([{ x: x + u(170), y: ground - u(252) }, { x: x + u(40), y: ground - u(330) }, { x: x - u(10), y: ground - u(316) }, { x: x + u(150), y: ground - u(240) }])]
|
|
25
|
+
const boot = [polyP([{ x: x + u(16), y: ground - u(146) }, { x: x + u(160), y: ground - u(146) }, { x: x + u(200), y: ground - u(236) }, { x: x + u(150), y: ground - u(240) }])]
|
|
26
|
+
return { body, windows, wheels, hubs, gate, boot }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function art(w: number, h: number): Drawn[] {
|
|
30
|
+
const s = w / 1056, B = 12
|
|
31
|
+
// A halftone sky: dots that grow towards the top, in rows, across the head of the sheet.
|
|
32
|
+
const dots: VectorPath[] = []
|
|
33
|
+
for (let row = 0; row < 16; row++) for (let col = 0; col < 40; col++) {
|
|
34
|
+
const x = -B + col * 28 * s + (row % 2) * 14 * s, y = -B + row * 26 * s, r = Math.max(0, (1 - row / 16) * 10 * s)
|
|
35
|
+
if (r > 0.6) dots.push(disc(x, y, r))
|
|
36
|
+
}
|
|
37
|
+
const g = h - 420 * s, U = 1.5 * s, X = (w - 520 * U) / 2 + 40 * s
|
|
38
|
+
const c = car(X, g, U)
|
|
39
|
+
const floor = g - 150 * U
|
|
40
|
+
const pumps = [[60, 70, 54], [118, 50, 40], [24, 44, 34]].map(([dx, pw, ph]) => ({ x: X + dx * U, y: floor, pw: pw * U, ph: ph * U, p: pumpkin(X + dx * U, floor, pw * U, ph * U) }))
|
|
41
|
+
const corn = [candyCorn(X + 96 * U, floor - 50 * U, 26 * U, -0.4), candyCorn(X + 140 * U, floor - 46 * U, 24 * U, 0.3), candyCorn(X + 8 * U, floor - 40 * U, 22 * U, 0.7)]
|
|
42
|
+
// Bunting hung from the lifted tailgate's lower edge.
|
|
43
|
+
const ga = { x: X + 150 * U, y: g - 240 * U }, gb = { x: X - 10 * U, y: g - 316 * U }
|
|
44
|
+
const bunting = [0, 1, 2, 3, 4, 5].map(i => {
|
|
45
|
+
const t0 = 0.04 + i * 0.16, t1 = t0 + 0.12
|
|
46
|
+
const p0 = { x: ga.x + (gb.x - ga.x) * t0, y: ga.y + (gb.y - ga.y) * t0 }, p1 = { x: ga.x + (gb.x - ga.x) * t1, y: ga.y + (gb.y - ga.y) * t1 }
|
|
47
|
+
return polyP([p0, p1, { x: (p0.x + p1.x) / 2, y: (p0.y + p1.y) / 2 + 30 * U }])
|
|
48
|
+
})
|
|
49
|
+
// String lights across the sky between the headline and the car.
|
|
50
|
+
const wire: VectorPath[] = [], bulbs: VectorPath[][] = [[], [], []]
|
|
51
|
+
for (let span = 0; span < 3; span++) {
|
|
52
|
+
const a = { x: -B + span * (w + 2 * B) / 3, y: 600 * s }, z = { x: -B + (span + 1) * (w + 2 * B) / 3, y: 600 * s }, m = { x: (a.x + z.x) / 2, y: 680 * s }
|
|
53
|
+
wire.push(quad(a, m, z, 16))
|
|
54
|
+
for (let i = 1; i < 6; i++) { const q = along(a, m, z, i / 6).p; bulbs[(i + span) % 3].push(egg(q.x, q.y + 16 * s, 16 * s, 24 * s, Math.PI)) }
|
|
55
|
+
}
|
|
56
|
+
const bats = [[0.16, 0.3, 70, 0.3], [0.82, 0.26, 90, -0.2], [0.9, 0.42, 50, 0.4], [0.08, 0.44, 46, -0.3]].map(([x, y, sz, f]) => bat(w * x, h * y, sz * s, f))
|
|
57
|
+
return [
|
|
58
|
+
{ name: 'Halftone', paths: dots, color: 'orangeDeep', o: { opacity: 0.55 } },
|
|
59
|
+
...bats.map(b => ({ name: 'Bat', paths: [b], color: 'purple' })),
|
|
60
|
+
{ name: 'Light wire', paths: wire, color: 'purple', o: { strokeWidth: 2.4 * s } },
|
|
61
|
+
{ name: 'Bulbs', paths: bulbs[0], color: 'teal' },
|
|
62
|
+
{ name: 'Bulbs', paths: bulbs[1], color: 'cream' },
|
|
63
|
+
{ name: 'Bulbs', paths: bulbs[2], color: 'purpleDeep' },
|
|
64
|
+
{ name: 'Asphalt', paths: [rectPath(-B, g - 6 * s, w + 2 * B, h - g + 6 * s + B)], color: 'purple' },
|
|
65
|
+
{ name: 'Kerb', paths: [rectPath(-B, g - 6 * s, w + 2 * B, 6 * s)], color: 'orangeDeep' },
|
|
66
|
+
{ name: 'Car', paths: c.body, color: 'teal' },
|
|
67
|
+
{ name: 'Tailgate', paths: c.gate, color: 'teal' },
|
|
68
|
+
{ name: 'Boot', paths: c.boot, color: 'purpleDeep' },
|
|
69
|
+
{ name: 'Car windows', paths: c.windows, color: 'purpleDeep' },
|
|
70
|
+
{ name: 'Wheels', paths: c.wheels, color: 'purpleDeep' },
|
|
71
|
+
{ name: 'Hubs', paths: c.hubs, color: 'orange' },
|
|
72
|
+
{ name: 'Bunting', paths: bunting.filter((_, i) => i % 2 === 0), color: 'orange' },
|
|
73
|
+
{ name: 'Bunting', paths: bunting.filter((_, i) => i % 2 === 1), color: 'cream' },
|
|
74
|
+
{ name: 'Boot pumpkins', paths: pumps.flatMap(p => p.p.lobes), color: 'orange' },
|
|
75
|
+
{ name: 'Boot pumpkin creases', paths: pumps.flatMap(p => p.p.creases), color: 'orangeDeep', o: { strokeWidth: 2 * s } },
|
|
76
|
+
{ name: 'Boot pumpkin stems', paths: pumps.flatMap(p => p.p.stem), color: 'purpleDeep' },
|
|
77
|
+
{ name: 'Boot pumpkin faces', paths: jackFace(pumps[0].x, pumps[0].y - pumps[0].ph * 0.5, pumps[0].pw * 0.8), color: 'purpleDeep' },
|
|
78
|
+
{ name: 'Candy corn', paths: corn.flatMap(k => [...k.base, ...k.mid, ...k.tip]), color: 'cream' },
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default defineTemplate({
|
|
83
|
+
id: 'print-holidays-halloween-trunk-or-treat-poster',
|
|
84
|
+
meta: {
|
|
85
|
+
name: NAME,
|
|
86
|
+
description: 'A two-colour screenprint-style poster for a church car-park trunk-or-treat: a halftone sky behind “Trunk or Treat” in huge capitals, a hatchback with its boot open and spilling pumpkins, candy and bunting, and the details in two columns: when and where, the costume parade, the best-trunk prize and the teal-pumpkin table of allergy-friendly treats. Tabloid with bleed, and a US Letter flyer',
|
|
87
|
+
category: 'poster', tags: ['halloween', 'trunk or treat', 'church', 'community', 'kids', 'family', 'costume parade', 'teal pumpkin', 'screenprint', 'retro', 'poster', 'flyer', 'print', 'event'],
|
|
88
|
+
platforms: ['print'], formats: ['poster', 'flyer'], useCases: ['event', 'announcement'],
|
|
89
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
90
|
+
},
|
|
91
|
+
stableNumbers: true,
|
|
92
|
+
build() {
|
|
93
|
+
const k = new Kit(NAME, preset('tabloid'))
|
|
94
|
+
k.quantizeGeometry = true
|
|
95
|
+
k.brand(
|
|
96
|
+
{ paper: '#F28A2E', text: '#2A1840', onPaper: '#3E2559', primary: '#2A1840', onPrimary: '#FFE9CC' },
|
|
97
|
+
{ name: 'Grace Community Church', site: 'gracecommunity.org/trunk' },
|
|
98
|
+
)
|
|
99
|
+
k.palette({ orange: '#F28A2E', orangeDeep: '#D9661A', purple: '#2A1840', purpleDeep: '#1A0E29', teal: '#3FB3A6', cream: '#FFE9CC' })
|
|
100
|
+
k.textStyle('Kicker 30', { size: 30, fontWeight: 800, letterSpacing: 8, lineHeight: 38 })
|
|
101
|
+
k.textStyle('Title 176', { size: 176, fontWeight: 900, letterSpacing: -7, lineHeight: 150 })
|
|
102
|
+
k.textStyle('Sub 34', { ...SERIF, size: 34, fontWeight: 700, fontStyle: 'italic', lineHeight: 42 })
|
|
103
|
+
k.textStyle('Col head 22', { size: 22, fontWeight: 800, letterSpacing: 3, lineHeight: 28 })
|
|
104
|
+
k.textStyle('Col 22', { ...SERIF, size: 22, fontWeight: 400, lineHeight: 31 })
|
|
105
|
+
k.textStyle('Foot 22', { size: 22, fontWeight: 700, letterSpacing: 1, lineHeight: 28 })
|
|
106
|
+
|
|
107
|
+
k.component('Detail', { gap: 6, width: 420, props: { Head: 'WHEN', Text: 'What happens' } }, id => {
|
|
108
|
+
k.propText(id, 'Head', { style: 'Col head 22', color: 'cream', width: 'FILL', name: 'Detail head' })
|
|
109
|
+
k.propText(id, 'Text', { style: 'Col 22', color: 'cream', width: 'FILL', name: 'Detail text' })
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const sheet = k.sheet
|
|
113
|
+
const { width: W, height: H } = k.node(sheet)
|
|
114
|
+
k.flowSheet(sheet, 0, PAD, 'paper', 'MIN', 'CENTER')
|
|
115
|
+
draw(k, sheet, art(W, H))
|
|
116
|
+
const c = { align: 'CENTER' as const, width: 'FILL' as const }
|
|
117
|
+
k.text(sheet, 'FRIDAY 30 OCTOBER · 5:30–7:30 PM', { style: 'Kicker 30', color: 'text', ...c, name: 'Kicker' })
|
|
118
|
+
k.stack(sheet, { height: 14, name: 'Gap' })
|
|
119
|
+
k.text(sheet, 'TRUNK\nOR TREAT', { style: 'Title 176', color: 'text', ...c, name: 'Title' })
|
|
120
|
+
k.stack(sheet, { height: 14, name: 'Gap' })
|
|
121
|
+
k.text(sheet, 'Safe, bright, and the candy comes to you', { style: 'Sub 34', color: 'text', ...c, name: 'Sub' })
|
|
122
|
+
// The car in the middle of the sheet, then the details on the tarmac below.
|
|
123
|
+
k.stack(sheet, { width: 'FILL', height: 'FILL', name: 'Car space' })
|
|
124
|
+
const cols = k.stack(sheet, { direction: 'HORIZONTAL', gap: 48, width: 'HUG', name: 'Details' })
|
|
125
|
+
const left = k.stack(cols, { gap: 22, width: 420, name: 'Left' })
|
|
126
|
+
const right = k.stack(cols, { gap: 22, width: 420, name: 'Right' })
|
|
127
|
+
k.instance(left, 'Detail', { Head: 'WHERE', Text: 'The {{brand.name}} car park, 1100 Elm Avenue. Come in costume; grown-ups too.' }, { name: 'Detail', width: 'FILL' })
|
|
128
|
+
k.instance(left, 'Detail', { Head: 'COSTUME PARADE', Text: 'Round the car park at 6:30, with a prize for every age group.' }, { name: 'Detail', width: 'FILL' })
|
|
129
|
+
k.instance(right, 'Detail', { Head: 'DECORATE A TRUNK', Text: 'Sign up by 23 October. Best-dressed trunk wins a $50 pumpkin-patch voucher.' }, { name: 'Detail', width: 'FILL' })
|
|
130
|
+
k.instance(right, 'Detail', { Head: 'TEAL PUMPKIN TABLE', Text: 'Allergy-friendly treats and small toys, no nuts anywhere near it.' }, { name: 'Detail', width: 'FILL' })
|
|
131
|
+
k.stack(sheet, { height: 34, name: 'Gap' })
|
|
132
|
+
k.text(sheet, 'Free · hot cider and doughnuts · {{brand.site}}', { style: 'Foot 22', color: 'cream', ...c, name: 'Foot' })
|
|
133
|
+
|
|
134
|
+
k.textStyle('Kicker 15', { size: 15, fontWeight: 800, letterSpacing: 4, lineHeight: 19 })
|
|
135
|
+
k.textStyle('Title 88', { size: 86, fontWeight: 900, letterSpacing: -3.4, lineHeight: 74 })
|
|
136
|
+
k.textStyle('Sub 17', { ...SERIF, size: 17, fontWeight: 700, fontStyle: 'italic', lineHeight: 21 })
|
|
137
|
+
k.textStyle('Foot 11', { size: 11, fontWeight: 700, letterSpacing: 0.6, lineHeight: 14 })
|
|
138
|
+
k.variants(sheet, [printVariant('us-letter', {
|
|
139
|
+
art, pad: PAD, masterWidth: W,
|
|
140
|
+
styles: [['Kicker', 'Kicker 15'], ['Title', 'Title 88'], ['Sub', 'Sub 17'], ['Foot', 'Foot 11']],
|
|
141
|
+
more: (kit, id) => {
|
|
142
|
+
for (const n of kit.named(id, 'Details')) kit.patch(n, { itemSpacing: 22 })
|
|
143
|
+
for (const n of [...kit.named(id, 'Left'), ...kit.named(id, 'Right')]) kit.patch(n, { width: 320, itemSpacing: 10 })
|
|
144
|
+
},
|
|
145
|
+
})])
|
|
146
|
+
return k.finish()
|
|
147
|
+
},
|
|
148
|
+
})
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// A printable Hanukkah activity sheet for children, for a Hebrew school, a synagogue's family service or a long
|
|
2
|
+
// evening at home: a maze to take the dreidel to the gelt, a word search of Hanukkah words, and a hanukkiah drawn in
|
|
3
|
+
// outline to colour, a candle for each night. Blue and white, playful heavy type, drawn to print in black and white
|
|
4
|
+
// as well as colour. US Letter with the header band running into the bleed, and an A4 copy.
|
|
5
|
+
|
|
6
|
+
import { Kit, preset } from '../kit'
|
|
7
|
+
import { defineTemplate } from '../build'
|
|
8
|
+
import { MONO, SERIF } from '../shared/type'
|
|
9
|
+
import type { VectorPath } from '@/lib/document/types'
|
|
10
|
+
import { disc, draw, dreidel, gelt, maze, menorah, printVariant, rectPath, sparkle, starOfDavid, wordSearch, type Drawn } from '../themes/holidays-shared'
|
|
11
|
+
|
|
12
|
+
const NAME = 'Hanukkah activity sheet for kids'
|
|
13
|
+
const PAD = { top: 26, right: 48, bottom: 40, left: 48 }
|
|
14
|
+
const WORDS = ['LATKES', 'DREIDEL', 'MENORAH', 'CANDLES', 'GELT', 'SHAMASH', 'MACCABEES', 'OIL', 'LIGHT', 'JERUSALEM'] as const
|
|
15
|
+
|
|
16
|
+
/** The header band: blue, with a scalloped edge, sparkles and stars of David. */
|
|
17
|
+
function header(w: number): Drawn[] {
|
|
18
|
+
const B = 12, s = w / 816, y = 140
|
|
19
|
+
const scallops: VectorPath[] = []
|
|
20
|
+
for (let x = -B, i = 0; x < w + B; x += 34 * s, i++) scallops.push(disc(x + 17 * s, y, 17 * s, 12 * s))
|
|
21
|
+
return [
|
|
22
|
+
{ name: 'Band', paths: [rectPath(-B, -B, w + 2 * B, y + B)], color: 'band' },
|
|
23
|
+
{ name: 'Scallops', paths: scallops, color: 'band' },
|
|
24
|
+
{ name: 'Band sparkles', paths: [[0.05, 0.3, 14], [0.12, 0.72, 9]].map(([x, yy, r]) => sparkle(w * x, 140 * yy, r * s)), color: 'onBand' },
|
|
25
|
+
{ name: 'Band sparkles', paths: [[0.92, 0.22, 18], [0.85, 0.62, 9]].map(([x, yy, r]) => sparkle(w * x, 140 * yy, r * s)), color: 'onBand' },
|
|
26
|
+
{ name: 'Band stars', paths: starOfDavid(w * 0.035, 112, 9 * s), color: 'onBand', o: { strokeWidth: 1.6 * s } },
|
|
27
|
+
{ name: 'Band stars', paths: starOfDavid(w * 0.965, 104, 10 * s), color: 'onBand', o: { strokeWidth: 1.6 * s } },
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default defineTemplate({
|
|
32
|
+
id: 'print-holidays-hanukkah-activity-sheet',
|
|
33
|
+
meta: {
|
|
34
|
+
name: NAME,
|
|
35
|
+
description: 'A printable Hanukkah activity sheet for children: a maze to take the dreidel to the gelt, a word search of ten Hanukkah words, and a hanukkiah in outline to colour a candle each night. Blue and white with playful heavy type, and it prints well in black and white. US Letter with bleed, and an A4 copy',
|
|
36
|
+
category: 'document', tags: ['hanukkah', 'chanukah', 'kids', 'children', 'activity sheet', 'colouring', 'coloring page', 'maze', 'word search', 'hebrew school', 'synagogue', 'printable', 'jewish', 'print'],
|
|
37
|
+
platforms: ['print'], formats: ['document'], useCases: ['education', 'event'],
|
|
38
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
39
|
+
},
|
|
40
|
+
stableNumbers: true,
|
|
41
|
+
build() {
|
|
42
|
+
const k = new Kit(NAME, preset('us-letter'))
|
|
43
|
+
k.quantizeGeometry = true
|
|
44
|
+
k.brand(
|
|
45
|
+
{ paper: '#FFFFFF', text: '#14306B', onPaper: '#3F5C99', primary: '#2457C5', onPrimary: '#FFFFFF', line: '#B9CBEF', card: '#EAF0FC', onCard: '#14306B' },
|
|
46
|
+
{ name: 'Temple Beth Shalom Hebrew School' },
|
|
47
|
+
)
|
|
48
|
+
k.palette({ band: '#2457C5', onBand: '#FFFFFF', gold: '#E8B33C', goldDeep: '#A87A1E', soft: '#EAF0FC', outline: '#14306B', dreidel: '#7FA6EA', dreidelSide: '#4C78CF' })
|
|
49
|
+
k.textStyle('Title 50', { size: 50, fontWeight: 900, letterSpacing: -1.4, lineHeight: 54 })
|
|
50
|
+
k.textStyle('Band sub 15', { ...SERIF, size: 15, fontWeight: 400, fontStyle: 'italic', lineHeight: 20 })
|
|
51
|
+
k.textStyle('Head 20', { size: 20, fontWeight: 800, letterSpacing: -0.2, lineHeight: 24 })
|
|
52
|
+
k.textStyle('Hint 12', { ...SERIF, size: 12, fontWeight: 400, lineHeight: 17 })
|
|
53
|
+
k.textStyle('Grid 17', { ...MONO, size: 15.5, fontWeight: 600, letterSpacing: 2.2, lineHeight: 22 })
|
|
54
|
+
k.textStyle('Words 11', { size: 11, fontWeight: 700, letterSpacing: 1.2, lineHeight: 17 })
|
|
55
|
+
k.textStyle('Name 12', { size: 12, fontWeight: 600, lineHeight: 18 })
|
|
56
|
+
k.textStyle('Foot 10', { size: 10, fontWeight: 500, lineHeight: 14 })
|
|
57
|
+
|
|
58
|
+
const sheet = k.sheet
|
|
59
|
+
const { width: W } = k.node(sheet)
|
|
60
|
+
k.flowSheet(sheet, 14, PAD, 'paper', 'MIN', 'MIN')
|
|
61
|
+
draw(k, sheet, header(W))
|
|
62
|
+
// The title on the band: the flow's first row is as tall as the band, so the words sit on it and stay live.
|
|
63
|
+
const head = k.stack(sheet, { width: 'FILL', height: 112, gap: 2, justify: 'CENTER', align: 'CENTER', name: 'Header' })
|
|
64
|
+
k.text(head, 'Hanukkah Fun Page', { style: 'Title 50', color: 'onBand', autoWidth: true, name: 'Title' })
|
|
65
|
+
k.text(head, 'Eight nights of light, one page of puzzles', { style: 'Band sub 15', color: 'onBand', autoWidth: true, name: 'Subtitle' })
|
|
66
|
+
k.stack(sheet, { height: 8, name: 'Gap' })
|
|
67
|
+
const top = k.stack(sheet, { direction: 'HORIZONTAL', width: 'FILL', justify: 'SPACE_BETWEEN', align: 'MAX', name: 'Name row' })
|
|
68
|
+
k.text(top, 'Name: ________________________', { style: 'Name 12', color: 'text', autoWidth: true, name: 'Name' })
|
|
69
|
+
k.text(top, 'Age: ______', { style: 'Name 12', color: 'text', autoWidth: true, name: 'Age' })
|
|
70
|
+
|
|
71
|
+
// A panel: a heading, a hint and a drawing or a puzzle, on a soft blue card.
|
|
72
|
+
const panel = (parent: string, title: string, hint: string, name: string) => {
|
|
73
|
+
const p = k.stack(parent, { width: 'FILL', gap: 8, padding: 18, fill: 'card', radius: 18, name })
|
|
74
|
+
k.instance(p, 'Panel head', { Title: title, Hint: hint }, { name: 'Panel head', width: 'FILL' })
|
|
75
|
+
return p
|
|
76
|
+
}
|
|
77
|
+
k.component('Panel head', { gap: 2, width: 320, props: { Title: 'Puzzle', Hint: 'What to do' } }, id => {
|
|
78
|
+
k.propText(id, 'Title', { style: 'Head 20', color: 'onCard', width: 'FILL', name: 'Panel title' })
|
|
79
|
+
k.propText(id, 'Hint', { style: 'Hint 12', color: 'onCard', width: 'FILL', name: 'Panel hint' })
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const row = k.stack(sheet, { direction: 'HORIZONTAL', gap: 20, width: 'FILL', name: 'Puzzles' })
|
|
83
|
+
const mz = panel(row, 'Spin to the gelt', 'Help the dreidel find its way through the maze to the chocolate coins.', 'Maze panel')
|
|
84
|
+
const mart = k.stack(mz, { width: 276, height: 300, name: 'Maze art' })
|
|
85
|
+
const cell = 276 / 12
|
|
86
|
+
const d = dreidel(cell * 0.5, 20, 30, -0.3), g = gelt(276 - cell * 0.5, 292, 8)
|
|
87
|
+
draw(k, mart, [
|
|
88
|
+
{ name: 'Maze walls', paths: maze(0, 34, 12, 11, cell, 2026), color: 'outline', o: { strokeWidth: 3 } },
|
|
89
|
+
{ name: 'Dreidel', paths: d.body, color: 'dreidel' }, { name: 'Dreidel side', paths: d.side, color: 'dreidelSide' }, { name: 'Dreidel handle', paths: d.handle, color: 'gold' },
|
|
90
|
+
{ name: 'Gelt', paths: [...g.coin, ...gelt(276 - cell * 0.5 - 14, 290, 7).coin], color: 'gold' },
|
|
91
|
+
])
|
|
92
|
+
const ws = panel(row, 'Word search', 'Find the ten words. They run across, down and on the slant.', 'Word panel')
|
|
93
|
+
k.text(ws, wordSearch(WORDS, 12, 5786).join('\n'), { style: 'Grid 17', color: 'onCard', align: 'CENTER', width: 'FILL', name: 'Grid' })
|
|
94
|
+
k.text(ws, WORDS.join(' · '), { style: 'Words 11', color: 'onCard', align: 'CENTER', width: 'FILL', name: 'Word list' })
|
|
95
|
+
|
|
96
|
+
const col = panel(sheet, 'Colour a candle each night', 'Each night add one more candle, filling from the right, and light the newest one first. Colour in tonight’s candles, and the shamash in the middle, the helper that lights the others.', 'Colouring panel')
|
|
97
|
+
const cart = k.stack(col, { width: 'FILL', height: 196, name: 'Colouring art', align: 'CENTER' })
|
|
98
|
+
const m = menorah(342, 190, 520, 132, 8)
|
|
99
|
+
draw(k, cart, [
|
|
100
|
+
{ name: 'Outline stand', paths: [...m.stand], color: 'outline', o: { strokeWidth: 3 } },
|
|
101
|
+
{ name: 'Outline foot', paths: m.foot, color: 'outline', o: { strokeWidth: 3 } },
|
|
102
|
+
{ name: 'Outline cups', paths: m.cups, color: 'outline', o: { strokeWidth: 2.4 } },
|
|
103
|
+
{ name: 'Outline candles', paths: m.candles, color: 'outline', o: { strokeWidth: 2.4 } },
|
|
104
|
+
{ name: 'Outline flames', paths: m.flames, color: 'outline', o: { strokeWidth: 2 } },
|
|
105
|
+
])
|
|
106
|
+
k.text(col, 'Night 1 is the candle on the far right. Count them all: with the shamash, you light 44 candles in eight nights.', { style: 'Hint 12', color: 'onCard', align: 'CENTER', width: 'FILL', name: 'Fact' })
|
|
107
|
+
k.text(sheet, 'Chag sameach from {{brand.name}} · Answers on the back of the classroom door', { style: 'Foot 10', color: 'onPaper', align: 'CENTER', width: 'FILL', name: 'Foot' })
|
|
108
|
+
|
|
109
|
+
k.variants(sheet, [printVariant('a4', { art: header, pad: PAD, masterWidth: W })])
|
|
110
|
+
return k.finish()
|
|
111
|
+
},
|
|
112
|
+
})
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// A printed invitation to a Hanukkah latke party on the second night, in a mid-century mood: a deep indigo card
|
|
2
|
+
// with a starburst sky, a brass hanukkiah in the middle with the shamash and two candles lit (candles are placed from
|
|
3
|
+
// the right, one more each night, so the second night shows two), dreidels spinning and gelt scattered at its foot.
|
|
4
|
+
// The type pairs a heavy, wide sans for "Latkes & Light" with a serif italic. 5 × 7 inches, the sky running into the
|
|
5
|
+
// eighth-inch bleed, and an A6 copy with the drawing worked out again.
|
|
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 { bandsAround, disc, polyP, draw, dreidel, flourishComponent, gelt, menorah, perBand, printVariant, sparkle, specksIn, starOfDavid, type Drawn } from '../themes/holidays-shared'
|
|
12
|
+
|
|
13
|
+
const NAME = 'Hanukkah latke party invitation'
|
|
14
|
+
const PAD = { top: 334, right: 40, bottom: 70, left: 40 }
|
|
15
|
+
|
|
16
|
+
function art(w: number, h: number): Drawn[] {
|
|
17
|
+
const s = w / 480, B = 14 * s, cx = w / 2
|
|
18
|
+
const base = 292 * s
|
|
19
|
+
const m = menorah(cx, base, 300 * s, 150 * s, 2)
|
|
20
|
+
const d1 = dreidel(62 * s, 252 * s, 64 * s, -0.35), d2 = dreidel(w - 66 * s, 246 * s, 56 * s, 0.4)
|
|
21
|
+
const coins = [[-104, 8, 13], [-78, 13, 10], [88, 9, 12], [112, 14, 9]].map(([dx, dy, r]) => gelt(cx + dx * s, base + dy * s, r * s))
|
|
22
|
+
// A mid-century border along the foot: diamonds and dots, with a star of David at the middle, running into the bleed.
|
|
23
|
+
const by = h - 32 * s, step = 26 * s
|
|
24
|
+
const border: VectorPath[] = [], borderDots: VectorPath[] = []
|
|
25
|
+
for (let x = -B, i = 0; x < w + B; x += step, i++) {
|
|
26
|
+
if (Math.abs(x - cx) < 34 * s) continue
|
|
27
|
+
border.push(polyP([{ x, y: by - 7 * s }, { x: x + 7 * s, y: by }, { x, y: by + 7 * s }, { x: x - 7 * s, y: by }]))
|
|
28
|
+
borderDots.push(disc(x + step / 2, by, 2.6 * s))
|
|
29
|
+
}
|
|
30
|
+
const borderStars = starOfDavid(cx, by, 14 * s)
|
|
31
|
+
const keep = { x: 28 * s, y: 318 * s, w: w - 56 * s, h: h - 380 * s }
|
|
32
|
+
// The sky: big four-point sparkles and a scatter of small ones, the mid-century kind.
|
|
33
|
+
const big = [[0.1, 0.07, 16], [0.9, 0.06, 18], [0.25, 0.04, 8], [0.72, 0.04, 9], [0.94, 0.27, 8], [0.06, 0.27, 10]].map(([x, y, r]) => sparkle(w * x, h * y, r * s, 0.16))
|
|
34
|
+
return [
|
|
35
|
+
...perBand('Stardust', 'sky', bandsAround(w, h, keep, B), (b, i) => specksIn(b, 520 * s * s, 40 + i, 2.6 * s), { opacity: 0.55 }),
|
|
36
|
+
{ name: 'Sparkles', paths: big, color: 'sky' },
|
|
37
|
+
{ name: 'Glow', paths: [disc(cx, 176 * s, 160 * s, 116 * s)], color: 'glow', o: { opacity: 0.2 } },
|
|
38
|
+
{ name: 'Border', paths: border, color: 'sky' },
|
|
39
|
+
{ name: 'Border dots', paths: borderDots, color: 'brass' },
|
|
40
|
+
{ name: 'Border stars', paths: borderStars, color: 'sky', o: { strokeWidth: 1.4 * s } },
|
|
41
|
+
{ name: 'Menorah', paths: m.stand, color: 'brass', o: { strokeWidth: 7 * s } },
|
|
42
|
+
{ name: 'Menorah foot', paths: m.foot, color: 'brass' },
|
|
43
|
+
{ name: 'Cups', paths: m.cups, color: 'brass' },
|
|
44
|
+
{ name: 'Candles', paths: m.candles, color: 'candle' },
|
|
45
|
+
{ name: 'Flames', paths: m.flames, color: 'flame' },
|
|
46
|
+
{ name: 'Dreidel', paths: [...d1.body, ...d2.body], color: 'sky' },
|
|
47
|
+
{ name: 'Dreidel side', paths: [...d1.side, ...d2.side], color: 'skyDeep' },
|
|
48
|
+
{ name: 'Dreidel handle', paths: [...d1.handle, ...d2.handle], color: 'brass' },
|
|
49
|
+
{ name: 'Gelt', paths: coins.flatMap(c => c.coin), color: 'brass' },
|
|
50
|
+
{ name: 'Gelt rim', paths: coins.flatMap(c => c.rim), color: 'brassDeep', o: { strokeWidth: 1.2 * s } },
|
|
51
|
+
{ name: 'Table', paths: [{ closed: false, points: [{ x: 40 * s, y: base + 22 * s }, { x: w - 40 * s, y: base + 22 * s }] }], color: 'sky', o: { strokeWidth: 1.4 * s, opacity: 0.6 } },
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default defineTemplate({
|
|
56
|
+
id: 'print-holidays-hanukkah-party-invitation',
|
|
57
|
+
meta: {
|
|
58
|
+
name: NAME,
|
|
59
|
+
description: 'A printed invitation to a Hanukkah latke party on the second night, in a mid-century style: a brass hanukkiah with the shamash and two candles lit under a starburst sky, spinning dreidels and gelt, then “Latkes & Light” in a heavy wide sans with a serif italic and the details. 5 × 7 inches with bleed, and an A6 copy',
|
|
60
|
+
category: 'invitation', tags: ['hanukkah', 'chanukah', 'latke party', 'menorah', 'hanukkiah', 'dreidel', 'gelt', 'jewish', 'holiday party', 'invitation', 'mid-century', 'retro', 'print', 'card'],
|
|
61
|
+
platforms: ['print'], formats: ['card'], useCases: ['invitation', 'event'],
|
|
62
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
63
|
+
},
|
|
64
|
+
stableNumbers: true,
|
|
65
|
+
build() {
|
|
66
|
+
const k = new Kit(NAME, preset('invitation-5x7'))
|
|
67
|
+
k.quantizeGeometry = true
|
|
68
|
+
k.brand(
|
|
69
|
+
{ paper: '#1E2F5C', text: '#F6F2E8', onPaper: '#EDBE4E', primary: '#E9B949', onPrimary: '#1E2F5C' },
|
|
70
|
+
{ name: 'Dana & Eli Rosen' },
|
|
71
|
+
)
|
|
72
|
+
k.palette({ sky: '#8FB4E6', skyDeep: '#5C82BE', brass: '#E2B04A', brassDeep: '#A87A22', candle: '#F6F2E8', flame: '#FFC94D', glow: '#4E6BA6' })
|
|
73
|
+
k.textStyle('Kicker 11', { size: 11, fontWeight: 700, letterSpacing: 3.4, lineHeight: 16 })
|
|
74
|
+
k.textStyle('Title 52', { size: 50, fontWeight: 900, letterSpacing: -1.8, lineHeight: 50 })
|
|
75
|
+
k.textStyle('Sub 22', { ...SERIF, size: 22, fontWeight: 400, fontStyle: 'italic', lineHeight: 28 })
|
|
76
|
+
k.textStyle('When 15', { size: 15, fontWeight: 700, lineHeight: 21 })
|
|
77
|
+
k.textStyle('Body 12', { ...SERIF, size: 12, fontWeight: 400, lineHeight: 17 })
|
|
78
|
+
k.textStyle('Rsvp 10', { size: 10, fontWeight: 600, letterSpacing: 1.4, lineHeight: 15 })
|
|
79
|
+
|
|
80
|
+
const sheet = k.sheet
|
|
81
|
+
const { width: W, height: H } = k.node(sheet)
|
|
82
|
+
k.flowSheet(sheet, 0, PAD, 'paper', 'MIN', 'CENTER')
|
|
83
|
+
draw(k, sheet, art(W, H))
|
|
84
|
+
const c = { align: 'CENTER' as const, width: 'FILL' as const }
|
|
85
|
+
k.text(sheet, 'CHAG SAMEACH! COME FOR', { style: 'Kicker 11', color: 'text', ...c, name: 'Kicker' })
|
|
86
|
+
k.stack(sheet, { height: 10, name: 'Gap' })
|
|
87
|
+
k.text(sheet, 'LATKES & LIGHT', { style: 'Title 52', color: 'text', ...c, name: 'Title' })
|
|
88
|
+
k.text(sheet, 'a Hanukkah party on the second night', { style: 'Sub 22', color: 'onPaper', ...c, name: 'Sub' })
|
|
89
|
+
k.stack(sheet, { height: 14, name: 'Gap' })
|
|
90
|
+
flourishComponent(k, 'Dot rule', 'onPaper', disc(0, 0, 4), { rule: 30, size: 6 })
|
|
91
|
+
k.instance(sheet, 'Dot rule', {}, { name: 'Divider' })
|
|
92
|
+
k.stack(sheet, { height: 14, name: 'Gap' })
|
|
93
|
+
k.text(sheet, 'Saturday 5 December · 5:30 pm', { style: 'When 15', color: 'text', ...c, name: 'When' })
|
|
94
|
+
k.text(sheet, 'at {{brand.name}}’s, 22 Maple Row, Brookline', { style: 'Body 12', color: 'text', ...c, name: 'Where' })
|
|
95
|
+
k.stack(sheet, { height: 10, name: 'Gap' })
|
|
96
|
+
k.text(sheet, 'We light the candles at sundown, then it’s latkes three ways, sufganiyot and a cutthroat dreidel tournament. Bring your own menorah if you like.', { style: 'Body 12', color: 'text', ...c, name: 'Body' })
|
|
97
|
+
k.stack(sheet, { height: 12, name: 'Gap' })
|
|
98
|
+
k.text(sheet, 'RSVP TO DANA · (617) 555-0193', { style: 'Rsvp 10', color: 'text', ...c, name: 'Rsvp' })
|
|
99
|
+
|
|
100
|
+
k.textStyle('Title 43', { size: 41, fontWeight: 900, letterSpacing: -1.4, lineHeight: 42 })
|
|
101
|
+
k.textStyle('Sub 18', { ...SERIF, size: 18, fontWeight: 400, fontStyle: 'italic', lineHeight: 23 })
|
|
102
|
+
k.textStyle('When 13', { size: 13, fontWeight: 700, lineHeight: 18 })
|
|
103
|
+
k.textStyle('Body 10.5', { ...SERIF, size: 10.5, fontWeight: 400, lineHeight: 15 })
|
|
104
|
+
k.textStyle('Rsvp 8.5', { size: 8.5, fontWeight: 600, letterSpacing: 1.1, lineHeight: 13 })
|
|
105
|
+
k.textStyle('Kicker 9.5', { size: 9.5, fontWeight: 700, letterSpacing: 2.8, lineHeight: 14 })
|
|
106
|
+
k.variants(sheet, [printVariant('a6', {
|
|
107
|
+
art, pad: PAD, masterWidth: W,
|
|
108
|
+
styles: [['Title', 'Title 43'], ['Sub', 'Sub 18'], ['When', 'When 13'], ['Where', 'Body 10.5'], ['Body', 'Body 10.5'], ['Rsvp', 'Rsvp 8.5'], ['Kicker', 'Kicker 9.5']],
|
|
109
|
+
})])
|
|
110
|
+
return k.finish()
|
|
111
|
+
},
|
|
112
|
+
})
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// A restaurant's menu for the Lunar New Year's Eve reunion dinner: a deep red card in a fine gold double frame, two
|
|
2
|
+
// round red lanterns with gold ribs and tassels hanging from the top corners, a branch of plum blossom reaching
|
|
3
|
+
// across, auspicious clouds at the foot, and the eight banquet dishes each with what it wishes the table (the whole
|
|
4
|
+
// fish for plenty, dumplings for wealth, long noodles for long life). A5 with the frame and lanterns running into
|
|
5
|
+
// the bleed with the drawing worked out again.
|
|
6
|
+
|
|
7
|
+
import { Kit, preset } from '../kit'
|
|
8
|
+
import { defineTemplate } from '../build'
|
|
9
|
+
import { SERIF } from '../shared/type'
|
|
10
|
+
import { blurred } from '../shared/texture'
|
|
11
|
+
import { branch, cloudCurl, coin, disc, doubleRule, draw, flourishComponent, plum, redLantern, type Drawn } from '../themes/holidays-shared'
|
|
12
|
+
|
|
13
|
+
const NAME = 'Lunar New Year banquet menu'
|
|
14
|
+
const PAD = { top: 212, right: 60, bottom: 92, left: 60 }
|
|
15
|
+
|
|
16
|
+
const DISHES = [
|
|
17
|
+
['Prosperity salad, tossed at the table', 'Lo hei: toss it high for a good year'],
|
|
18
|
+
['Crystal prawn and chive dumplings', 'Shaped like silver ingots, for wealth'],
|
|
19
|
+
['Golden spring rolls', 'Bars of gold'],
|
|
20
|
+
['Whole steamed sea bass, ginger and scallion', 'Plenty, with some to spare'],
|
|
21
|
+
['Braised e-fu longevity noodles', 'Long noodles for long life: no cutting'],
|
|
22
|
+
['Soy-glazed duck with lotus leaf rice', 'A table that stays together'],
|
|
23
|
+
['Stir-fried greens with garlic', 'A fresh start'],
|
|
24
|
+
['Sesame tangyuan in ginger syrup, and nian gao', 'Reunion, and rising higher every year'],
|
|
25
|
+
] as const
|
|
26
|
+
|
|
27
|
+
function art(w: number, h: number): Drawn[] {
|
|
28
|
+
const s = w / 559, B = 12 * s
|
|
29
|
+
const l1 = redLantern(92 * s, -B, 118 * s, 48 * s), l2 = redLantern(w - 92 * s, -B, 96 * s, 28 * s)
|
|
30
|
+
const br = branch([{ x: w + B, y: 168 * s }, { x: w - 120 * s, y: 150 * s }, { x: w - 230 * s, y: 172 * s }, { x: w - 330 * s, y: 154 * s }], 6, 5, 50 * s)
|
|
31
|
+
const blossoms = br.ends.slice(0, 9).map((p, i) => plum(p.x, p.y, (i % 3 ? 10 : 14) * s, i))
|
|
32
|
+
const cloudL = cloudCurl(24 * s, h - 44 * s, 130 * s, 1), cloudR = cloudCurl(w - 24 * s, h - 40 * s, 116 * s, -1)
|
|
33
|
+
const coins = [coin(w / 2 - 36 * s, h - 40 * s, 12 * s), coin(w / 2, h - 44 * s, 14 * s), coin(w / 2 + 36 * s, h - 40 * s, 12 * s)]
|
|
34
|
+
const lanterns = [l1, l2]
|
|
35
|
+
return [
|
|
36
|
+
{ name: 'Frame', paths: doubleRule(w, h, 18 * s, 6 * s), color: 'gold', o: { strokeWidth: 1 * s } },
|
|
37
|
+
{ name: 'Branch', paths: br.limb, color: 'bark', o: { strokeWidth: 5 * s } },
|
|
38
|
+
{ name: 'Twigs', paths: br.twigs, color: 'bark', o: { strokeWidth: 2.4 * s } },
|
|
39
|
+
{ name: 'Blossoms', paths: blossoms.flatMap(b => b.petals), color: 'blossom' },
|
|
40
|
+
{ name: 'Blossom hearts', paths: blossoms.flatMap(b => b.heart), color: 'gold' },
|
|
41
|
+
{ name: 'Lantern glow', paths: lanterns.flatMap(l => l.body), color: 'glow', o: { opacity: 0.45, effects: [blurred(16 * s)] } },
|
|
42
|
+
{ name: 'Lantern cords', paths: lanterns.flatMap(l => l.cord), color: 'gold', o: { strokeWidth: 1.4 * s } },
|
|
43
|
+
{ name: 'Lanterns', paths: lanterns.flatMap(l => l.body), color: 'lantern' },
|
|
44
|
+
{ name: 'Lantern ribs', paths: lanterns.flatMap(l => l.ribs), color: 'gold', o: { strokeWidth: 1.4 * s } },
|
|
45
|
+
{ name: 'Lantern caps', paths: lanterns.flatMap(l => l.caps), color: 'gold' },
|
|
46
|
+
{ name: 'Tassels', paths: lanterns.flatMap(l => l.tassel), color: 'gold' },
|
|
47
|
+
{ name: 'Clouds', paths: cloudL, color: 'gold', o: { strokeWidth: 2.4 * s } },
|
|
48
|
+
{ name: 'Clouds', paths: cloudR, color: 'gold', o: { strokeWidth: 2.4 * s } },
|
|
49
|
+
{ name: 'Coins', paths: coins.flatMap(c => c.coin), color: 'gold' },
|
|
50
|
+
{ name: 'Coin holes', paths: coins.flatMap(c => c.hole), color: 'paperRed' },
|
|
51
|
+
{ name: 'Coin rims', paths: coins.flatMap(c => c.rim), color: 'goldDeep', o: { strokeWidth: 1 * s } },
|
|
52
|
+
{ name: 'Dots', paths: [disc(w / 2 - 62 * s, h - 42 * s, 2.4 * s), disc(w / 2 + 62 * s, h - 42 * s, 2.4 * s)], color: 'gold' },
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default defineTemplate({
|
|
57
|
+
id: 'print-holidays-lunar-new-year-banquet-menu',
|
|
58
|
+
meta: {
|
|
59
|
+
name: NAME,
|
|
60
|
+
description: 'A restaurant menu for the Lunar New Year’s Eve reunion dinner: a deep red card in a fine gold double frame, round red lanterns with gold ribs, a branch of plum blossom, auspicious clouds and coins at the foot, and eight banquet dishes each with the wish it brings the table. A5 with bleed',
|
|
61
|
+
category: 'menu', tags: ['lunar new year', 'chinese new year', 'spring festival', 'reunion dinner', 'banquet', 'restaurant', 'menu', 'lanterns', 'plum blossom', 'red and gold', 'year of the goat', 'small business', 'print'],
|
|
62
|
+
platforms: ['print'], formats: ['card'], useCases: ['menu', 'event'],
|
|
63
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
64
|
+
},
|
|
65
|
+
stableNumbers: true,
|
|
66
|
+
build() {
|
|
67
|
+
const k = new Kit(NAME, preset('a5'))
|
|
68
|
+
k.quantizeGeometry = true
|
|
69
|
+
k.brand(
|
|
70
|
+
{ paper: '#A3141E', text: '#FFF1D8', onPaper: '#F4C95D', primary: '#F4C95D', onPrimary: '#A3141E' },
|
|
71
|
+
{ name: 'Golden Lotus' },
|
|
72
|
+
)
|
|
73
|
+
k.palette({ gold: '#E9BC52', goldDeep: '#B0852A', lantern: '#D8242F', glow: '#FF7A45', blossom: '#F7C6CF', bark: '#4A1A12', paperRed: '#A3141E' })
|
|
74
|
+
k.textStyle('Kicker 11', { size: 11, fontWeight: 700, letterSpacing: 3.4, lineHeight: 16 })
|
|
75
|
+
k.textStyle('Title 40', { ...SERIF, size: 40, fontWeight: 700, letterSpacing: -0.6, lineHeight: 44 })
|
|
76
|
+
k.textStyle('Greeting 15', { ...SERIF, size: 15, fontWeight: 400, fontStyle: 'italic', lineHeight: 20 })
|
|
77
|
+
k.textStyle('Dish 14', { ...SERIF, size: 14, fontWeight: 600, lineHeight: 18 })
|
|
78
|
+
k.textStyle('Wish 10.5', { ...SERIF, size: 10.5, fontWeight: 400, fontStyle: 'italic', lineHeight: 14 })
|
|
79
|
+
k.textStyle('Price 11', { size: 11, fontWeight: 700, letterSpacing: 1.2, lineHeight: 16 })
|
|
80
|
+
|
|
81
|
+
const DW = Math.round(k.node(k.sheet).width - PAD.left - PAD.right)
|
|
82
|
+
k.component('Dish', { gap: 1, width: DW, align: 'CENTER', props: { Dish: 'Dish', Wish: 'What it wishes you' } }, id => {
|
|
83
|
+
k.propText(id, 'Dish', { style: 'Dish 14', color: 'text', align: 'CENTER', width: 'FILL', name: 'Dish name' })
|
|
84
|
+
k.propText(id, 'Wish', { style: 'Wish 10.5', color: 'onPaper', align: 'CENTER', width: 'FILL', name: 'Wish' })
|
|
85
|
+
})
|
|
86
|
+
flourishComponent(k, 'Coin rule', 'onPaper', disc(0, 0, 6), { rule: 40, size: 8 })
|
|
87
|
+
|
|
88
|
+
const sheet = k.sheet
|
|
89
|
+
const { width: W, height: H } = k.node(sheet)
|
|
90
|
+
k.flowSheet(sheet, 0, PAD, 'paper', 'MIN', 'CENTER')
|
|
91
|
+
draw(k, sheet, art(W, H))
|
|
92
|
+
const c = { align: 'CENTER' as const, width: 'FILL' as const }
|
|
93
|
+
k.text(sheet, 'FRIDAY 5 FEBRUARY · NEW YEAR’S EVE', { style: 'Kicker 11', color: 'onPaper', ...c, name: 'Kicker' })
|
|
94
|
+
k.stack(sheet, { height: 6, name: 'Gap' })
|
|
95
|
+
k.text(sheet, 'Reunion Dinner', { style: 'Title 40', color: 'text', ...c, name: 'Title' })
|
|
96
|
+
k.text(sheet, 'Gong hei fat choy, and welcome to the Year of the Goat', { style: 'Greeting 15', color: 'onPaper', ...c, name: 'Greeting' })
|
|
97
|
+
k.stack(sheet, { height: 14, name: 'Gap' })
|
|
98
|
+
k.instance(sheet, 'Coin rule', {}, { name: 'Divider' })
|
|
99
|
+
k.stack(sheet, { height: 14, name: 'Gap' })
|
|
100
|
+
for (const [i, [dish, wish]] of DISHES.entries()) {
|
|
101
|
+
k.instance(sheet, 'Dish', { Dish: dish, Wish: wish }, { name: 'Dish', width: 'FILL' })
|
|
102
|
+
if (i < DISHES.length - 1) k.stack(sheet, { height: 6, name: 'Gap' })
|
|
103
|
+
}
|
|
104
|
+
k.stack(sheet, { height: 16, name: 'Gap' })
|
|
105
|
+
k.text(sheet, '$388 PER TABLE OF TEN · SEATINGS AT 5:30 AND 8', { style: 'Price 11', color: 'onPaper', ...c, name: 'Price' })
|
|
106
|
+
k.text(sheet, '{{brand.name}}, 136-20 41st Avenue, Flushing', { style: 'Wish 10.5', color: 'text', ...c, name: 'Address' })
|
|
107
|
+
|
|
108
|
+
return k.finish()
|
|
109
|
+
},
|
|
110
|
+
})
|