@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,246 @@
|
|
|
1
|
+
// A parish newsletter for Advent and Christmas, two sides of US Letter to print back to back and leave at the door.
|
|
2
|
+
// Side one: a night-blue banner across the top with the star over the stable and the hills cut like a woodcut,
|
|
3
|
+
// the masthead, the vicar's letter beside a box of every Christmas service, and the giving tree. Side two: the
|
|
4
|
+
// month's diary, the Christmas Day lunch for neighbours who would otherwise eat alone, carol singing round the
|
|
5
|
+
// streets, a children's corner and the notices. The banner and the second side's band run into the bleed; every
|
|
6
|
+
// word sits inside the half-inch margin. Service times and diary dates are components, so a new row is one instance.
|
|
7
|
+
|
|
8
|
+
import { Kit, preset } from '../kit'
|
|
9
|
+
import { defineTemplate } from '../build'
|
|
10
|
+
import { SERIF } from '../shared/type'
|
|
11
|
+
import type { VectorPath } from '@/lib/document/types'
|
|
12
|
+
import { disc, draw, rng, flame, flourishComponent, pine, polyP, rectPath, specksIn, starPath, type Drawn, type P } from '../themes/holidays-shared'
|
|
13
|
+
|
|
14
|
+
const NAME = 'Christmas church newsletter'
|
|
15
|
+
const W = 816, H = 1056, B = 12, M = 48
|
|
16
|
+
|
|
17
|
+
const SERVICES = [
|
|
18
|
+
['Sun 29 Nov', '10:00', 'Advent Sunday Eucharist, with the lighting of the first candle'],
|
|
19
|
+
['Sun 13 Dec', '18:00', 'Nine Lessons and Carols by candlelight, with the choir'],
|
|
20
|
+
['Sun 20 Dec', '16:00', 'Christingle for families, with oranges and candles'],
|
|
21
|
+
['Thu 24 Dec', '16:00', 'Crib service. Come dressed as a shepherd, a sheep or a star'],
|
|
22
|
+
['Thu 24 Dec', '23:30', 'Midnight Mass'],
|
|
23
|
+
['Fri 25 Dec', '10:00', 'Christmas Day family communion (about 50 minutes)'],
|
|
24
|
+
] as const
|
|
25
|
+
|
|
26
|
+
const DIARY = [
|
|
27
|
+
['Wed 2', 'Giving tree goes up in the porch: take a tag, bring the gift back by 13 Dec'],
|
|
28
|
+
['Sat 5', 'Christmas fair, 10–2 in the hall. Cakes, books, a raffle and Santa'],
|
|
29
|
+
['Tue 8', 'Wreath-making with the flower team, 7 pm, greenery and cookies provided'],
|
|
30
|
+
['Fri 11', 'Riverside Elementary nativity in church, 2 pm (all welcome)'],
|
|
31
|
+
['Sat 19', 'Carol singing round the streets, meet on the church steps at 5 pm'],
|
|
32
|
+
['Mon 21', 'Hall decorated for Christmas Day lunch, 10 am, many hands needed'],
|
|
33
|
+
['Sun 27', 'One said service at 10:00, then the hall is closed until 4 Jan'],
|
|
34
|
+
] as const
|
|
35
|
+
|
|
36
|
+
/** The night banner: rays from the star, the star, the stable's roof and the hills, cut like a woodcut. */
|
|
37
|
+
function banner(w: number, top: number, h: number): Drawn[] {
|
|
38
|
+
const sx = w * 0.82, sy = top + h * 0.28
|
|
39
|
+
const rays: VectorPath[] = []
|
|
40
|
+
for (let i = 0; i < 16; i++) {
|
|
41
|
+
const a = (i * Math.PI) / 8, long = i === 4 ? h * 0.4 : i === 12 ? h * 0.6 : i % 4 === 0 ? h * 0.3 : h * 0.2
|
|
42
|
+
const p = (r: number, da: number): P => ({ x: sx + Math.cos(a + da) * r, y: sy + Math.sin(a + da) * r })
|
|
43
|
+
rays.push(polyP([p(8, -0.18), p(long, 0), p(8, 0.18)]))
|
|
44
|
+
}
|
|
45
|
+
const ground = top + h
|
|
46
|
+
const hills = (y: number, amp: number, phase: number) => polyP([{ x: -B, y: ground }, ...Array.from({ length: 21 }, (_, i) => ({ x: -B + ((w + 2 * B) * i) / 20, y: y + Math.sin(i * 0.55 + phase) * amp + Math.sin(i * 1.3 + phase) * amp * 0.3 })), { x: w + B, y: ground }])
|
|
47
|
+
// The stable: a low roof on posts, its door a warm square of light.
|
|
48
|
+
const st = { x: w * 0.82 - w * 0.065, y: ground - h * 0.3, w: w * 0.13 }
|
|
49
|
+
const stable = [polyP([{ x: st.x - 14, y: st.y }, { x: st.x + st.w / 2, y: st.y - h * 0.13 }, { x: st.x + st.w + 14, y: st.y }]), rectPath(st.x, st.y, st.w, h * 0.22)]
|
|
50
|
+
const door = [rectPath(st.x + st.w * 0.36, st.y + h * 0.06, st.w * 0.28, h * 0.16)]
|
|
51
|
+
const star = (list: number[][]) => list.map(([x, y], i) => starPath(w * x, top + h * y, i % 3 ? 3.2 : 5, i % 3 ? 1.3 : 2))
|
|
52
|
+
const trees = [0.08, 0.12, 0.9, 0.94].map((x, i) => polyP([{ x: w * x, y: ground - h * (0.36 + (i % 2) * 0.06) }, { x: w * x + 16, y: ground - h * 0.12 }, { x: w * x - 16, y: ground - h * 0.12 }]))
|
|
53
|
+
return [
|
|
54
|
+
{ name: 'Night', paths: [rectPath(-B, -B, w + 2 * B, top + h + B - B)], color: 'night' },
|
|
55
|
+
{ name: 'Rays', paths: rays, color: 'gold', o: { opacity: 0.85 } },
|
|
56
|
+
{ name: 'Star', paths: [starPath(sx, sy, 20, 7, 8)], color: 'cream' },
|
|
57
|
+
{ name: 'Stars', paths: star([[0.88, 0.18], [0.95, 0.46], [0.62, 0.1], [0.82, 0.6], [0.58, 0.5], [0.66, 0.66]]), color: 'cream', o: { opacity: 0.8 } },
|
|
58
|
+
{ name: 'Stars', paths: star([[0.06, 0.05], [0.22, 0.07], [0.42, 0.04], [0.32, 0.09]]), color: 'cream', o: { opacity: 0.8 } },
|
|
59
|
+
{ name: 'Stars', paths: star([[0.06, 0.6], [0.24, 0.56], [0.38, 0.62], [0.48, 0.54]]), color: 'cream', o: { opacity: 0.8 } },
|
|
60
|
+
{ name: 'Far hills', paths: [hills(ground - h * 0.3, 10, 0.4)], color: 'hillFar' },
|
|
61
|
+
{ name: 'Trees', paths: trees, color: 'hillNear' },
|
|
62
|
+
{ name: 'Stable', paths: stable, color: 'hillNear' },
|
|
63
|
+
{ name: 'Door light', paths: door, color: 'gold' },
|
|
64
|
+
{ name: 'Near hills', paths: [hills(ground - h * 0.12, 7, 2.1)], color: 'hillNear' },
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** An Advent wreath seen a little from above, centred on (cx, cy): the evergreen ring, three purple candles and a rose one, the white one in the middle, the first lit. */
|
|
69
|
+
function wreath(cx: number, cy: number): Drawn[] {
|
|
70
|
+
const rx = 84, ry = 26
|
|
71
|
+
const ring = Array.from({ length: 22 }, (_, i) => {
|
|
72
|
+
const a = (i * 2 * Math.PI) / 22, p = { x: cx + Math.cos(a) * rx, y: cy + Math.sin(a) * ry }
|
|
73
|
+
return pine({ x: p.x - Math.sin(a) * 14, y: p.y + Math.cos(a) * 5 }, { x: p.x, y: p.y - 4 }, { x: p.x + Math.sin(a) * 16, y: p.y - Math.cos(a) * 6 }, { needle: 11, seed: 60 + i, density: 0.35 })
|
|
74
|
+
})
|
|
75
|
+
const candle = (a: number, hgt: number) => { const x = cx + Math.cos(a) * rx * 0.86, y = cy + Math.sin(a) * ry * 0.86; return { body: rectPath(x - 7, y - hgt, 14, hgt), x, top: y - hgt } }
|
|
76
|
+
const back = [candle(-Math.PI * 0.88, 56), candle(-Math.PI * 0.38, 54)], front = [candle(Math.PI * 0.62, 62), candle(Math.PI * 0.12, 60)]
|
|
77
|
+
const centre = { body: rectPath(cx - 8, cy - 86, 16, 86), x: cx, top: cy - 86 }
|
|
78
|
+
return [
|
|
79
|
+
{ name: 'Wreath back', paths: ring.slice(11).flatMap(r => [...r.dark, ...r.light]), color: 'pine', o: { strokeWidth: 2 } },
|
|
80
|
+
{ name: 'Candles back', paths: back.map(c => c.body), color: 'purple' },
|
|
81
|
+
{ name: 'Christ candle', paths: [centre.body], color: 'wax', o: { stroke: { color: 'line', width: 1 } } },
|
|
82
|
+
{ name: 'Wreath front', paths: ring.slice(0, 11).flatMap(r => [...r.dark, ...r.light]), color: 'pineLight', o: { strokeWidth: 2 } },
|
|
83
|
+
{ name: 'Candles front', paths: [front[1].body], color: 'purple' },
|
|
84
|
+
{ name: 'Rose candle', paths: [front[0].body], color: 'rose' },
|
|
85
|
+
{ name: 'Berries', paths: [0.3, 1.1, 1.9, 2.6, 3.5, 4.4, 5.2].map(a => disc(cx + Math.cos(a) * rx, cy + Math.sin(a) * ry + 2, 3.2)), color: 'primary' },
|
|
86
|
+
{ name: 'Flame', paths: [flame(front[1].x, front[1].top - 2, 10, 22)], color: 'gold' },
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Riverside at night: rooftops with snow on them and lit windows, the church's spire with its star, from `top` down past the trim. */
|
|
91
|
+
function street(w: number, top: number, h: number): Drawn[] {
|
|
92
|
+
const r = rng(77)
|
|
93
|
+
const houses: VectorPath[] = [], snow: VectorPath[] = [], windows: VectorPath[] = []
|
|
94
|
+
let x = -B
|
|
95
|
+
while (x < w + B) {
|
|
96
|
+
const hw = 70 + r() * 60, hh = h * (0.32 + r() * 0.16), roof = 26 + r() * 22, y = top + h - hh
|
|
97
|
+
if (Math.abs(x + hw / 2 - w * 0.5) < 80) { x += 90; continue }
|
|
98
|
+
houses.push(polyP([{ x, y: top + h + B }, { x, y }, { x: x + hw / 2, y: y - roof }, { x: x + hw, y }, { x: x + hw, y: top + h + B }]))
|
|
99
|
+
snow.push(polyP([{ x: x - 4, y: y + 2 }, { x: x + hw / 2, y: y - roof - 3 }, { x: x + hw + 4, y: y + 2 }, { x: x + hw / 2, y: y - roof + 6 }]))
|
|
100
|
+
for (let i = 0; i < 2; i++) if (r() > 0.3) windows.push(rectPath(x + hw * (0.22 + i * 0.36), y + hh * 0.22, hw * 0.18, hw * 0.2))
|
|
101
|
+
x += hw + 6 + r() * 10
|
|
102
|
+
}
|
|
103
|
+
const cx = w * 0.5, base = top + h + B
|
|
104
|
+
const church = [rectPath(cx - 70, top + h * 0.42, 140, h * 0.58 + B), polyP([{ x: cx - 80, y: top + h * 0.43 }, { x: cx, y: top + h * 0.2 }, { x: cx + 80, y: top + h * 0.43 }]), rectPath(cx - 20, top - h * 0.02, 40, h * 0.5), polyP([{ x: cx - 24, y: top }, { x: cx, y: top - h * 0.3 }, { x: cx + 24, y: top }])]
|
|
105
|
+
const glass = [{ closed: true, points: [{ x: cx - 16, y: base - B - 10 }, { x: cx - 16, y: top + h * 0.62 }, { x: cx, y: top + h * 0.52 }, { x: cx + 16, y: top + h * 0.62 }, { x: cx + 16, y: base - B - 10 }] } as VectorPath, rectPath(cx - 6, top + h * 0.04, 12, 22)]
|
|
106
|
+
return [
|
|
107
|
+
{ name: 'Street sky', paths: [rectPath(-B, top - h * 0.36, w + 2 * B, h * 1.36 + B)], color: 'night' },
|
|
108
|
+
{ name: 'Street stars', paths: [0.6, 0.7, 0.8, 0.9, 0.96].map((f, i) => starPath(w * f, top - h * (0.22 - (i % 2) * 0.1), 4, 1.6)), color: 'cream', o: { opacity: 0.8 } },
|
|
109
|
+
{ name: 'Spire star', paths: [starPath(cx, top - h * 0.36 + 2, 9, 3.4, 8)], color: 'gold' },
|
|
110
|
+
{ name: 'Houses', paths: houses, color: 'hillFar' },
|
|
111
|
+
{ name: 'Roof snow', paths: snow, color: 'cream', o: { opacity: 0.85 } },
|
|
112
|
+
{ name: 'Windows', paths: windows, color: 'gold' },
|
|
113
|
+
{ name: 'Church', paths: church, color: 'hillNear' },
|
|
114
|
+
{ name: 'Church windows', paths: glass, color: 'gold' },
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export default defineTemplate({
|
|
119
|
+
id: 'print-holidays-christmas-church-newsletter',
|
|
120
|
+
meta: {
|
|
121
|
+
name: NAME,
|
|
122
|
+
description: 'A two-sided parish newsletter for Advent and Christmas on US Letter: a woodcut night banner of the star over the stable, the vicar’s letter beside every Christmas service, the giving tree, the month’s diary, a Christmas Day lunch for neighbours, carol singing, a children’s corner and notices. Service times and diary dates are components; the banner runs into the bleed',
|
|
123
|
+
category: 'newspaper', tags: ['christmas', 'advent', 'church', 'parish', 'newsletter', 'services', 'carols', 'nativity', 'community', 'congregation', 'bulletin', 'print'],
|
|
124
|
+
platforms: ['print'], formats: ['document'], useCases: ['newsletter', 'event'],
|
|
125
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
126
|
+
},
|
|
127
|
+
sequence: true,
|
|
128
|
+
stableNumbers: true,
|
|
129
|
+
build() {
|
|
130
|
+
const k = new Kit(NAME, preset('us-letter'))
|
|
131
|
+
k.quantizeGeometry = true
|
|
132
|
+
k.brand(
|
|
133
|
+
{ paper: '#F6F0E3', text: '#1D2433', onPaper: '#8C2B2B', primary: '#8C2B2B', onPrimary: '#FFF8EC', line: '#CBBFA6', card: '#EDE3CF', onCard: '#1D2433' },
|
|
134
|
+
{ name: 'St Columba’s, Riverside', site: 'stcolumbariverside.org', phone: '(503) 555-0172' },
|
|
135
|
+
)
|
|
136
|
+
k.palette({ wax: '#FFFDF6', purple: '#5B3A7A', rose: '#D88FA6', pine: '#2F5A43', pineLight: '#4F7D5E', night: '#18233A', gold: '#E2B95A', cream: '#FFF8EC', hillFar: '#2A3A5A', hillNear: '#0F1626', tint: '#EDE3CF', fibre: '#B9A984' })
|
|
137
|
+
k.textStyle('Mast 64', { ...SERIF, size: 64, fontWeight: 700, letterSpacing: -1.6, lineHeight: 64 })
|
|
138
|
+
k.textStyle('Mast sub 12', { size: 12, fontWeight: 600, letterSpacing: 2.4, lineHeight: 16 })
|
|
139
|
+
k.textStyle('Parish 17', { ...SERIF, size: 17, fontWeight: 400, fontStyle: 'italic', lineHeight: 22 })
|
|
140
|
+
k.textStyle('Head 24', { ...SERIF, size: 24, fontWeight: 700, letterSpacing: -0.3, lineHeight: 28 })
|
|
141
|
+
k.textStyle('Head 18', { ...SERIF, size: 18, fontWeight: 700, lineHeight: 22 })
|
|
142
|
+
k.textStyle('Kicker 10', { size: 10, fontWeight: 700, letterSpacing: 2, lineHeight: 14 })
|
|
143
|
+
k.textStyle('Body 12', { ...SERIF, size: 12, fontWeight: 400, lineHeight: 17.5 })
|
|
144
|
+
k.textStyle('Body i 12', { ...SERIF, size: 12, fontWeight: 400, fontStyle: 'italic', lineHeight: 17.5 })
|
|
145
|
+
k.textStyle('Row 11', { size: 11, fontWeight: 600, lineHeight: 15, fontFeatures: { tnum: 1 } })
|
|
146
|
+
k.textStyle('Row text 11', { ...SERIF, size: 11.5, fontWeight: 400, lineHeight: 15 })
|
|
147
|
+
k.textStyle('Foot 10', { size: 10, fontWeight: 500, letterSpacing: 0.6, lineHeight: 14 })
|
|
148
|
+
|
|
149
|
+
flourishComponent(k, 'Star rule', 'primary', starPath(0, 0, 10, 4.2), { rule: 40 })
|
|
150
|
+
// A service: day, time, and what it is.
|
|
151
|
+
k.component('Service', { direction: 'HORIZONTAL', gap: 10, width: 300, padding: { top: 7, right: 0, bottom: 7, left: 0 }, props: { Day: 'Sun 1 Dec', Time: '10:00', What: 'Service' } }, id => {
|
|
152
|
+
const when = k.stack(id, { width: 74, gap: 1, name: 'When' })
|
|
153
|
+
k.propText(when, 'Day', { style: 'Row 11', color: 'onCard', name: 'Day' })
|
|
154
|
+
k.propText(when, 'Time', { style: 'Row 11', color: 'onCard', name: 'Time' })
|
|
155
|
+
k.propText(id, 'What', { style: 'Row text 11', color: 'onCard', width: 'FILL', name: 'What' })
|
|
156
|
+
})
|
|
157
|
+
// A diary date.
|
|
158
|
+
k.component('Diary date', { direction: 'HORIZONTAL', gap: 12, width: 440, padding: { top: 6, right: 0, bottom: 6, left: 0 }, props: { Date: 'Mon 1', What: 'What is on' } }, id => {
|
|
159
|
+
k.propText(id, 'Date', { style: 'Row 11', color: 'onPaper', width: 52, name: 'Date' } as never)
|
|
160
|
+
k.propText(id, 'What', { style: 'Row text 11', color: 'text', width: 'FILL', name: 'What' })
|
|
161
|
+
})
|
|
162
|
+
const sectionHead = (parent: string, kicker: string, title: string, style = 'Head 24', on: 'paper' | 'card' = 'paper') => {
|
|
163
|
+
k.text(parent, kicker, { style: 'Kicker 10', color: on === 'card' ? 'onCard' : 'onPaper', width: 'FILL', name: 'Kicker' })
|
|
164
|
+
k.text(parent, title, { style, color: on === 'card' ? 'onCard' : 'text', width: 'FILL', name: 'Heading' })
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Side one ──
|
|
168
|
+
const one = k.sheet
|
|
169
|
+
k.nameSheet(one, 'Side one')
|
|
170
|
+
const BANNER = 300
|
|
171
|
+
k.flowSheet(one, 18, { top: 36, right: M, bottom: M, left: M }, 'paper', 'MIN', 'MIN')
|
|
172
|
+
draw(k, one, banner(W, 0, BANNER))
|
|
173
|
+
const mast = k.stack(one, { width: 'FILL', height: BANNER - 36, gap: 6, name: 'Masthead' })
|
|
174
|
+
k.text(mast, 'ADVENT & CHRISTMAS 2026 · ISSUE 48', { style: 'Mast sub 12', color: 'gold', autoWidth: true, name: 'Issue' })
|
|
175
|
+
k.text(mast, 'The Lamplighter', { style: 'Mast 64', color: 'cream', autoWidth: true, name: 'Title' })
|
|
176
|
+
k.text(mast, 'News and services from {{brand.name}}', { style: 'Parish 17', color: 'cream', autoWidth: true, name: 'Parish' })
|
|
177
|
+
const cols = k.stack(one, { direction: 'HORIZONTAL', gap: 30, width: 'FILL', name: 'Columns' })
|
|
178
|
+
const left = k.stack(cols, { width: 'FILL', gap: 10, name: 'Letter' })
|
|
179
|
+
sectionHead(left, 'FROM THE RECTORY', 'Make room')
|
|
180
|
+
for (const [i, p] of [
|
|
181
|
+
'Every year somebody asks me why we bother with Advent when the shops have had Christmas since Halloween. My answer is that Advent is the season of making room: in the diary, in the house, in ourselves, for a child who arrived to a town with no space for him.',
|
|
182
|
+
'This December, I want to ask each of us to make a little room for someone else. Take a tag from the giving tree. Bring a neighbour to the carol service, especially one who has not set foot in church since their own school nativity. Come and peel potatoes on Christmas morning, and stay for lunch.',
|
|
183
|
+
'The candle we light on Advent Sunday is small. By Christmas there are five, and the church is full of light. That is how it works: one at a time, and together. I look forward to seeing you, and to meeting whoever you bring.',
|
|
184
|
+
].entries()) k.text(left, p, { style: i === 2 ? 'Body 12' : 'Body 12', color: 'text', width: 'FILL', name: 'Paragraph' })
|
|
185
|
+
k.text(left, 'The Rev. Ruth Adeyemi, Rector', { style: 'Body i 12', color: 'text', width: 'FILL', name: 'Signature' })
|
|
186
|
+
k.instance(left, 'Star rule', {}, { name: 'Divider' })
|
|
187
|
+
sectionHead(left, 'THE GIVING TREE', 'Two hundred tags, two hundred gifts', 'Head 18')
|
|
188
|
+
k.text(left, 'Each tag on the tree in the porch names a gift asked for by a family the Riverside Food Bank supports: a winter coat, a football, a set of felt-tips. Take a tag, wrap the gift, tie the tag on, and bring it back by Sunday 13 December. Last year all 184 went.', { style: 'Body 12', color: 'text', width: 'FILL', name: 'Paragraph' })
|
|
189
|
+
|
|
190
|
+
const right = k.stack(cols, { width: 300, gap: 4, padding: 18, fill: 'card', name: 'Services box' })
|
|
191
|
+
sectionHead(right, 'COME AND CELEBRATE', 'Christmas services', 'Head 18', 'card')
|
|
192
|
+
k.stack(right, { height: 4, name: 'Gap' })
|
|
193
|
+
for (const [day, time, what] of SERVICES) {
|
|
194
|
+
k.instance(right, 'Service', { Day: day, Time: time, What: what }, { name: 'Service', width: 'FILL' })
|
|
195
|
+
k.rect(right, 'FILL', 1, 'line', { name: 'Hairline' })
|
|
196
|
+
}
|
|
197
|
+
k.stack(right, { height: 6, name: 'Gap' })
|
|
198
|
+
k.text(right, 'Every service has a step-free way in by the north door, a loop for hearing aids and large-print sheets. Children are always welcome, noise and all.', { style: 'Body i 12', color: 'onCard', width: 'FILL', name: 'Access' })
|
|
199
|
+
|
|
200
|
+
// The Advent wreath: the first candle lit, and what each one stands for.
|
|
201
|
+
const wr = k.stack(one, { direction: 'HORIZONTAL', gap: 26, width: 'FILL', padding: { top: 16, right: 20, bottom: 16, left: 12 }, align: 'CENTER', stroke: { color: 'line', width: 1 }, name: 'Wreath feature' })
|
|
202
|
+
const wa = k.stack(wr, { width: 210, height: 150, name: 'Wreath art' })
|
|
203
|
+
draw(k, wa, wreath(105, 112))
|
|
204
|
+
const wt = k.stack(wr, { width: 'FILL', gap: 6, name: 'Wreath text' })
|
|
205
|
+
k.text(wt, 'THE ADVENT WREATH', { style: 'Kicker 10', color: 'onPaper', width: 'FILL', name: 'Kicker' })
|
|
206
|
+
k.text(wt, 'One more candle each Sunday', { style: 'Head 18', color: 'text', width: 'FILL', name: 'Heading' })
|
|
207
|
+
k.text(wt, 'Hope on 29 November, peace on 6 December, joy on the 13th (the rose-pink one), love on the 20th, and the white Christ candle in the middle at midnight on Christmas Eve. Children who would like to light one, see the sacristan before the service.', { style: 'Body 12', color: 'text', width: 'FILL', name: 'Paragraph' })
|
|
208
|
+
|
|
209
|
+
// ── Side two ──
|
|
210
|
+
const two = k.addSheet(preset('us-letter'), 'Side two')
|
|
211
|
+
k.flowSheet(two, 18, { top: 76, right: M, bottom: M, left: M }, 'paper', 'MIN', 'MIN')
|
|
212
|
+
draw(k, two, [
|
|
213
|
+
{ name: 'Band', paths: [rectPath(-B, -B, W + 2 * B, 40 + B)], color: 'night' },
|
|
214
|
+
{ name: 'Band stars', paths: [0.1, 0.3, 0.5, 0.7, 0.9].map(x => starPath(W * x, 20, 6, 2.4, 8)), color: 'gold' },
|
|
215
|
+
...[0, 1].map(i => ({ name: 'Fibre', paths: specksIn({ x: -B, y: 940 + i * 60, w: W + 2 * B, h: 60 }, 900, 30 + i, 1.6), color: 'fibre', o: { opacity: 0.4 } })),
|
|
216
|
+
])
|
|
217
|
+
const row = k.stack(two, { direction: 'HORIZONTAL', gap: 30, width: 'FILL', name: 'Columns' })
|
|
218
|
+
const diary = k.stack(row, { width: 'FILL', gap: 2, name: 'Diary' })
|
|
219
|
+
sectionHead(diary, 'DECEMBER AT ST COLUMBA’S', 'Diary')
|
|
220
|
+
k.stack(diary, { height: 6, name: 'Gap' })
|
|
221
|
+
for (const [d, what] of DIARY) { k.instance(diary, 'Diary date', { Date: d, What: what }, { name: 'Diary date', width: 'FILL' }); k.rect(diary, 'FILL', 1, 'line', { name: 'Hairline' }) }
|
|
222
|
+
const side = k.stack(row, { width: 250, gap: 10, padding: 18, fill: 'primary', name: 'Lunch box' })
|
|
223
|
+
k.text(side, 'CHRISTMAS DAY', { style: 'Kicker 10', color: 'onPrimary', width: 'FILL', name: 'Kicker' })
|
|
224
|
+
k.text(side, 'Lunch for anyone on their own', { style: 'Head 18', color: 'onPrimary', width: 'FILL', name: 'Heading' })
|
|
225
|
+
k.text(side, 'Turkey or nut roast with everything, pie, crackers and carols round the tree, in the hall from 12:30. It is free, and there is a lift there and back. Tell Margaret by 18 December, or tell us about a neighbour and we will ask them.', { style: 'Body 12', color: 'onPrimary', width: 'FILL', name: 'Paragraph' })
|
|
226
|
+
k.text(side, 'Margaret Holt · (503) 555-0139', { style: 'Row 11', color: 'onPrimary', width: 'FILL', name: 'Contact' })
|
|
227
|
+
|
|
228
|
+
k.instance(two, 'Star rule', {}, { name: 'Divider' })
|
|
229
|
+
const row2 = k.stack(two, { direction: 'HORIZONTAL', gap: 30, width: 'FILL', name: 'Columns' })
|
|
230
|
+
const carols = k.stack(row2, { width: 'FILL', gap: 8, name: 'Carols' })
|
|
231
|
+
sectionHead(carols, 'SATURDAY 19 DECEMBER', 'Carols round Riverside', 'Head 18')
|
|
232
|
+
k.text(carols, 'We sing our way down Alder Street to the care home on Birch Lane, where the residents choose the next three. Torches and tambourines welcome. Collecting for the Riverside Food Bank; hot chocolate in the hall afterwards.', { style: 'Body 12', color: 'text', width: 'FILL', name: 'Paragraph' })
|
|
233
|
+
const kids = k.stack(row2, { width: 'FILL', gap: 8, name: 'Kids' })
|
|
234
|
+
sectionHead(kids, 'CHILDREN’S CORNER', 'Find the five stars', 'Head 18')
|
|
235
|
+
k.text(kids, 'Five paper stars are hiding in church this Advent, one on each Sunday. Find them all, tell Mrs. Okoro at the door, and collect a chocolate coin and a sticker for your Christingle.', { style: 'Body 12', color: 'text', width: 'FILL', name: 'Paragraph' })
|
|
236
|
+
const notices = k.stack(two, { width: 'FILL', gap: 6, padding: 16, fill: 'card', name: 'Notices' })
|
|
237
|
+
k.text(notices, 'NOTICES', { style: 'Kicker 10', color: 'onCard', width: 'FILL', name: 'Kicker' })
|
|
238
|
+
k.text(notices, 'The office is closed from 24 December to 4 January; for funerals and emergencies call the rectory. Flowers in memory of loved ones can be given for Christmas: $10 in an envelope marked with their name. The hall heating is fixed, thank you to all who gave.', { style: 'Body 12', color: 'onCard', width: 'FILL', name: 'Paragraph' })
|
|
239
|
+
// The street at night along the foot of the page, the church among the houses, and the footer on it.
|
|
240
|
+
draw(k, two, street(W, H - 250, 250))
|
|
241
|
+
const foot = k.stack(two, { width: 'FILL', height: 'FILL', justify: 'MAX', align: 'MIN', gap: 6, padding: { top: 0, right: 0, bottom: 150, left: 0 }, name: 'Foot' })
|
|
242
|
+
k.text(foot, 'All are welcome, whoever and wherever you are.', { style: 'Parish 17', color: 'cream', width: 320, name: 'Welcome' })
|
|
243
|
+
k.text(foot, '{{brand.name}} · 220 Alder Street, Portland OR 97202\n{{brand.phone}} · {{brand.site}}', { style: 'Foot 10', color: 'cream', width: 320, name: 'Footer' })
|
|
244
|
+
return k.finish()
|
|
245
|
+
},
|
|
246
|
+
})
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// A printed invitation to a family's Christmas Eve open house: gold and red baubles hang on threads from a pine
|
|
2
|
+
// garland that runs off the top edge, over a deep forest-green card dusted with snow, and the invitation is set
|
|
3
|
+
// below in a cream serif with gold small capitals, ending on a sprig of holly. 5 × 7 inches with the garland and
|
|
4
|
+
// threads running into the eighth-inch bleed, and an A6 copy with the drawing worked out again at that size.
|
|
5
|
+
|
|
6
|
+
import { Kit, preset } from '../kit'
|
|
7
|
+
import { defineTemplate } from '../build'
|
|
8
|
+
import { SERIF } from '../shared/type'
|
|
9
|
+
import { bandsAround, bauble, disc, doubleRule, draw, flourishComponent, holly, perBand, pine, printVariant, snowflake, specksIn, sprig, starPath, type Drawn } from '../themes/holidays-shared'
|
|
10
|
+
|
|
11
|
+
const NAME = 'Christmas Eve open house invitation'
|
|
12
|
+
const PAD = { top: 292, right: 48, bottom: 84, left: 48 }
|
|
13
|
+
|
|
14
|
+
/** The garland, baubles, snow and holly for a `w` × `h` card (a 480 px wide card is scale 1). */
|
|
15
|
+
function art(w: number, h: number): Drawn[] {
|
|
16
|
+
const s = w / 480, B = 14 * s
|
|
17
|
+
const garland = [
|
|
18
|
+
pine({ x: -B - 10 * s, y: 18 * s }, { x: w * 0.25, y: 58 * s }, { x: w * 0.52, y: 26 * s }, { needle: 30 * s, seed: 3, density: 1.4 }),
|
|
19
|
+
pine({ x: w + B + 10 * s, y: 14 * s }, { x: w * 0.74, y: 62 * s }, { x: w * 0.46, y: 30 * s }, { needle: 30 * s, seed: 5, density: 1.4 }),
|
|
20
|
+
pine({ x: w * 0.3, y: -B - 6 * s }, { x: w * 0.4, y: 40 * s }, { x: w * 0.6, y: 18 * s }, { needle: 22 * s, seed: 9, density: 0.9 }),
|
|
21
|
+
pine({ x: -B, y: 50 * s }, { x: w * 0.12, y: 76 * s }, { x: w * 0.3, y: 54 * s }, { needle: 24 * s, seed: 12, density: 0.8 }),
|
|
22
|
+
pine({ x: w + B, y: 52 * s }, { x: w * 0.86, y: 80 * s }, { x: w * 0.68, y: 56 * s }, { needle: 24 * s, seed: 14, density: 0.8 }),
|
|
23
|
+
]
|
|
24
|
+
// Red berries and gold beads caught in the garland.
|
|
25
|
+
const beads = [0.06, 0.2, 0.35, 0.5, 0.64, 0.8, 0.94].map((x, i) => disc(w * x, (44 + (i % 2 ? 14 : 2)) * s, (i % 3 ? 4.5 : 6) * s))
|
|
26
|
+
// Baubles on threads of different lengths, big and small, gold and red.
|
|
27
|
+
const hang: [number, number, number, 'gold' | 'red'][] = [[0.13, 70, 34, 'red'], [0.27, 128, 26, 'gold'], [0.42, 64, 22, 'gold'], [0.58, 150, 38, 'red'], [0.74, 82, 28, 'gold'], [0.88, 136, 22, 'red']]
|
|
28
|
+
const gold = hang.filter(b => b[3] === 'gold').map(([x, l, r]) => bauble(w * x, 30 * s, r * s, { string: l * s }))
|
|
29
|
+
const red = hang.filter(b => b[3] === 'red').map(([x, l, r]) => bauble(w * x, 30 * s, r * s, { string: l * s }))
|
|
30
|
+
const all = [...gold, ...red]
|
|
31
|
+
const flake = (list: number[][]) => list.flatMap(([x, y, r], i) => snowflake(w * x, h * y, r * s, i * 0.3))
|
|
32
|
+
const keep = { x: 40 * s, y: 286 * s, w: w - 80 * s, h: h - 330 * s }
|
|
33
|
+
const foot = h - 30 * s
|
|
34
|
+
const hl = [holly({ x: w / 2 - 6 * s, y: foot }, 50 * s, Math.PI + 0.12), holly({ x: w / 2 + 6 * s, y: foot }, 50 * s, -0.12), holly({ x: w / 2 - 4 * s, y: foot + 4 * s }, 36 * s, Math.PI - 0.45), holly({ x: w / 2 + 4 * s, y: foot + 4 * s }, 36 * s, 0.45)]
|
|
35
|
+
const berries = [disc(w / 2 - 7 * s, foot - 2 * s, 6 * s), disc(w / 2 + 7 * s, foot - 3 * s, 6 * s), disc(w / 2, foot + 7 * s, 6 * s)]
|
|
36
|
+
const mist = sprig({ x: -B, y: h * 0.99 }, { x: w * 0.02, y: h * 0.95 }, { x: w * 0.08, y: h * 0.955 }, { leaves: 5, size: 18 * s, seed: 2 })
|
|
37
|
+
const mist2 = sprig({ x: w + B, y: h * 0.99 }, { x: w * 0.98, y: h * 0.95 }, { x: w * 0.92, y: h * 0.955 }, { leaves: 5, size: 18 * s, seed: 4 })
|
|
38
|
+
return [
|
|
39
|
+
...perBand('Snow', 'snow', bandsAround(w, h, keep, B), (b, i) => specksIn(b, 700 * s * s, 7 + i, 2.4 * s), { opacity: 0.35 }),
|
|
40
|
+
{ name: 'Snowflakes', paths: flake([[0.05, 0.5, 9], [0.06, 0.74, 7]]), color: 'snow', o: { strokeWidth: 1.2 * s, opacity: 0.5 } },
|
|
41
|
+
{ name: 'Snowflakes', paths: flake([[0.95, 0.56, 8], [0.94, 0.8, 10]]), color: 'snow', o: { strokeWidth: 1.2 * s, opacity: 0.5 } },
|
|
42
|
+
{ name: 'Threads', paths: all.flatMap(b => b.string), color: 'gold', o: { strokeWidth: 1 * s, opacity: 0.9 } },
|
|
43
|
+
{ name: 'Gold baubles', paths: gold.flatMap(b => b.body), color: 'gold' },
|
|
44
|
+
{ name: 'Red baubles', paths: red.flatMap(b => b.body), color: 'red' },
|
|
45
|
+
{ name: 'Bauble shade', paths: all.flatMap(b => b.shade), color: 'ink', o: { opacity: 0.22 } },
|
|
46
|
+
{ name: 'Bauble bands', paths: all.flatMap(b => b.band), color: 'cream', o: { strokeWidth: 1.4 * s, opacity: 0.75 } },
|
|
47
|
+
{ name: 'Bauble shine', paths: all.flatMap(b => b.shine), color: 'snow', o: { opacity: 0.55 } },
|
|
48
|
+
{ name: 'Bauble caps', paths: all.flatMap(b => b.cap), color: 'goldDeep' },
|
|
49
|
+
{ name: 'Pine stems', paths: garland.map(g => g.stem), color: 'bark', o: { strokeWidth: 2.4 * s } },
|
|
50
|
+
{ name: 'Pine dark', paths: garland.flatMap(g => g.dark), color: 'pine', o: { strokeWidth: 2.2 * s } },
|
|
51
|
+
{ name: 'Pine light', paths: garland.flatMap(g => g.light), color: 'pineLight', o: { strokeWidth: 2 * s } },
|
|
52
|
+
{ name: 'Garland beads', paths: beads, color: 'red' },
|
|
53
|
+
{ name: 'Frame', paths: doubleRule(w, h, 16 * s, 5 * s), color: 'gold', o: { strokeWidth: 0.8 * s, opacity: 0.7 } },
|
|
54
|
+
{ name: 'Corner leaves', paths: mist.dark, color: 'pineLight', o: { opacity: 0.8 } },
|
|
55
|
+
{ name: 'Corner leaves', paths: mist2.dark, color: 'pineLight', o: { opacity: 0.8 } },
|
|
56
|
+
{ name: 'Holly', paths: hl, color: 'pineLight' },
|
|
57
|
+
{ name: 'Holly berries', paths: berries, color: 'red' },
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default defineTemplate({
|
|
62
|
+
id: 'print-holidays-christmas-eve-invitation',
|
|
63
|
+
meta: {
|
|
64
|
+
name: NAME,
|
|
65
|
+
description: 'A printed invitation to a family Christmas Eve open house: gold and red baubles hang from a pine garland over a forest-green card dusted with snow, with the invitation set below in a cream serif, gold small capitals and a sprig of holly. 5 × 7 inches with bleed, and an A6 copy',
|
|
66
|
+
category: 'invitation', tags: ['christmas', 'christmas eve', 'holiday party', 'open house', 'family', 'baubles', 'ornaments', 'pine', 'holly', 'invitation', 'print', 'card', 'winter'],
|
|
67
|
+
platforms: ['print'], formats: ['card'], useCases: ['invitation', 'event'],
|
|
68
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
69
|
+
},
|
|
70
|
+
stableNumbers: true,
|
|
71
|
+
build() {
|
|
72
|
+
const k = new Kit(NAME, preset('invitation-5x7'))
|
|
73
|
+
k.quantizeGeometry = true
|
|
74
|
+
k.brand(
|
|
75
|
+
{ paper: '#163A2E', text: '#F8F0DE', onPaper: '#E2C47A', primary: '#B83A30', onPrimary: '#FFF6E8' },
|
|
76
|
+
{ name: 'The Okafor family' },
|
|
77
|
+
)
|
|
78
|
+
k.palette({ gold: '#D8B356', goldDeep: '#9C7A2E', red: '#B83A30', cream: '#F8F0DE', pine: '#2D6247', pineLight: '#5E9470', bark: '#5A4632', snow: '#FFFFFF', ink: '#06140F' })
|
|
79
|
+
k.textStyle('Kicker 11', { size: 11, fontWeight: 600, letterSpacing: 3.6, lineHeight: 16 })
|
|
80
|
+
k.textStyle('Title 60', { ...SERIF, size: 60, fontWeight: 400, fontStyle: 'italic', letterSpacing: -1.4, lineHeight: 60 })
|
|
81
|
+
k.textStyle('Host 22', { ...SERIF, size: 22, fontWeight: 400, lineHeight: 28 })
|
|
82
|
+
k.textStyle('When 15', { ...SERIF, size: 15, fontWeight: 700, letterSpacing: 0.2, lineHeight: 21 })
|
|
83
|
+
k.textStyle('Body 12', { ...SERIF, size: 12, fontWeight: 400, lineHeight: 17 })
|
|
84
|
+
k.textStyle('Rsvp 10', { size: 10, fontWeight: 600, letterSpacing: 1.6, lineHeight: 15 })
|
|
85
|
+
|
|
86
|
+
const sheet = k.sheet
|
|
87
|
+
const { width: W, height: H } = k.node(sheet)
|
|
88
|
+
k.flowSheet(sheet, 0, PAD, 'paper', 'MIN', 'CENTER')
|
|
89
|
+
// A warm glow behind the baubles, over the bound green: depth without taking the ground from a kit.
|
|
90
|
+
k.rect(sheet, W + 28, 300, [k.gradient({ from: 'gold', to: 'gold', kind: 'RADIAL', fromOpacity: 0.2, toOpacity: 0, handles: [{ x: 0.5, y: 0.45 }, { x: 1, y: 0.45 }, { x: 0.5, y: 1.1 }] })], { name: 'Glow', absolute: { x: -14, y: -14 } })
|
|
91
|
+
draw(k, sheet, art(W, H))
|
|
92
|
+
|
|
93
|
+
const c = { align: 'CENTER' as const, width: 'FILL' as const }
|
|
94
|
+
k.text(sheet, 'YOU’RE INVITED TO SPEND', { style: 'Kicker 11', color: 'onPaper', ...c, name: 'Kicker' })
|
|
95
|
+
k.stack(sheet, { height: 8, name: 'Gap' })
|
|
96
|
+
k.text(sheet, 'Christmas Eve', { style: 'Title 60', color: 'text', ...c, name: 'Title' })
|
|
97
|
+
k.text(sheet, 'with {{brand.name}}', { style: 'Host 22', color: 'text', ...c, name: 'Host' })
|
|
98
|
+
k.stack(sheet, { height: 12, name: 'Gap' })
|
|
99
|
+
flourishComponent(k, 'Star rule', 'onPaper', starPath(0, 0, 10, 4.2), { rule: 34 })
|
|
100
|
+
k.instance(sheet, 'Star rule', {}, { name: 'Divider' })
|
|
101
|
+
k.stack(sheet, { height: 12, name: 'Gap' })
|
|
102
|
+
k.text(sheet, 'Thursday 24 December · from 6 pm', { style: 'When 15', color: 'onPaper', ...c, name: 'When' })
|
|
103
|
+
k.text(sheet, '418 Linden Avenue, Oak Park', { style: 'Body 12', color: 'text', ...c, name: 'Where' })
|
|
104
|
+
k.stack(sheet, { height: 12, name: 'Gap' })
|
|
105
|
+
k.text(sheet, 'Carols round the piano at eight, jollof, mince pies and mulled cider all evening. Bring the little ones, and a jumper you’re not proud of.', { style: 'Body 12', color: 'text', ...c, name: 'Body' })
|
|
106
|
+
k.stack(sheet, { height: 12, name: 'Gap' })
|
|
107
|
+
k.text(sheet, 'RSVP TO NGOZI BY 18 DECEMBER · (708) 555-0148', { style: 'Rsvp 10', color: 'onPaper', ...c, name: 'Rsvp' })
|
|
108
|
+
|
|
109
|
+
k.textStyle('Title 50', { ...SERIF, size: 50, fontWeight: 400, fontStyle: 'italic', letterSpacing: -1.2, lineHeight: 50 })
|
|
110
|
+
k.textStyle('Host 18', { ...SERIF, size: 18, fontWeight: 400, lineHeight: 23 })
|
|
111
|
+
k.textStyle('When 13', { ...SERIF, size: 13, fontWeight: 700, lineHeight: 18 })
|
|
112
|
+
k.textStyle('Body 10.5', { ...SERIF, size: 10.5, fontWeight: 400, lineHeight: 15 })
|
|
113
|
+
k.textStyle('Rsvp 8.5', { size: 8.5, fontWeight: 600, letterSpacing: 1.2, lineHeight: 13 })
|
|
114
|
+
k.textStyle('Kicker 9.5', { size: 9.5, fontWeight: 600, letterSpacing: 3, lineHeight: 14 })
|
|
115
|
+
k.variants(sheet, [printVariant('a6', {
|
|
116
|
+
art, pad: PAD, masterWidth: W,
|
|
117
|
+
styles: [['Title', 'Title 50'], ['Host', 'Host 18'], ['When', 'When 13'], ['Where', 'Body 10.5'], ['Body', 'Body 10.5'], ['Rsvp', 'Rsvp 8.5'], ['Kicker', 'Kicker 9.5']],
|
|
118
|
+
})])
|
|
119
|
+
return k.finish()
|
|
120
|
+
},
|
|
121
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// A sheet of twelve round Christmas gift tags to print on sticker paper or card and cut out: four designs (a
|
|
2
|
+
// layered pine tree under a star, a striped bauble, a snowflake, a sprig of holly) in red, green and cream, each
|
|
3
|
+
// with a stitched ring and "To" and "From" to fill in. Every tag is an instance of one component set, so the names
|
|
4
|
+
// are typed once per tag and a colour change reaches all twelve. US Letter on a kraft-paper ground that runs 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 type { VectorPath } from '@/lib/document/types'
|
|
11
|
+
import { bauble, disc, draw, holly, polyP, printVariant, snowflake, specksIn, starPath, type Drawn } from '../themes/holidays-shared'
|
|
12
|
+
|
|
13
|
+
const NAME = 'Christmas gift tags sticker sheet'
|
|
14
|
+
const PAD = { top: 54, right: 60, bottom: 40, left: 60 }
|
|
15
|
+
const D = 196
|
|
16
|
+
|
|
17
|
+
type Motif = 'Tree' | 'Bauble' | 'Snowflake' | 'Holly'
|
|
18
|
+
const MOTIFS: readonly Motif[] = ['Tree', 'Bauble', 'Snowflake', 'Holly']
|
|
19
|
+
/** Each design's disc, its drawing and its words. */
|
|
20
|
+
const LOOK: Record<Motif, { ground: string; ink: string; art: string; art2: string; words: string }> = {
|
|
21
|
+
Tree: { ground: 'tagGreen', ink: 'cream', art: 'pineLight', art2: 'gold', words: 'cream' },
|
|
22
|
+
Bauble: { ground: 'tagRed', ink: 'cream', art: 'gold', art2: 'cream', words: 'cream' },
|
|
23
|
+
Snowflake: { ground: 'tagCream', ink: 'red', art: 'red', art2: 'green', words: 'red' },
|
|
24
|
+
Holly: { ground: 'tagCream', ink: 'green', art: 'green', art2: 'red', words: 'green' },
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The picture on a tag, centred at (cx, cy) in a tag `D` across. */
|
|
28
|
+
function motif(m: Motif, cx: number, cy: number): Drawn[] {
|
|
29
|
+
const L = LOOK[m]
|
|
30
|
+
if (m === 'Tree') {
|
|
31
|
+
const tiers: VectorPath[] = [0, 1, 2].map(i => polyP([{ x: cx, y: cy - 38 + i * 17 }, { x: cx + 22 + i * 10, y: cy - 8 + i * 19 }, { x: cx - 22 - i * 10, y: cy - 8 + i * 19 }]))
|
|
32
|
+
return [
|
|
33
|
+
{ name: 'Tree', paths: tiers, color: L.art },
|
|
34
|
+
{ name: 'Trunk', paths: [polyP([{ x: cx - 5, y: cy + 30 }, { x: cx + 5, y: cy + 30 }, { x: cx + 5, y: cy + 40 }, { x: cx - 5, y: cy + 40 }])], color: 'bark' },
|
|
35
|
+
{ name: 'Top star', paths: [starPath(cx, cy - 40, 9, 3.8)], color: L.art2 },
|
|
36
|
+
{ name: 'Lights', paths: [[-8, -18], [9, -10], [-16, 2], [8, 8], [-4, 20], [20, 24], [-24, 26], [2, -2]].map(([x, y]) => disc(cx + x, cy + y, 2.8)), color: L.art2 },
|
|
37
|
+
{ name: 'Garland', paths: [[-14, -4, 16, -12], [-22, 14, 26, 6], [-30, 34, 32, 26]].map(([a, b, c, d]) => ({ closed: false, points: [{ x: cx + a, y: cy + b }, { x: cx + c, y: cy + d, handleIn: { x: cx + (a + c) / 2, y: cy + (b + d) / 2 + 7 } }] })), color: L.ink, o: { strokeWidth: 1.2, opacity: 0.8 } },
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
if (m === 'Bauble') {
|
|
41
|
+
const b = bauble(cx, cy - 48, 34, { string: 2 })
|
|
42
|
+
return [
|
|
43
|
+
{ name: 'Bauble', paths: b.body, color: L.art },
|
|
44
|
+
{ name: 'Bauble shade', paths: b.shade, color: 'goldDeep', o: { opacity: 0.5 } },
|
|
45
|
+
{ name: 'Bauble bands', paths: b.band, color: L.art2, o: { strokeWidth: 2 } },
|
|
46
|
+
{ name: 'Bauble cap', paths: b.cap, color: 'goldDeep' },
|
|
47
|
+
{ name: 'Bauble shine', paths: b.shine, color: L.art2, o: { opacity: 0.7 } },
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
if (m === 'Snowflake') return [
|
|
51
|
+
{ name: 'Snowflake', paths: snowflake(cx, cy, 40), color: L.art, o: { strokeWidth: 3.2 } },
|
|
52
|
+
{ name: 'Snow dots', paths: [0, 1, 2, 3, 4, 5].map(i => disc(cx + Math.cos(i * Math.PI / 3 + Math.PI / 6) * 30, cy + Math.sin(i * Math.PI / 3 + Math.PI / 6) * 30, 2.6)), color: L.art2 },
|
|
53
|
+
{ name: 'Snowflake heart', paths: [disc(cx, cy, 5)], color: L.art2 },
|
|
54
|
+
]
|
|
55
|
+
return [
|
|
56
|
+
{ name: 'Holly', paths: [holly({ x: cx - 4, y: cy + 4 }, 48, Math.PI + 0.45), holly({ x: cx + 4, y: cy + 4 }, 48, -0.45), holly({ x: cx, y: cy + 6 }, 34, Math.PI / 2 + 0.1)], color: L.art },
|
|
57
|
+
{ name: 'Berries', paths: [disc(cx - 7, cy - 2, 7), disc(cx + 7, cy - 3, 7), disc(cx, cy + 9, 7)], color: L.art2 },
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The kraft paper's fibre, round the edges and between the tags (a full sheet of specks, in four bands). */
|
|
62
|
+
function paper(w: number, h: number): Drawn[] {
|
|
63
|
+
const B = 12
|
|
64
|
+
return [0, 1, 2, 3].map(i => {
|
|
65
|
+
const band = { x: -B, y: -B + ((h + 2 * B) * i) / 4, w: w + 2 * B, h: (h + 2 * B) / 4 }
|
|
66
|
+
return { name: 'Fibre', paths: specksIn(band, 260, 21 + i, 2.2), color: 'fibre', o: { opacity: 0.45 } }
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default defineTemplate({
|
|
71
|
+
id: 'print-holidays-christmas-gift-tags',
|
|
72
|
+
meta: {
|
|
73
|
+
name: NAME,
|
|
74
|
+
description: 'Twelve round Christmas gift tags on one sheet, to print on sticker paper or card and cut out: a layered pine tree, a striped bauble, a snowflake and a sprig of holly in red, green and cream, each with a stitched ring and “To” and “From” lines. One component set, so a colour change reaches every tag. US Letter on kraft paper with bleed, and an A4 copy',
|
|
75
|
+
category: 'sticker', tags: ['christmas', 'gift tags', 'labels', 'stickers', 'sticker sheet', 'presents', 'wrapping', 'printable', 'holly', 'snowflake', 'kraft', 'diy', 'print'],
|
|
76
|
+
platforms: ['print'], formats: ['card'], useCases: ['greeting'],
|
|
77
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
78
|
+
},
|
|
79
|
+
stableNumbers: true,
|
|
80
|
+
build() {
|
|
81
|
+
const k = new Kit(NAME, preset('us-letter'))
|
|
82
|
+
k.quantizeGeometry = true
|
|
83
|
+
k.brand(
|
|
84
|
+
{ paper: '#E6D6BC', text: '#4A2B1E', onPaper: '#7A2E25', primary: '#A3312A', onPrimary: '#FFF6E8' },
|
|
85
|
+
{ name: 'From the Lindqvist house' },
|
|
86
|
+
)
|
|
87
|
+
k.palette({ tagRed: '#A3312A', tagGreen: '#2F5A43', tagCream: '#FBF4E6', red: '#A3312A', green: '#2F5A43', cream: '#FBF4E6', gold: '#D9B356', goldDeep: '#8E6C25', pineLight: '#7FAE86', bark: '#6B4A33', fibre: '#9C7F57' })
|
|
88
|
+
k.textStyle('Head 30', { ...SERIF, size: 30, fontWeight: 600, fontStyle: 'italic', letterSpacing: -0.4, lineHeight: 34 })
|
|
89
|
+
k.textStyle('Note 11', { size: 11, fontWeight: 500, letterSpacing: 0.4, lineHeight: 16 })
|
|
90
|
+
k.textStyle('Tag 13', { ...SERIF, size: 13, fontWeight: 600, fontStyle: 'italic', lineHeight: 17 })
|
|
91
|
+
k.textStyle('Tag word 9', { size: 9, fontWeight: 700, letterSpacing: 2.2, lineHeight: 12 })
|
|
92
|
+
|
|
93
|
+
// One tag, four designs: a disc with a stitched ring, the picture, a greeting and "To" / "From" lines.
|
|
94
|
+
k.variantSet('Gift tag', 'Design', MOTIFS, { width: D, height: D, radius: D / 2, padding: { top: 110, right: 30, bottom: 18, left: 30 }, gap: 4, align: 'CENTER', props: { Greeting: 'Merry Christmas', To: 'To: ____________', From: 'From: __________' } }, (m, id) => {
|
|
95
|
+
const L = LOOK[m as Motif]
|
|
96
|
+
k.vector(id, D - 16, D - 16, [disc((D - 16) / 2, (D - 16) / 2, (D - 16) / 2)], [], { name: 'Stitch', absolute: { x: 8, y: 8 }, stroke: { color: L.words, width: 1.4, dash: [5, 4] }, opacity: 0.8 })
|
|
97
|
+
draw(k, id, motif(m as Motif, D / 2, 64))
|
|
98
|
+
k.propText(id, 'Greeting', { style: 'Tag 13', color: L.words, align: 'CENTER', width: 'FILL', name: 'Greeting' })
|
|
99
|
+
k.propText(id, 'To', { style: 'Tag word 9', color: L.words, align: 'CENTER', width: 'FILL', name: 'To' })
|
|
100
|
+
k.propText(id, 'From', { style: 'Tag word 9', color: L.words, align: 'CENTER', width: 'FILL', name: 'From' })
|
|
101
|
+
}, m => ({ fill: LOOK[m as Motif].ground }))
|
|
102
|
+
|
|
103
|
+
const sheet = k.sheet
|
|
104
|
+
const { width: W, height: H } = k.node(sheet)
|
|
105
|
+
k.flowSheet(sheet, 18, PAD, 'paper', 'MIN', 'CENTER')
|
|
106
|
+
draw(k, sheet, paper(W, H))
|
|
107
|
+
k.text(sheet, 'Merry & bright gift tags', { style: 'Head 30', color: 'text', align: 'CENTER', width: 'FILL', name: 'Title' })
|
|
108
|
+
const grid = k.stack(sheet, { direction: 'HORIZONTAL', wrap: true, gap: 22, width: 'FILL', justify: 'CENTER', name: 'Tags' })
|
|
109
|
+
k.patch(grid, { counterAxisSpacing: 22 })
|
|
110
|
+
const greet = ['Merry Christmas', 'Joy to you', 'Ho ho ho', 'Season’s greetings', 'Peace & joy', 'Merry Christmas', 'With love', 'Don’t peek!', 'Happy holidays', 'Be merry', 'Noel', 'For you, with love']
|
|
111
|
+
greet.forEach((g, i) => k.instance(grid, `Gift tag/${MOTIFS[i % 4]}`, { Greeting: g }, { name: 'Gift tag' }))
|
|
112
|
+
k.text(sheet, 'Print on full-sheet sticker paper or card and cut round the stitched ring · {{brand.name}}', { style: 'Note 11', color: 'text', align: 'CENTER', width: 'FILL', name: 'Note' })
|
|
113
|
+
|
|
114
|
+
k.variants(sheet, [printVariant('a4', { art: paper, pad: PAD, masterWidth: W })])
|
|
115
|
+
return k.finish()
|
|
116
|
+
},
|
|
117
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// A printed invitation to a family's Diwali evening: a toran of marigolds and mango leaves hangs across the top of a
|
|
2
|
+
// deep aubergine card, a rangoli in pink, saffron and teal rises from the foot with a row of lit clay diyas before it,
|
|
3
|
+
// and between them "Shubh Deepavali" in a gold serif over the evening's details (Lakshmi puja, dinner, sparklers in the
|
|
4
|
+
// garden). 5 × 7 inches with the toran and rangoli running into the eighth-inch bleed, and an A6 copy.
|
|
5
|
+
|
|
6
|
+
import { Kit, preset } from '../kit'
|
|
7
|
+
import { defineTemplate } from '../build'
|
|
8
|
+
import { SERIF } from '../shared/type'
|
|
9
|
+
import { blurred } from '../shared/texture'
|
|
10
|
+
import { bandsAround, disc, diya, draw, flourishComponent, perBand, printVariant, rangoli, specksIn, toran, type Drawn } from '../themes/holidays-shared'
|
|
11
|
+
|
|
12
|
+
const NAME = 'Diwali party invitation'
|
|
13
|
+
const PAD = { top: 150, right: 46, bottom: 214, left: 46 }
|
|
14
|
+
|
|
15
|
+
function art(w: number, h: number): Drawn[] {
|
|
16
|
+
const s = w / 480, B = 14 * s, cx = w / 2
|
|
17
|
+
const t = toran(-B, w + B, -2 * s, 14 * s, 7, 15 * s, 3)
|
|
18
|
+
const r = rangoli(cx, h + 26 * s, 210 * s, 8)
|
|
19
|
+
const diyas = [0.14, 0.32, 0.5, 0.68, 0.86].map((x, i) => diya(w * x - 10 * s, h - 22 * s - (i === 2 ? 14 * s : i % 2 ? 6 * s : 0), (i === 2 ? 64 : 52) * s))
|
|
20
|
+
const keep = { x: 30 * s, y: 100 * s, w: w - 60 * s, h: h - 330 * s }
|
|
21
|
+
return [
|
|
22
|
+
...perBand('Glitter', 'gold', bandsAround(w, h, keep, B).slice(0, 1).concat(bandsAround(w, h, keep, B).slice(2)), (b, i) => specksIn(b, 420 * s * s, 60 + i, 2.2 * s), { opacity: 0.5 }),
|
|
23
|
+
{ name: 'Rangoli outer', paths: r.outer, color: 'pink' },
|
|
24
|
+
{ name: 'Rangoli middle', paths: r.middle, color: 'saffron' },
|
|
25
|
+
{ name: 'Rangoli inner', paths: r.inner, color: 'teal' },
|
|
26
|
+
{ name: 'Rangoli dots', paths: r.dots, color: 'cream' },
|
|
27
|
+
{ name: 'Rangoli heart', paths: r.heart, color: 'gold' },
|
|
28
|
+
{ name: 'Diya glow', paths: diyas.map(d => d.flame[0]).map(f => ({ ...f })), color: 'saffron', o: { opacity: 0.6, effects: [blurred(10 * s)] } },
|
|
29
|
+
{ name: 'Diyas', paths: diyas.flatMap(d => d.bowl), color: 'clay' },
|
|
30
|
+
{ name: 'Diya rims', paths: diyas.flatMap(d => d.rim), color: 'gold' },
|
|
31
|
+
{ name: 'Flames', paths: diyas.flatMap(d => d.flame), color: 'saffron' },
|
|
32
|
+
{ name: 'Flame cores', paths: diyas.flatMap(d => d.core), color: 'cream' },
|
|
33
|
+
{ name: 'Toran string', paths: t.string, color: 'gold', o: { strokeWidth: 1.6 * s } },
|
|
34
|
+
{ name: 'Mango leaves', paths: t.leaves, color: 'leaf' },
|
|
35
|
+
{ name: 'Marigolds', paths: t.blooms.outer, color: 'marigold' },
|
|
36
|
+
{ name: 'Marigold hearts', paths: t.blooms.inner, color: 'saffron' },
|
|
37
|
+
{ name: 'Bells', paths: [0.07, 0.93].map(x => disc(w * x, 70 * s, 6 * s)), color: 'gold' },
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default defineTemplate({
|
|
42
|
+
id: 'print-holidays-diwali-party-invitation',
|
|
43
|
+
meta: {
|
|
44
|
+
name: NAME,
|
|
45
|
+
description: 'A printed invitation to a family Diwali evening: a toran of marigolds and mango leaves across the top of a deep aubergine card, a rangoli in pink, saffron and teal rising from the foot behind a row of lit clay diyas, and “Shubh Deepavali” in a gold serif over the details: Lakshmi puja, dinner and sparklers in the garden. 5 × 7 inches with bleed, and an A6 copy',
|
|
46
|
+
category: 'invitation', tags: ['diwali', 'deepavali', 'festival of lights', 'diya', 'rangoli', 'marigold', 'toran', 'lakshmi puja', 'hindu', 'family', 'party', 'invitation', 'print', 'card'],
|
|
47
|
+
platforms: ['print'], formats: ['card'], useCases: ['invitation', 'event'],
|
|
48
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
49
|
+
},
|
|
50
|
+
stableNumbers: true,
|
|
51
|
+
build() {
|
|
52
|
+
const k = new Kit(NAME, preset('invitation-5x7'))
|
|
53
|
+
k.quantizeGeometry = true
|
|
54
|
+
k.brand(
|
|
55
|
+
{ paper: '#3B0F3D', text: '#FFF1D6', onPaper: '#F3C969', primary: '#F3C969', onPrimary: '#3B0F3D' },
|
|
56
|
+
{ name: 'the Mehta family' },
|
|
57
|
+
)
|
|
58
|
+
k.palette({ gold: '#E9B949', saffron: '#FFA41B', marigold: '#F57C14', cream: '#FFF1D6', pink: '#E0457B', teal: '#1E9C93', leaf: '#3F8F45', clay: '#B9572B' })
|
|
59
|
+
k.textStyle('Kicker 11', { size: 11, fontWeight: 700, letterSpacing: 3.6, lineHeight: 16 })
|
|
60
|
+
k.textStyle('Title 50', { ...SERIF, size: 50, fontWeight: 600, fontStyle: 'italic', letterSpacing: -1, lineHeight: 52 })
|
|
61
|
+
k.textStyle('Sub 18', { ...SERIF, size: 18, fontWeight: 400, lineHeight: 24 })
|
|
62
|
+
k.textStyle('When 15', { size: 15, fontWeight: 700, lineHeight: 21 })
|
|
63
|
+
k.textStyle('Body 12', { ...SERIF, size: 12, fontWeight: 400, lineHeight: 17 })
|
|
64
|
+
k.textStyle('Rsvp 10', { size: 10, fontWeight: 700, letterSpacing: 1.6, lineHeight: 15 })
|
|
65
|
+
|
|
66
|
+
const sheet = k.sheet
|
|
67
|
+
const { width: W, height: H } = k.node(sheet)
|
|
68
|
+
k.flowSheet(sheet, 0, PAD, 'paper', 'MIN', 'CENTER')
|
|
69
|
+
draw(k, sheet, art(W, H))
|
|
70
|
+
const c = { align: 'CENTER' as const, width: 'FILL' as const }
|
|
71
|
+
k.text(sheet, 'A FESTIVAL OF LIGHTS', { style: 'Kicker 11', color: 'onPaper', ...c, name: 'Kicker' })
|
|
72
|
+
k.stack(sheet, { height: 6, name: 'Gap' })
|
|
73
|
+
k.text(sheet, 'Shubh Deepavali', { style: 'Title 50', color: 'onPaper', ...c, name: 'Title' })
|
|
74
|
+
k.text(sheet, 'Celebrate Diwali with {{brand.name}}', { style: 'Sub 18', color: 'text', ...c, name: 'Sub' })
|
|
75
|
+
k.stack(sheet, { height: 14, name: 'Gap' })
|
|
76
|
+
flourishComponent(k, 'Lamp rule', 'primary', disc(0, 0, 5, 7), { rule: 30, size: 7 })
|
|
77
|
+
k.instance(sheet, 'Lamp rule', {}, { name: 'Divider' })
|
|
78
|
+
k.stack(sheet, { height: 14, name: 'Gap' })
|
|
79
|
+
k.text(sheet, 'Sunday 8 November · 6:30 pm', { style: 'When 15', color: 'text', ...c, name: 'When' })
|
|
80
|
+
k.text(sheet, '31 Juniper Court, Edison', { style: 'Body 12', color: 'text', ...c, name: 'Where' })
|
|
81
|
+
k.stack(sheet, { height: 10, name: 'Gap' })
|
|
82
|
+
k.text(sheet, 'Lakshmi puja at seven, then dinner, mithai and sparklers in the garden. Wear your brightest; the kids will be judging.', { style: 'Body 12', color: 'text', ...c, name: 'Body' })
|
|
83
|
+
k.stack(sheet, { height: 10, name: 'Gap' })
|
|
84
|
+
k.text(sheet, 'RSVP TO PRIYA · (732) 555-0186', { style: 'Rsvp 10', color: 'onPaper', ...c, name: 'Rsvp' })
|
|
85
|
+
|
|
86
|
+
k.textStyle('Title 42', { ...SERIF, size: 42, fontWeight: 600, fontStyle: 'italic', letterSpacing: -0.8, lineHeight: 44 })
|
|
87
|
+
k.textStyle('Sub 15', { ...SERIF, size: 15, fontWeight: 400, lineHeight: 20 })
|
|
88
|
+
k.textStyle('When 13', { size: 13, fontWeight: 700, lineHeight: 18 })
|
|
89
|
+
k.textStyle('Body 10.5', { ...SERIF, size: 10.5, fontWeight: 400, lineHeight: 15 })
|
|
90
|
+
k.textStyle('Rsvp 8.5', { size: 8.5, fontWeight: 700, letterSpacing: 1.2, lineHeight: 13 })
|
|
91
|
+
k.textStyle('Kicker 9.5', { size: 9.5, fontWeight: 700, letterSpacing: 3, lineHeight: 14 })
|
|
92
|
+
k.variants(sheet, [printVariant('a6', {
|
|
93
|
+
art, pad: PAD, masterWidth: W,
|
|
94
|
+
styles: [['Title', 'Title 42'], ['Sub', 'Sub 15'], ['When', 'When 13'], ['Where', 'Body 10.5'], ['Body', 'Body 10.5'], ['Rsvp', 'Rsvp 8.5'], ['Kicker', 'Kicker 9.5']],
|
|
95
|
+
})])
|
|
96
|
+
return k.finish()
|
|
97
|
+
},
|
|
98
|
+
})
|