@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,110 @@
|
|
|
1
|
+
// A neighbourhood bakery's Lunar New Year post, in red and gold papercut on warm cream: strings of firecrackers hang
|
|
2
|
+
// down both edges, a round papercut medallion of plum blossom and cash coins sits in the corner with a red lantern
|
|
3
|
+
// beside it, and "New Year treats are here" is set big in a heavy red serif over the three treats and their prices,
|
|
4
|
+
// the days the bakery closes to celebrate, and the shop's name. 4:5 for the feed and 9:16 for stories, the drawing
|
|
5
|
+
// worked out again for each.
|
|
6
|
+
|
|
7
|
+
import { Kit, preset, type VariantSpec } from '../kit'
|
|
8
|
+
import { defineTemplate } from '../build'
|
|
9
|
+
import { SERIF } from '../shared/type'
|
|
10
|
+
import type { VectorPath } from '@/lib/document/types'
|
|
11
|
+
import { verticalPadding } from './shared'
|
|
12
|
+
import { cloudCurl, coin, disc, draw, firecrackers, plum, redLantern, redraw, restyleAll, scaleGaps, starPath, type Drawn } from '../themes/holidays-shared'
|
|
13
|
+
|
|
14
|
+
const NAME = 'Lunar New Year bakery post'
|
|
15
|
+
|
|
16
|
+
function art(w: number, h: number, safe = { top: 0, bottom: 0 }): Drawn[] {
|
|
17
|
+
const s = w / 1080
|
|
18
|
+
const fl = firecrackers(40 * s, -10, 9, 60 * s)
|
|
19
|
+
// The medallion: a red disc, a ring of plum blossoms cut into it, coins at the cardinal points.
|
|
20
|
+
const mx = w - 250 * s, my = h - 260 * s - safe.bottom * 0.4, R = 210 * s
|
|
21
|
+
const ring = Array.from({ length: 10 }, (_, i) => { const a = (i * Math.PI) / 5; return plum(mx + Math.cos(a) * R * 0.7, my + Math.sin(a) * R * 0.7, 34 * s, a) })
|
|
22
|
+
const coins = [0, 1, 2, 3].map(i => coin(mx + Math.cos(i * Math.PI / 2 + Math.PI / 4) * R * 0.32, my + Math.sin(i * Math.PI / 2 + Math.PI / 4) * R * 0.32, 26 * s))
|
|
23
|
+
const top = safe.top * 0.6
|
|
24
|
+
const lanterns = [[0.75, 150, 110], [0.87, 70, 150], [0.97, 200, 100]].map(([x, drop, lw]) => redLantern(w * x, -10, lw * s, top + drop * s))
|
|
25
|
+
const clouds = [...cloudCurl(70 * s, h - 96 * s - safe.bottom * 0.4, 230 * s, 1), ...cloudCurl(300 * s, h - 60 * s - safe.bottom * 0.4, 170 * s, 1)]
|
|
26
|
+
const lantern = { body: lanterns.flatMap(l => l.body), ribs: lanterns.flatMap(l => l.ribs), caps: lanterns.flatMap(l => l.caps), tassel: lanterns.flatMap(l => l.tassel), cord: lanterns.flatMap(l => l.cord) }
|
|
27
|
+
const sparks: VectorPath[] = [[0.3, 0.06], [0.52, 0.04], [0.62, 0.09]].map(([x, y]) => starPath(w * x, safe.top * 0.6 + h * y * (1350 / h), 10 * s, 4 * s, 4))
|
|
28
|
+
return [
|
|
29
|
+
{ name: 'Firecracker cords', paths: fl.cord, color: 'gold', o: { strokeWidth: 3 * s } },
|
|
30
|
+
{ name: 'Firecrackers', paths: fl.crackers, color: 'red' },
|
|
31
|
+
{ name: 'Firecracker tips', paths: fl.tips, color: 'gold' },
|
|
32
|
+
{ name: 'Sparks', paths: sparks, color: 'gold' },
|
|
33
|
+
{ name: 'Medallion', paths: [disc(mx, my, R)], color: 'red' },
|
|
34
|
+
{ name: 'Medallion rim', paths: [disc(mx, my, R * 0.93)], color: 'gold', o: { strokeWidth: 4 * s } },
|
|
35
|
+
{ name: 'Cut blossoms', paths: ring.flatMap(p => p.petals), color: 'cream' },
|
|
36
|
+
{ name: 'Blossom hearts', paths: ring.flatMap(p => p.heart), color: 'gold' },
|
|
37
|
+
{ name: 'Coins', paths: coins.flatMap(c => c.coin), color: 'gold' },
|
|
38
|
+
{ name: 'Coin holes', paths: coins.flatMap(c => c.hole), color: 'red' },
|
|
39
|
+
{ name: 'Lantern cords', paths: lantern.cord, color: 'gold', o: { strokeWidth: 2.4 * s } },
|
|
40
|
+
{ name: 'Lantern', paths: lantern.body, color: 'red' },
|
|
41
|
+
{ name: 'Lantern ribs', paths: lantern.ribs, color: 'gold', o: { strokeWidth: 2 * s } },
|
|
42
|
+
{ name: 'Lantern caps', paths: [...lantern.caps, ...lantern.tassel], color: 'gold' },
|
|
43
|
+
{ name: 'Clouds', paths: clouds, color: 'red', o: { strokeWidth: 5 * s } },
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default defineTemplate({
|
|
48
|
+
id: 'social-holidays-lunar-new-year-bakery-post',
|
|
49
|
+
meta: {
|
|
50
|
+
name: NAME,
|
|
51
|
+
description: 'A neighbourhood bakery’s Lunar New Year post in red and gold papercut on cream: firecrackers down both edges, a papercut medallion of plum blossom and coins with a red lantern, “New Year treats are here” in a heavy red serif, three treats with prices and the days the bakery closes to celebrate. 4:5 for the feed and 9:16 for stories',
|
|
52
|
+
category: 'social', tags: ['lunar new year', 'chinese new year', 'spring festival', 'bakery', 'pineapple tarts', 'holiday hours', 'small business', 'papercut', 'red and gold', 'lantern', 'instagram', 'story', 'post'],
|
|
53
|
+
platforms: ['instagram', 'facebook'], formats: ['portrait-post', 'story'], useCases: ['sale', 'announcement'],
|
|
54
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
55
|
+
},
|
|
56
|
+
stableNumbers: true,
|
|
57
|
+
build() {
|
|
58
|
+
const k = new Kit(NAME, preset('ig-portrait'))
|
|
59
|
+
k.quantizeGeometry = true
|
|
60
|
+
k.brand(
|
|
61
|
+
{ paper: '#FFF3DC', text: '#3A1410', onPaper: '#A50F23', primary: '#C8102E', onPrimary: '#FFF3DC', line: '#EBCB9A' },
|
|
62
|
+
{ name: 'Lucky Star Bakery', site: 'luckystarbakery.com' },
|
|
63
|
+
)
|
|
64
|
+
k.palette({ red: '#C8102E', gold: '#E2A72E', cream: '#FFF3DC' })
|
|
65
|
+
k.textStyle('Kicker 26', { size: 26, fontWeight: 800, letterSpacing: 5, lineHeight: 34 })
|
|
66
|
+
k.textStyle('Title 100', { ...SERIF, size: 100, fontWeight: 800, letterSpacing: -2.8, lineHeight: 96 })
|
|
67
|
+
k.textStyle('Treat 31', { ...SERIF, size: 31, fontWeight: 600, lineHeight: 38 })
|
|
68
|
+
k.textStyle('Price 31', { size: 31, fontWeight: 800, lineHeight: 38, fontFeatures: { tnum: 1 } })
|
|
69
|
+
k.textStyle('Foot 26', { size: 26, fontWeight: 800, lineHeight: 34 })
|
|
70
|
+
k.textStyle('Body 26', { size: 26, fontWeight: 500, lineHeight: 35 })
|
|
71
|
+
|
|
72
|
+
k.component('Treat', { direction: 'HORIZONTAL', gap: 20, width: 560, justify: 'SPACE_BETWEEN', padding: { top: 10, right: 0, bottom: 10, left: 0 }, props: { Treat: 'Treat', Price: '$0' } }, id => {
|
|
73
|
+
k.propText(id, 'Treat', { style: 'Treat 31', color: 'text', autoWidth: true, name: 'Treat name' })
|
|
74
|
+
k.propText(id, 'Price', { style: 'Price 31', color: 'onPaper', autoWidth: true, name: 'Treat price' })
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const sheet = k.sheet
|
|
78
|
+
const { width: W, height: H } = k.node(sheet)
|
|
79
|
+
k.flowSheet(sheet, 0, { top: 150, right: 436, bottom: 90, left: 84 }, 'paper', 'MIN', 'MIN')
|
|
80
|
+
draw(k, sheet, art(W, H))
|
|
81
|
+
k.text(sheet, 'GONG HEI FAT CHOY', { style: 'Kicker 26', color: 'onPaper', width: 'FILL', name: 'Kicker' })
|
|
82
|
+
k.stack(sheet, { height: 14, name: 'Gap' })
|
|
83
|
+
k.text(sheet, 'New Year treats are here', { style: 'Title 100', color: 'onPaper', width: 'FILL', name: 'Title' })
|
|
84
|
+
k.stack(sheet, { height: 26, name: 'Gap' })
|
|
85
|
+
const list = k.stack(sheet, { width: 560, name: 'Treats' })
|
|
86
|
+
for (const [i, [n, p]] of [['Pineapple tarts, box of 12', '$16'], ['Almond cookies, tin', '$12'], ['Nian gao, coconut', '$9']].entries()) {
|
|
87
|
+
k.instance(list, 'Treat', { Treat: n, Price: p }, { name: 'Treat', width: 'FILL' })
|
|
88
|
+
if (i < 2) k.rect(list, 'FILL', 2, 'line', { name: 'Rule' })
|
|
89
|
+
}
|
|
90
|
+
k.stack(sheet, { height: 22, name: 'Gap' })
|
|
91
|
+
k.text(sheet, 'Closed Saturday 6 and Sunday 7 February to celebrate with our families. Pre-order by the 4th.', { style: 'Body 26', color: 'text', width: 'FILL', name: 'Body' })
|
|
92
|
+
k.stack(sheet, { height: 14, name: 'Gap' })
|
|
93
|
+
k.text(sheet, '{{brand.name}} · {{brand.site}}', { style: 'Foot 26', color: 'onPaper', width: 'FILL', name: 'Foot' })
|
|
94
|
+
|
|
95
|
+
k.textStyle('Title 112', { ...SERIF, size: 112, fontWeight: 800, letterSpacing: -3, lineHeight: 106 })
|
|
96
|
+
const story: VariantSpec = {
|
|
97
|
+
preset: 'ig-story',
|
|
98
|
+
adjust: (kit, id) => {
|
|
99
|
+
const { width: w, height: h } = kit.node(id)
|
|
100
|
+
const safe = verticalPadding(84)
|
|
101
|
+
redraw(kit, id, art(w, h, { top: safe.top - 84, bottom: safe.bottom - 84 }))
|
|
102
|
+
kit.patch(id, { padding: { top: safe.top + 60, right: w - safe.left - 560, bottom: safe.bottom, left: safe.left } })
|
|
103
|
+
restyleAll(kit, id, [['Title', 'Title 112']])
|
|
104
|
+
scaleGaps(kit, id, 1.3)
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
k.variants(sheet, [story])
|
|
108
|
+
return k.finish()
|
|
109
|
+
},
|
|
110
|
+
})
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// A neighbourhood wine shop's holiday-hours post for the week between Christmas and New Year: confetti and streamers
|
|
2
|
+
// on deep teal, a bottle of fizz popping its cork in the corner, "Pop in before you pop the cork" set big and heavy,
|
|
3
|
+
// every day's opening hours in a list (closed days marked), and the offer in a yellow pill. 4:5 for the feed and 9:16
|
|
4
|
+
// for stories, the drawing worked out again for each and the type kept inside the apps' safe area.
|
|
5
|
+
|
|
6
|
+
import { Kit, preset, type VariantSpec } from '../kit'
|
|
7
|
+
import { defineTemplate } from '../build'
|
|
8
|
+
import { SERIF } from '../shared/type'
|
|
9
|
+
import type { VectorPath } from '@/lib/document/types'
|
|
10
|
+
import { verticalPadding } from './shared'
|
|
11
|
+
import { bandsAround, confetti, disc, draw, perBand, polyP, redraw, restyleAll, scaleGaps, sparkle, streamer, type Drawn } from '../themes/holidays-shared'
|
|
12
|
+
|
|
13
|
+
const NAME = 'New Year’s Eve shop holiday hours post'
|
|
14
|
+
|
|
15
|
+
const HOURS = [
|
|
16
|
+
['Wed 23 Dec', '10 am – 8 pm'], ['Thu 24 Dec', '10 am – 4 pm'], ['Fri 25 Dec', 'Closed'], ['Sat 26 Dec', '12 – 6 pm'],
|
|
17
|
+
['Wed 30 Dec', '10 am – 8 pm'], ['Thu 31 Dec', '10 am – 6 pm'], ['Fri 1 Jan', 'Closed'],
|
|
18
|
+
] as const
|
|
19
|
+
|
|
20
|
+
/** A bottle of fizz tilted back, its cork flying and a spray of foam, with its base at (x, y). */
|
|
21
|
+
function bottle(x: number, y: number, s: number, tilt: number): { glass: VectorPath[]; foil: VectorPath[]; label: VectorPath[]; cork: VectorPath[]; spray: VectorPath[] } {
|
|
22
|
+
const c = Math.cos(tilt), sn = Math.sin(tilt)
|
|
23
|
+
const p = (u: number, v: number) => ({ x: x + (u * c - v * sn) * s, y: y + (u * sn + v * c) * s })
|
|
24
|
+
const glass = [polyP([p(-0.16, 0), p(0.16, 0), p(0.16, -0.5), p(0.06, -0.68), p(0.05, -0.92), p(-0.05, -0.92), p(-0.06, -0.68), p(-0.16, -0.5)])]
|
|
25
|
+
const foil = [polyP([p(-0.058, -0.74), p(0.058, -0.74), p(0.05, -0.94), p(-0.05, -0.94)])]
|
|
26
|
+
const label = [polyP([p(-0.16, -0.16), p(0.16, -0.16), p(0.16, -0.38), p(-0.16, -0.38)])]
|
|
27
|
+
const tip = p(0, -1.18)
|
|
28
|
+
const cork = [polyP([{ x: tip.x - 0.035 * s, y: tip.y - 0.05 * s }, { x: tip.x + 0.035 * s, y: tip.y - 0.05 * s }, { x: tip.x + 0.045 * s, y: tip.y + 0.05 * s }, { x: tip.x - 0.045 * s, y: tip.y + 0.05 * s }])]
|
|
29
|
+
const spray = [0, 1, 2, 3, 4, 5, 6, 7].map(i => { const q = p((i % 2 ? 1 : -1) * (0.04 + i * 0.012), -0.98 - i * 0.03); return disc(q.x, q.y, (0.014 + (i % 3) * 0.006) * s) })
|
|
30
|
+
return { glass, foil, label, cork, spray }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function art(w: number, h: number, safe = { top: 0, bottom: 0 }): Drawn[] {
|
|
34
|
+
const s = w / 1080, B = 0
|
|
35
|
+
const b = bottle(w - 120 * s, h - 70 * s - safe.bottom * 0.3, 470 * s, -0.36)
|
|
36
|
+
const keep = { x: 60 * s, y: 60 * s + safe.top, w: 700 * s, h: h - 120 * s - safe.top - safe.bottom }
|
|
37
|
+
const bands = bandsAround(w, h, keep, B)
|
|
38
|
+
const st = [streamer({ x: w * 0.62, y: -10 }, w * 0.5, 0.5, 18 * s, 3.5), streamer({ x: w * 0.78, y: -10 }, w * 0.4, 1.05, 16 * s, 3), streamer({ x: -10, y: h - 40 * s }, w * 0.4, -0.12, 14 * s, 2.5)]
|
|
39
|
+
return [
|
|
40
|
+
...perBand('Confetti', 'yellow', bands, (r, i) => confetti(r.w, r.h, Math.round((r.w * r.h) / (9000 * s * s)), 90 + i, 22 * s).bars.map(p => ({ ...p, points: p.points.map(q => ({ x: q.x + r.x, y: q.y + r.y })) }))),
|
|
41
|
+
...perBand('Confetti dots', 'coral', bands, (r, i) => confetti(r.w, r.h, Math.round((r.w * r.h) / (14000 * s * s)), 120 + i, 22 * s).dots.map(p => ({ ...p, points: p.points.map(q => ({ ...q, x: q.x + r.x, y: q.y + r.y, ...(q.handleIn ? { handleIn: { x: q.handleIn.x + r.x, y: q.handleIn.y + r.y } } : {}), ...(q.handleOut ? { handleOut: { x: q.handleOut.x + r.x, y: q.handleOut.y + r.y } } : {}) })) }))),
|
|
42
|
+
{ name: 'Streamers', paths: st.slice(0, 2), color: 'coral', o: { strokeWidth: 7 * s } },
|
|
43
|
+
{ name: 'Streamers', paths: st.slice(2), color: 'mint', o: { strokeWidth: 7 * s } },
|
|
44
|
+
{ name: 'Bottle', paths: b.glass, color: 'bottle' },
|
|
45
|
+
{ name: 'Bottle label', paths: b.label, color: 'cream' },
|
|
46
|
+
{ name: 'Bottle foil', paths: b.foil, color: 'yellow' },
|
|
47
|
+
{ name: 'Cork', paths: b.cork, color: 'cork' },
|
|
48
|
+
{ name: 'Spray', paths: b.spray, color: 'cream' },
|
|
49
|
+
{ name: 'Pop', paths: [sparkle(w - 140 * s, h - 700 * s - safe.bottom * 0.3, 40 * s), sparkle(w - 290 * s, h - 660 * s - safe.bottom * 0.3, 26 * s)], color: 'yellow' },
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default defineTemplate({
|
|
54
|
+
id: 'social-holidays-new-years-eve-shop-hours',
|
|
55
|
+
meta: {
|
|
56
|
+
name: NAME,
|
|
57
|
+
description: 'A holiday-hours post for a neighbourhood wine shop, Christmas to New Year: confetti and streamers on deep teal, a bottle popping its cork, “Pop in before you pop the cork” in big heavy type, every day’s hours with the closed days marked, and the sparkling-wine offer in a yellow pill. 4:5 for the feed and 9:16 for stories',
|
|
58
|
+
category: 'social', tags: ['new year’s eve', 'new year', 'holiday hours', 'opening hours', 'wine shop', 'small business', 'shop', 'champagne', 'sale', 'confetti', 'instagram', 'story', 'post'],
|
|
59
|
+
platforms: ['instagram', 'facebook'], formats: ['portrait-post', 'story'], useCases: ['announcement', 'sale'],
|
|
60
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
61
|
+
},
|
|
62
|
+
stableNumbers: true,
|
|
63
|
+
build() {
|
|
64
|
+
const k = new Kit(NAME, preset('ig-portrait'))
|
|
65
|
+
k.quantizeGeometry = true
|
|
66
|
+
k.brand(
|
|
67
|
+
{ paper: '#0C4A47', text: '#FFF7E6', onPaper: '#F5C443', primary: '#F5C443', onPrimary: '#0C2E2C' },
|
|
68
|
+
{ name: 'Cork & Crate', site: 'corkandcrate.co' },
|
|
69
|
+
)
|
|
70
|
+
k.palette({ yellow: '#F5C443', coral: '#F2725E', mint: '#8FDCC2', cream: '#FFF7E6', bottle: '#14302C', cork: '#C99A5B', line: '#2E6B66' })
|
|
71
|
+
k.textStyle('Kicker 26', { ...SERIF, size: 34, fontWeight: 400, fontStyle: 'italic', lineHeight: 40 })
|
|
72
|
+
k.textStyle('Title 96', { size: 96, fontWeight: 900, letterSpacing: -3.6, lineHeight: 92 })
|
|
73
|
+
k.textStyle('Day 30', { size: 30, fontWeight: 700, lineHeight: 38, fontFeatures: { tnum: 1 } })
|
|
74
|
+
k.textStyle('Hours 30', { ...SERIF, size: 30, fontWeight: 400, lineHeight: 38, fontFeatures: { tnum: 1 } })
|
|
75
|
+
k.textStyle('Pill 28', { size: 28, fontWeight: 800, lineHeight: 34 })
|
|
76
|
+
k.textStyle('Foot 24', { size: 24, fontWeight: 600, letterSpacing: 1, lineHeight: 30 })
|
|
77
|
+
|
|
78
|
+
k.component('Hours row', { direction: 'HORIZONTAL', gap: 20, width: 620, justify: 'SPACE_BETWEEN', padding: { top: 9, right: 0, bottom: 9, left: 0 }, props: { Day: 'Mon 1 Jan', Hours: '9 – 5' } }, id => {
|
|
79
|
+
k.propText(id, 'Day', { style: 'Day 30', color: 'text', autoWidth: true, name: 'Day' })
|
|
80
|
+
k.propText(id, 'Hours', { style: 'Hours 30', color: 'text', autoWidth: true, name: 'Hours' })
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const sheet = k.sheet
|
|
84
|
+
const { width: W, height: H } = k.node(sheet)
|
|
85
|
+
k.flowSheet(sheet, 0, { top: 84, right: 340, bottom: 70, left: 84 }, 'paper', 'CENTER', 'MIN')
|
|
86
|
+
draw(k, sheet, art(W, H))
|
|
87
|
+
k.text(sheet, 'Holiday hours at {{brand.name}}', { style: 'Kicker 26', color: 'onPaper', width: 'FILL', name: 'Kicker' })
|
|
88
|
+
k.stack(sheet, { height: 22, name: 'Gap' })
|
|
89
|
+
k.text(sheet, 'Pop in before you pop the cork', { style: 'Title 96', color: 'text', width: 'FILL', name: 'Title' })
|
|
90
|
+
k.stack(sheet, { height: 34, name: 'Gap' })
|
|
91
|
+
const list = k.stack(sheet, { width: 620, name: 'Hours' })
|
|
92
|
+
for (const [i, [d, hrs]] of HOURS.entries()) {
|
|
93
|
+
k.instance(list, 'Hours row', { Day: d, Hours: hrs }, { name: 'Hours row', width: 'FILL' })
|
|
94
|
+
if (i < HOURS.length - 1) k.rect(list, 'FILL', 2, 'line', { name: 'Rule' })
|
|
95
|
+
}
|
|
96
|
+
k.stack(sheet, { height: 34, name: 'Gap' })
|
|
97
|
+
const pill = k.stack(sheet, { direction: 'HORIZONTAL', width: 'HUG', padding: { top: 16, right: 28, bottom: 16, left: 28 }, radius: 40, fill: 'primary', name: 'Offer' })
|
|
98
|
+
k.text(pill, '15% off any six bottles of bubbly, to 31 Dec', { style: 'Pill 28', color: 'onPrimary', autoWidth: true, name: 'Offer text' })
|
|
99
|
+
k.stack(sheet, { height: 26, name: 'Gap' })
|
|
100
|
+
k.text(sheet, '1408 Elm Street · {{brand.site}}', { style: 'Foot 24', color: 'onPaper', width: 'FILL', name: 'Foot' })
|
|
101
|
+
|
|
102
|
+
k.textStyle('Title 116', { size: 106, fontWeight: 900, letterSpacing: -4, lineHeight: 100 })
|
|
103
|
+
k.textStyle('Kicker 40', { ...SERIF, size: 40, fontWeight: 400, fontStyle: 'italic', lineHeight: 48 })
|
|
104
|
+
const story: VariantSpec = {
|
|
105
|
+
preset: 'ig-story',
|
|
106
|
+
adjust: (kit, id) => {
|
|
107
|
+
const { width: w, height: h } = kit.node(id)
|
|
108
|
+
const safe = verticalPadding(84)
|
|
109
|
+
redraw(kit, id, art(w, h, { top: safe.top - 84, bottom: safe.bottom - 84 }))
|
|
110
|
+
kit.patch(id, { padding: { top: safe.top, right: 260, bottom: safe.bottom + 160, left: safe.left } })
|
|
111
|
+
restyleAll(kit, id, [['Title', 'Title 116'], ['Kicker', 'Kicker 40']])
|
|
112
|
+
scaleGaps(kit, id, 1.25)
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
k.variants(sheet, [story])
|
|
116
|
+
return k.finish()
|
|
117
|
+
},
|
|
118
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// A neighbourhood bakery's Thanksgiving pie-order post, in a warm retro bakery style: a lattice-topped pie seen from
|
|
2
|
+
// above sits on a gingham cloth that runs along the foot, maple leaves blow across a mustard ground, "Pie orders close
|
|
3
|
+
// Sunday" is set big in a heavy serif, the four pies are listed with their prices, and pick-up is the day before
|
|
4
|
+
// Thanksgiving. 4:5 for the feed and 9:16 for stories, the drawing worked out again for each.
|
|
5
|
+
|
|
6
|
+
import { Kit, preset, type VariantSpec } from '../kit'
|
|
7
|
+
import { defineTemplate } from '../build'
|
|
8
|
+
import { SERIF } from '../shared/type'
|
|
9
|
+
import type { VectorPath } from '@/lib/document/types'
|
|
10
|
+
import { verticalPadding } from './shared'
|
|
11
|
+
import { disc, draw, maple, mapleVeins, polyP, rectPath, redraw, restyleAll, scaleGaps, type Drawn } from '../themes/holidays-shared'
|
|
12
|
+
|
|
13
|
+
const NAME = 'Thanksgiving pie orders post'
|
|
14
|
+
|
|
15
|
+
/** A lattice pie from above, `r` its radius: the tin, the crimped crust, the filling and the woven strips. */
|
|
16
|
+
function pie(cx: number, cy: number, r: number) {
|
|
17
|
+
const crimp: VectorPath = polyP(Array.from({ length: 72 }, (_, i) => { const a = (i * Math.PI) / 36, rr = r * (i % 2 ? 0.93 : 1); return { x: cx + Math.cos(a) * rr, y: cy + Math.sin(a) * rr } }))
|
|
18
|
+
const strips: VectorPath[] = [], under: VectorPath[] = []
|
|
19
|
+
const inner = r * 0.8, sw = r * 0.11
|
|
20
|
+
for (let i = -3; i <= 3; i++) {
|
|
21
|
+
const o = (i * inner) / 3.6, half = Math.sqrt(Math.max(0, inner * inner - o * o))
|
|
22
|
+
strips.push(rectPath(cx + o - sw / 2, cy - half, sw, half * 2))
|
|
23
|
+
under.push(rectPath(cx - half, cy + o - sw / 2, half * 2, sw))
|
|
24
|
+
}
|
|
25
|
+
const vents = [0, 1, 2, 3].map(i => disc(cx + Math.cos(i * Math.PI / 2 + 0.78) * r * 0.42, cy + Math.sin(i * Math.PI / 2 + 0.78) * r * 0.42, r * 0.03))
|
|
26
|
+
return { tin: [disc(cx, cy, r * 1.08)], crust: [crimp], filling: [disc(cx, cy, inner)], under, strips, vents }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function art(w: number, h: number, safe = { top: 0, bottom: 0 }): Drawn[] {
|
|
30
|
+
const s = w / 1080, B = 0
|
|
31
|
+
const clothTop = h - 230 * s - safe.bottom
|
|
32
|
+
const checks: VectorPath[] = [], checks2: VectorPath[] = []
|
|
33
|
+
const sz = 46 * s
|
|
34
|
+
for (let y = clothTop, row = 0; y < h + sz; y += sz, row++) for (let x = -B, col = 0; x < w + sz; x += sz, col++) {
|
|
35
|
+
if ((row + col) % 2 === 0) checks.push(rectPath(x, y, sz, sz))
|
|
36
|
+
if (row % 2 === 0 && col % 2 === 0) checks2.push(rectPath(x, y, sz, sz))
|
|
37
|
+
}
|
|
38
|
+
const p = pie(w - 250 * s, clothTop + 20 * s, 230 * s)
|
|
39
|
+
const leaves = [[0.06, 0.05, 80, 0.4], [0.88, 0.09, 140, -0.6], [0.76, 0.24, 96, 1.4], [0.95, 0.33, 110, 2.4], [0.8, 0.45, 84, -1.0], [0.93, 0.56, 70, 0.9], [0.74, 0.62, 56, 2.0], [0.42, 0.04, 54, -0.3]].map(([x, y, sz2, t]) => ({ m: maple(w * x, safe.top * 0.4 + h * y, sz2 * s, t), v: mapleVeins(w * x, safe.top * 0.4 + h * y, sz2 * s, t) }))
|
|
40
|
+
return [
|
|
41
|
+
{ name: 'Cloth', paths: checks, color: 'gingham', o: { opacity: 0.55 } },
|
|
42
|
+
{ name: 'Cloth weave', paths: checks2, color: 'gingham', o: { opacity: 0.5 } },
|
|
43
|
+
{ name: 'Pie shadow', paths: [disc(w - 236 * s, clothTop + 44 * s, 250 * s)], color: 'shadow', o: { opacity: 0.25 } },
|
|
44
|
+
{ name: 'Pie tin', paths: p.tin, color: 'tin' },
|
|
45
|
+
{ name: 'Pie crust', paths: p.crust, color: 'crust' },
|
|
46
|
+
{ name: 'Pie filling', paths: p.filling, color: 'filling' },
|
|
47
|
+
{ name: 'Lattice under', paths: p.under, color: 'crustDeep' },
|
|
48
|
+
{ name: 'Lattice', paths: p.strips, color: 'crust' },
|
|
49
|
+
{ name: 'Lattice edges', paths: p.strips, color: 'crustDeep', o: { strokeWidth: 2 * s, opacity: 0.6 } },
|
|
50
|
+
...leaves.map(l => ({ name: 'Leaf', paths: l.m, color: 'rust' })),
|
|
51
|
+
...leaves.map(l => ({ name: 'Leaf veins', paths: l.v, color: 'rustDeep', o: { strokeWidth: 2 * s } })),
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default defineTemplate({
|
|
56
|
+
id: 'social-holidays-thanksgiving-pie-orders-post',
|
|
57
|
+
meta: {
|
|
58
|
+
name: NAME,
|
|
59
|
+
description: 'A neighbourhood bakery’s Thanksgiving pie-order post in a warm retro style: a lattice pie from above on a gingham cloth, maple leaves blowing across a mustard ground, “Pie orders close Sunday” in a heavy serif, four pies with prices and the pick-up day. 4:5 for the feed and 9:16 for stories',
|
|
60
|
+
category: 'social', tags: ['thanksgiving', 'bakery', 'pie', 'pre-order', 'pumpkin pie', 'small business', 'autumn', 'fall', 'retro', 'gingham', 'instagram', 'story', 'post'],
|
|
61
|
+
platforms: ['instagram', 'facebook'], formats: ['portrait-post', 'story'], useCases: ['sale', 'announcement'],
|
|
62
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
63
|
+
},
|
|
64
|
+
stableNumbers: true,
|
|
65
|
+
build() {
|
|
66
|
+
const k = new Kit(NAME, preset('ig-portrait'))
|
|
67
|
+
k.quantizeGeometry = true
|
|
68
|
+
k.brand(
|
|
69
|
+
{ paper: '#E9B44C', text: '#3A1F10', onPaper: '#8A3412', primary: '#9C3D17', onPrimary: '#FFF6E5', line: '#C98F2C' },
|
|
70
|
+
{ name: 'Rise & Shine Bakery', site: 'riseandshinebakes.com' },
|
|
71
|
+
)
|
|
72
|
+
k.palette({ gingham: '#B5442A', tin: '#B9B3A8', crust: '#F3C77A', crustDeep: '#C98A3A', filling: '#A94A1E', shadow: '#5E3518', rust: '#B4512A', rustDeep: '#7E3216' })
|
|
73
|
+
k.textStyle('Kicker 26', { size: 26, fontWeight: 800, letterSpacing: 5, lineHeight: 34 })
|
|
74
|
+
k.textStyle('Title 100', { ...SERIF, size: 100, fontWeight: 800, letterSpacing: -2.6, lineHeight: 96 })
|
|
75
|
+
k.textStyle('Pie 32', { ...SERIF, size: 32, fontWeight: 600, lineHeight: 40 })
|
|
76
|
+
k.textStyle('Price 32', { size: 32, fontWeight: 800, lineHeight: 40, fontFeatures: { tnum: 1 } })
|
|
77
|
+
k.textStyle('Body 27', { size: 27, fontWeight: 500, lineHeight: 36 })
|
|
78
|
+
|
|
79
|
+
k.component('Pie line', { direction: 'HORIZONTAL', gap: 20, width: 620, justify: 'SPACE_BETWEEN', padding: { top: 10, right: 0, bottom: 10, left: 0 }, props: { Pie: 'Pie', Price: '$0' } }, id => {
|
|
80
|
+
k.propText(id, 'Pie', { style: 'Pie 32', color: 'text', autoWidth: true, name: 'Pie name' })
|
|
81
|
+
k.propText(id, 'Price', { style: 'Price 32', color: 'onPaper', autoWidth: true, name: 'Pie price' })
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const sheet = k.sheet
|
|
85
|
+
const { width: W, height: H } = k.node(sheet)
|
|
86
|
+
k.flowSheet(sheet, 0, { top: 150, right: 376, bottom: 260, left: 84 }, 'paper', 'MIN', 'MIN')
|
|
87
|
+
draw(k, sheet, art(W, H))
|
|
88
|
+
k.text(sheet, 'THANKSGIVING PIES', { style: 'Kicker 26', color: 'onPaper', width: 'FILL', name: 'Kicker' })
|
|
89
|
+
k.stack(sheet, { height: 16, name: 'Gap' })
|
|
90
|
+
k.text(sheet, 'Pie orders close Sunday', { style: 'Title 100', color: 'text', width: 'FILL', name: 'Title' })
|
|
91
|
+
k.stack(sheet, { height: 28, name: 'Gap' })
|
|
92
|
+
const list = k.stack(sheet, { width: 620, name: 'Pies' })
|
|
93
|
+
for (const [i, [n, p]] of [['Classic pumpkin', '$28'], ['Apple crumble', '$30'], ['Bourbon pecan', '$34'], ['Sweet potato', '$28']].entries()) {
|
|
94
|
+
k.instance(list, 'Pie line', { Pie: n, Price: p }, { name: 'Pie line', width: 'FILL' })
|
|
95
|
+
if (i < 3) k.rect(list, 'FILL', 2, 'line', { name: 'Rule' })
|
|
96
|
+
}
|
|
97
|
+
k.stack(sheet, { height: 24, name: 'Gap' })
|
|
98
|
+
k.text(sheet, 'Order by Sunday 22 November, pick up Wednesday 25th, 7 am – 6 pm. {{brand.site}}', { style: 'Body 27', color: 'text', width: 'FILL', name: 'Body' })
|
|
99
|
+
|
|
100
|
+
k.textStyle('Title 112', { ...SERIF, size: 112, fontWeight: 800, letterSpacing: -3, lineHeight: 106 })
|
|
101
|
+
const story: VariantSpec = {
|
|
102
|
+
preset: 'ig-story',
|
|
103
|
+
adjust: (kit, id) => {
|
|
104
|
+
const { width: w, height: h } = kit.node(id)
|
|
105
|
+
const safe = verticalPadding(84)
|
|
106
|
+
redraw(kit, id, art(w, h, { top: safe.top - 84, bottom: 0 }))
|
|
107
|
+
kit.patch(id, { padding: { top: safe.top + 120, right: w - safe.left - 620, bottom: safe.bottom, left: safe.left } })
|
|
108
|
+
restyleAll(kit, id, [['Title', 'Title 112']])
|
|
109
|
+
scaleGaps(kit, id, 1.3)
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
k.variants(sheet, [story])
|
|
113
|
+
return k.finish()
|
|
114
|
+
},
|
|
115
|
+
})
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// A florist's Valentine's pre-order post: a crowd of roses seen from above spills in from the top corner of a deep red
|
|
2
|
+
// ground, with leaves between them and loose petals drifting down, "Order your roses by 11 February" in a big serif,
|
|
3
|
+
// three bouquets with their prices in a list, and how delivery works. 4:5 for the feed and 9:16 for stories, the
|
|
4
|
+
// flowers worked out again for each and the words kept inside the apps' safe area.
|
|
5
|
+
|
|
6
|
+
import { Kit, preset, type VariantSpec } from '../kit'
|
|
7
|
+
import { defineTemplate } from '../build'
|
|
8
|
+
import { SERIF } from '../shared/type'
|
|
9
|
+
import { verticalPadding } from './shared'
|
|
10
|
+
import { draw, heart, leaf, redraw, restyleAll, rng, rose, scaleGaps, type Drawn } from '../themes/holidays-shared'
|
|
11
|
+
|
|
12
|
+
const NAME = 'Valentine’s florist pre-order post'
|
|
13
|
+
|
|
14
|
+
function art(w: number, h: number, top = 0): Drawn[] {
|
|
15
|
+
const s = w / 1080, r = rng(14)
|
|
16
|
+
// A crowd across the top, and two more down the right-hand margin, each group its own layers so none lies under the words.
|
|
17
|
+
const groups = [
|
|
18
|
+
[[0.86, 0.06, 120], [0.66, 0.03, 100], [0.98, 0.2, 104], [0.77, 0.19, 86], [0.47, 0.0, 84], [1.02, 0.02, 80], [0.28, -0.02, 76], [0.1, 0.03, 90], [-0.02, 0.15, 70], [0.2, 0.15, 56], [0.58, 0.16, 60], [0.38, 0.14, 48]],
|
|
19
|
+
[[0.95, 0.38, 70], [0.86, 0.33, 50]],
|
|
20
|
+
]
|
|
21
|
+
const y = (f: number) => top * 0.5 + f * 1350 * s
|
|
22
|
+
const parts = groups.map((g, gi) => {
|
|
23
|
+
const roses = g.map(([x, f, rr], i) => rose(w * x, y(f), rr * s, 20 + gi * 10 + i))
|
|
24
|
+
const leaves = g.flatMap(([x, f, rr]) => [0, 1].map(j => leaf({ x: w * x, y: y(f) }, rr * 1.3 * s, rr * 0.5 * s, 1.6 + j * 2.2 + r() * 0.6)))
|
|
25
|
+
return [
|
|
26
|
+
{ name: 'Leaves', paths: leaves, color: 'leaf' },
|
|
27
|
+
{ name: 'Roses outer', paths: roses.flatMap(x => x.outer), color: 'blush' },
|
|
28
|
+
{ name: 'Rose edges', paths: roses.flatMap(x => x.outer), color: 'ground', o: { strokeWidth: 2.4 * s, opacity: 0.3 } },
|
|
29
|
+
{ name: 'Roses middle', paths: roses.flatMap(x => x.mid), color: 'pink' },
|
|
30
|
+
{ name: 'Rose middle edges', paths: roses.flatMap(x => x.mid), color: 'ground', o: { strokeWidth: 2 * s, opacity: 0.3 } },
|
|
31
|
+
{ name: 'Rose hearts', paths: roses.flatMap(x => x.core), color: 'deep' },
|
|
32
|
+
{ name: 'Rose swirls', paths: roses.flatMap(x => x.swirl), color: 'blush', o: { strokeWidth: 2.4 * s, opacity: 0.8 } },
|
|
33
|
+
] as Drawn[]
|
|
34
|
+
})
|
|
35
|
+
const fall = [[0.84, 0.56, 20, 0.5], [0.95, 0.64, 16, -0.4], [0.88, 0.74, 13, 1.1], [0.96, 0.84, 15, 0.3], [0.86, 0.92, 11, -0.8]].map(([x, f, sz, t]) => heart(w * x, top + h * f, sz * 2 * s, t))
|
|
36
|
+
return [...parts.flat(), { name: 'Falling petals', paths: fall, color: 'blush', o: { opacity: 0.85 } }]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default defineTemplate({
|
|
40
|
+
id: 'social-holidays-valentines-florist-post',
|
|
41
|
+
meta: {
|
|
42
|
+
name: NAME,
|
|
43
|
+
description: 'A florist’s Valentine’s pre-order post: roses seen from above spill in from the corner of a deep red ground with petals drifting down, “Order your roses by 11 February” in a big serif, three bouquets with prices, and how delivery works. 4:5 for the feed and 9:16 for stories',
|
|
44
|
+
category: 'social', tags: ['valentine’s day', 'valentines', 'florist', 'flowers', 'roses', 'pre-order', 'flower shop', 'small business', 'delivery', 'instagram', 'story', 'post'],
|
|
45
|
+
platforms: ['instagram', 'facebook'], formats: ['portrait-post', 'story'], useCases: ['sale', 'announcement'],
|
|
46
|
+
created: '2026-10-10', updated: '2026-10-10',
|
|
47
|
+
},
|
|
48
|
+
stableNumbers: true,
|
|
49
|
+
build() {
|
|
50
|
+
const k = new Kit(NAME, preset('ig-portrait'))
|
|
51
|
+
k.quantizeGeometry = true
|
|
52
|
+
k.brand(
|
|
53
|
+
{ paper: '#7A1027', text: '#FFE9EC', onPaper: '#F7B7C2', primary: '#FFD2DA', onPrimary: '#5E0B1D', line: '#A43450' },
|
|
54
|
+
{ name: 'Petal & Stem', site: 'petalandstem.flowers' },
|
|
55
|
+
)
|
|
56
|
+
k.palette({ blush: '#F49AAE', pink: '#DB4A68', deep: '#A11E3C', leaf: '#3F6B4A', ground: '#7A1027' })
|
|
57
|
+
k.textStyle('Kicker 26', { size: 26, fontWeight: 700, letterSpacing: 5, lineHeight: 34 })
|
|
58
|
+
k.textStyle('Title 104', { ...SERIF, size: 104, fontWeight: 700, letterSpacing: -3, lineHeight: 100 })
|
|
59
|
+
k.textStyle('Item 32', { ...SERIF, size: 32, fontWeight: 600, lineHeight: 40 })
|
|
60
|
+
k.textStyle('Price 32', { size: 32, fontWeight: 800, lineHeight: 40, fontFeatures: { tnum: 1 } })
|
|
61
|
+
k.textStyle('Body 26', { size: 26, fontWeight: 400, lineHeight: 36 })
|
|
62
|
+
k.textStyle('Foot 24', { size: 24, fontWeight: 700, letterSpacing: 1, lineHeight: 30 })
|
|
63
|
+
|
|
64
|
+
k.component('Bouquet', { direction: 'HORIZONTAL', gap: 24, width: 696, justify: 'SPACE_BETWEEN', padding: { top: 14, right: 0, bottom: 14, left: 0 }, props: { Name: 'Bouquet', Price: '$0' } }, id => {
|
|
65
|
+
k.propText(id, 'Name', { style: 'Item 32', color: 'text', autoWidth: true, name: 'Bouquet name' })
|
|
66
|
+
k.propText(id, 'Price', { style: 'Price 32', color: 'onPaper', autoWidth: true, name: 'Bouquet price' })
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const sheet = k.sheet
|
|
70
|
+
const { width: W, height: H } = k.node(sheet)
|
|
71
|
+
k.flowSheet(sheet, 0, { top: 450, right: 300, bottom: 84, left: 84 }, 'paper', 'MIN', 'MIN')
|
|
72
|
+
draw(k, sheet, art(W, H))
|
|
73
|
+
k.text(sheet, 'VALENTINE’S DAY · SUNDAY 14 FEB', { style: 'Kicker 26', color: 'onPaper', width: 'FILL', name: 'Kicker' })
|
|
74
|
+
k.stack(sheet, { height: 20, name: 'Gap' })
|
|
75
|
+
k.text(sheet, 'Order your roses by 11 February', { style: 'Title 104', color: 'text', width: 'FILL', name: 'Title' })
|
|
76
|
+
k.stack(sheet, { height: 30, name: 'Gap' })
|
|
77
|
+
const list = k.stack(sheet, { width: 'FILL', name: 'Bouquets' })
|
|
78
|
+
for (const [i, [n, p]] of [['A dozen red roses', '$78'], ['Hand-tied spring posy', '$45'], ['One stem, bud vase', '$14']].entries()) {
|
|
79
|
+
k.instance(list, 'Bouquet', { Name: n, Price: p }, { name: 'Bouquet', width: 'FILL' })
|
|
80
|
+
if (i < 2) k.rect(list, 'FILL', 2, 'line', { name: 'Rule' })
|
|
81
|
+
}
|
|
82
|
+
k.stack(sheet, { height: 24, name: 'Gap' })
|
|
83
|
+
k.text(sheet, 'Delivered across the city on the 13th and 14th, or collect from the shop. Every bunch comes with a handwritten card.', { style: 'Body 26', color: 'onPaper', width: 'FILL', name: 'Body' })
|
|
84
|
+
k.stack(sheet, { height: 24, name: 'Gap' })
|
|
85
|
+
k.text(sheet, '{{brand.name}} · {{brand.site}}', { style: 'Foot 24', color: 'onPaper', width: 'FILL', name: 'Foot' })
|
|
86
|
+
|
|
87
|
+
k.textStyle('Title 116', { ...SERIF, size: 116, fontWeight: 700, letterSpacing: -3.4, lineHeight: 110 })
|
|
88
|
+
const story: VariantSpec = {
|
|
89
|
+
preset: 'ig-story',
|
|
90
|
+
adjust: (kit, id) => {
|
|
91
|
+
const { width: w, height: h } = kit.node(id)
|
|
92
|
+
const safe = verticalPadding(84)
|
|
93
|
+
redraw(kit, id, art(w, h, safe.top - 84))
|
|
94
|
+
kit.patch(id, { padding: { top: safe.top + 300, right: w - safe.left - 696, bottom: safe.bottom, left: safe.left } })
|
|
95
|
+
restyleAll(kit, id, [['Title', 'Title 116']])
|
|
96
|
+
scaleGaps(kit, id, 1)
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
k.variants(sheet, [story])
|
|
100
|
+
return k.finish()
|
|
101
|
+
},
|
|
102
|
+
})
|