@cogenta/starters 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/dist/blueprints/assets/photos/association/coat-bank.jpg +0 -0
  2. package/dist/blueprints/assets/photos/association/food-bank.jpg +0 -0
  3. package/dist/blueprints/assets/photos/association/garden-corner.jpg +0 -0
  4. package/dist/blueprints/assets/photos/association/garden-planting.jpg +0 -0
  5. package/dist/blueprints/assets/photos/association/hall.jpg +0 -0
  6. package/dist/blueprints/assets/photos/association/hero.jpg +0 -0
  7. package/dist/blueprints/assets/photos/association/homework-club.jpg +0 -0
  8. package/dist/blueprints/assets/photos/association/orientation.jpg +0 -0
  9. package/dist/blueprints/assets/photos/association/partner-fr-college.png +0 -0
  10. package/dist/blueprints/assets/photos/association/partner-fr-delcourt.png +0 -0
  11. package/dist/blueprints/assets/photos/association/partner-fr-fondation.png +0 -0
  12. package/dist/blueprints/assets/photos/association/partner-fr-habitat.png +0 -0
  13. package/dist/blueprints/assets/photos/association/partner-fr-mairie.png +0 -0
  14. package/dist/blueprints/assets/photos/association/partner-fr-pharmacie.png +0 -0
  15. package/dist/blueprints/assets/photos/association/supper.jpg +0 -0
  16. package/dist/blueprints/assets/photos/blog/desk-setup.jpg +0 -0
  17. package/dist/blueprints/assets/photos/blog/library.jpg +0 -0
  18. package/dist/blueprints/assets/photos/blog/notebook-and-coffee.jpg +0 -0
  19. package/dist/blueprints/assets/photos/blog/notebooks.jpg +0 -0
  20. package/dist/blueprints/assets/photos/blog/platform.jpg +0 -0
  21. package/dist/blueprints/assets/photos/blog/pour-over.jpg +0 -0
  22. package/dist/blueprints/assets/photos/blog/typing.jpg +0 -0
  23. package/dist/blueprints/assets/photos/documentation/delivery-flow-en.png +0 -0
  24. package/dist/blueprints/assets/photos/documentation/delivery-flow-fr.png +0 -0
  25. package/dist/blueprints/assets/photos/magazine/allotments.jpg +0 -0
  26. package/dist/blueprints/assets/photos/magazine/archive-stacks.jpg +0 -0
  27. package/dist/blueprints/assets/photos/magazine/baguettes.jpg +0 -0
  28. package/dist/blueprints/assets/photos/magazine/dining-room.jpg +0 -0
  29. package/dist/blueprints/assets/photos/magazine/empty-tram-stop.jpg +0 -0
  30. package/dist/blueprints/assets/photos/magazine/gallery.jpg +0 -0
  31. package/dist/blueprints/assets/photos/magazine/office-block-dusk.jpg +0 -0
  32. package/dist/blueprints/assets/photos/magazine/reading-room.jpg +0 -0
  33. package/dist/blueprints/assets/photos/magazine/reception-hall.jpg +0 -0
  34. package/dist/blueprints/assets/photos/magazine/theatre.jpg +0 -0
  35. package/dist/blueprints/assets/photos/magazine/town-hall.jpg +0 -0
  36. package/dist/blueprints/assets/photos/magazine/tram-stop.jpg +0 -0
  37. package/dist/blueprints/assets/photos/portfolio/coast-boats.jpg +0 -0
  38. package/dist/blueprints/assets/photos/portfolio/coast-cover.jpg +0 -0
  39. package/dist/blueprints/assets/photos/portfolio/coast-ridge.jpg +0 -0
  40. package/dist/blueprints/assets/photos/portfolio/coast-water.jpg +0 -0
  41. package/dist/blueprints/assets/photos/portfolio/forest-beech.jpg +0 -0
  42. package/dist/blueprints/assets/photos/portfolio/forest-birch.jpg +0 -0
  43. package/dist/blueprints/assets/photos/portfolio/forest-branches.jpg +0 -0
  44. package/dist/blueprints/assets/photos/portfolio/forest-cover.jpg +0 -0
  45. package/dist/blueprints/assets/photos/portfolio/glacier-cave.jpg +0 -0
  46. package/dist/blueprints/assets/photos/portfolio/glacier-cover.jpg +0 -0
  47. package/dist/blueprints/assets/photos/portfolio/glacier-melt.jpg +0 -0
  48. package/dist/blueprints/assets/photos/portfolio/glacier-tongue.jpg +0 -0
  49. package/dist/blueprints/assets/photos/portfolio/lagoon-cover.jpg +0 -0
  50. package/dist/blueprints/assets/photos/portfolio/lagoon-egret.jpg +0 -0
  51. package/dist/blueprints/assets/photos/portfolio/lagoon-shore.jpg +0 -0
  52. package/dist/blueprints/assets/photos/portfolio/lagoon-storm.jpg +0 -0
  53. package/dist/blueprints/assets/photos/portfolio/port-cover.jpg +0 -0
  54. package/dist/blueprints/assets/photos/portfolio/port-dusk.jpg +0 -0
  55. package/dist/blueprints/assets/photos/portfolio/port-marina.jpg +0 -0
  56. package/dist/blueprints/assets/photos/portfolio/port-quay.jpg +0 -0
  57. package/dist/blueprints/assets/photos/portfolio/towers-cover.jpg +0 -0
  58. package/dist/blueprints/assets/photos/portfolio/towers-green.jpg +0 -0
  59. package/dist/blueprints/assets/photos/portfolio/towers-single.jpg +0 -0
  60. package/dist/blueprints/assets/photos/portfolio/towers-valley.jpg +0 -0
  61. package/dist/blueprints/assets/photos/portfolio/vines-cover.jpg +0 -0
  62. package/dist/blueprints/assets/photos/portfolio/vines-grapes.jpg +0 -0
  63. package/dist/blueprints/assets/photos/portfolio/vines-shed.jpg +0 -0
  64. package/dist/blueprints/assets/photos/portfolio/vines-tree.jpg +0 -0
  65. package/dist/blueprints/assets/photos/restaurant/beetroot-carpaccio.jpg +0 -0
  66. package/dist/blueprints/assets/photos/restaurant/chocolate-tart.jpg +0 -0
  67. package/dist/blueprints/assets/photos/restaurant/cod-vin-jaune.jpg +0 -0
  68. package/dist/blueprints/assets/photos/restaurant/duck-confit.jpg +0 -0
  69. package/dist/blueprints/assets/photos/restaurant/grilled-octopus.jpg +0 -0
  70. package/dist/blueprints/assets/photos/restaurant/hero.jpg +0 -0
  71. package/dist/blueprints/assets/photos/restaurant/market-stall.jpg +0 -0
  72. package/dist/blueprints/assets/photos/restaurant/mushroom-risotto.jpg +0 -0
  73. package/dist/blueprints/assets/photos/restaurant/pike-quenelle.jpg +0 -0
  74. package/dist/blueprints/assets/photos/restaurant/poached-pear.jpg +0 -0
  75. package/dist/blueprints/assets/photos/restaurant/praline-cake.jpg +0 -0
  76. package/dist/blueprints/assets/photos/restaurant/pumpkin-soup.jpg +0 -0
  77. package/dist/blueprints/assets/photos/restaurant/street-burdeau.jpg +0 -0
  78. package/dist/blueprints/assets/photos/saas/api-webhooks-en.png +0 -0
  79. package/dist/blueprints/assets/photos/saas/api-webhooks-fr.png +0 -0
  80. package/dist/blueprints/assets/photos/saas/approvals-queue-en.png +0 -0
  81. package/dist/blueprints/assets/photos/saas/approvals-queue-fr.png +0 -0
  82. package/dist/blueprints/assets/photos/saas/audit-log-en.png +0 -0
  83. package/dist/blueprints/assets/photos/saas/audit-log-fr.png +0 -0
  84. package/dist/blueprints/assets/photos/saas/erp-sync-en.png +0 -0
  85. package/dist/blueprints/assets/photos/saas/erp-sync-fr.png +0 -0
  86. package/dist/blueprints/assets/photos/saas/logo-castellan-alimentaire.png +0 -0
  87. package/dist/blueprints/assets/photos/saas/logo-cliniques-du-vivier.png +0 -0
  88. package/dist/blueprints/assets/photos/saas/logo-marenne-logistique.png +0 -0
  89. package/dist/blueprints/assets/photos/saas/logo-nordgate-habitat.png +0 -0
  90. package/dist/blueprints/assets/photos/saas/logo-tessaly-energies.png +0 -0
  91. package/dist/blueprints/assets/photos/saas/office-london.jpg +0 -0
  92. package/dist/blueprints/assets/photos/saas/office-montreal.jpg +0 -0
  93. package/dist/blueprints/assets/photos/saas/office-paris.jpg +0 -0
  94. package/dist/blueprints/assets/photos/saas/office-toronto.jpg +0 -0
  95. package/dist/blueprints/assets/photos/saas/policy-editor-en.png +0 -0
  96. package/dist/blueprints/assets/photos/saas/policy-editor-fr.png +0 -0
  97. package/dist/blueprints/assets/photos/saas/security-racks.jpg +0 -0
  98. package/dist/blueprints/assets/photos/saas/spend-reporting-en.png +0 -0
  99. package/dist/blueprints/assets/photos/saas/spend-reporting-fr.png +0 -0
  100. package/dist/blueprints/assets/photos/saas/sso-scim-en.png +0 -0
  101. package/dist/blueprints/assets/photos/saas/sso-scim-fr.png +0 -0
  102. package/dist/blueprints/assets/photos/store/aleppo-soap.jpg +0 -0
  103. package/dist/blueprints/assets/photos/store/copper-pan.jpg +0 -0
  104. package/dist/blueprints/assets/photos/store/crochet-throw.jpg +0 -0
  105. package/dist/blueprints/assets/photos/store/enamel-mug.jpg +0 -0
  106. package/dist/blueprints/assets/photos/store/hero.jpg +0 -0
  107. package/dist/blueprints/assets/photos/store/knit-sweater.jpg +0 -0
  108. package/dist/blueprints/assets/photos/store/leather-boots.jpg +0 -0
  109. package/dist/blueprints/assets/photos/store/leather-duffel.jpg +0 -0
  110. package/dist/blueprints/assets/photos/store/leather-wallet.jpg +0 -0
  111. package/dist/blueprints/assets/photos/store/moka-pot.jpg +0 -0
  112. package/dist/blueprints/assets/photos/store/skillet.jpg +0 -0
  113. package/dist/blueprints/assets/photos/store/stoneware-bottles.jpg +0 -0
  114. package/dist/blueprints/assets/photos/store/story.jpg +0 -0
  115. package/dist/blueprints/assets/photos/store/turned-bowl.jpg +0 -0
  116. package/dist/blueprints/association-copy-en.d.ts +3 -0
  117. package/dist/blueprints/association-copy-en.d.ts.map +1 -0
  118. package/dist/blueprints/association-copy-en.js +834 -0
  119. package/dist/blueprints/association-copy-en.js.map +1 -0
  120. package/dist/blueprints/association-copy-fr.d.ts +3 -0
  121. package/dist/blueprints/association-copy-fr.d.ts.map +1 -0
  122. package/dist/blueprints/association-copy-fr.js +841 -0
  123. package/dist/blueprints/association-copy-fr.js.map +1 -0
  124. package/dist/blueprints/association-copy.d.ts +301 -0
  125. package/dist/blueprints/association-copy.d.ts.map +1 -0
  126. package/dist/blueprints/association-copy.js +17 -0
  127. package/dist/blueprints/association-copy.js.map +1 -0
  128. package/dist/blueprints/association-credits.d.ts +16 -0
  129. package/dist/blueprints/association-credits.d.ts.map +1 -0
  130. package/dist/blueprints/association-credits.js +80 -0
  131. package/dist/blueprints/association-credits.js.map +1 -0
  132. package/dist/blueprints/association-typography.d.ts +23 -0
  133. package/dist/blueprints/association-typography.d.ts.map +1 -0
  134. package/dist/blueprints/association-typography.js +55 -0
  135. package/dist/blueprints/association-typography.js.map +1 -0
  136. package/dist/blueprints/association.d.ts +86 -287
  137. package/dist/blueprints/association.d.ts.map +1 -1
  138. package/dist/blueprints/association.js +510 -1082
  139. package/dist/blueprints/association.js.map +1 -1
  140. package/dist/blueprints/blog-copy-en.d.ts +4 -0
  141. package/dist/blueprints/blog-copy-en.d.ts.map +1 -0
  142. package/dist/blueprints/blog-copy-en.js +701 -0
  143. package/dist/blueprints/blog-copy-en.js.map +1 -0
  144. package/dist/blueprints/blog-copy-fr.d.ts +12 -0
  145. package/dist/blueprints/blog-copy-fr.d.ts.map +1 -0
  146. package/dist/blueprints/blog-copy-fr.js +722 -0
  147. package/dist/blueprints/blog-copy-fr.js.map +1 -0
  148. package/dist/blueprints/blog-copy.d.ts +157 -0
  149. package/dist/blueprints/blog-copy.d.ts.map +1 -0
  150. package/dist/blueprints/blog-copy.js +2 -0
  151. package/dist/blueprints/blog-copy.js.map +1 -0
  152. package/dist/blueprints/blog-credits.d.ts +28 -0
  153. package/dist/blueprints/blog-credits.d.ts.map +1 -0
  154. package/dist/blueprints/blog-credits.js +73 -0
  155. package/dist/blueprints/blog-credits.js.map +1 -0
  156. package/dist/blueprints/blog-rich.d.ts +19 -0
  157. package/dist/blueprints/blog-rich.d.ts.map +1 -0
  158. package/dist/blueprints/blog-rich.js +51 -0
  159. package/dist/blueprints/blog-rich.js.map +1 -0
  160. package/dist/blueprints/blog.d.ts +51 -176
  161. package/dist/blueprints/blog.d.ts.map +1 -1
  162. package/dist/blueprints/blog.js +358 -947
  163. package/dist/blueprints/blog.js.map +1 -1
  164. package/dist/blueprints/content-pack.d.ts.map +1 -1
  165. package/dist/blueprints/content-pack.js +11 -16
  166. package/dist/blueprints/content-pack.js.map +1 -1
  167. package/dist/blueprints/content-packs.d.ts +2 -4
  168. package/dist/blueprints/content-packs.d.ts.map +1 -1
  169. package/dist/blueprints/content-packs.js +27 -15
  170. package/dist/blueprints/content-packs.js.map +1 -1
  171. package/dist/blueprints/documentation-copy-en.d.ts +3 -0
  172. package/dist/blueprints/documentation-copy-en.d.ts.map +1 -0
  173. package/dist/blueprints/documentation-copy-en.js +883 -0
  174. package/dist/blueprints/documentation-copy-en.js.map +1 -0
  175. package/dist/blueprints/documentation-copy-fr.d.ts +3 -0
  176. package/dist/blueprints/documentation-copy-fr.d.ts.map +1 -0
  177. package/dist/blueprints/documentation-copy-fr.js +939 -0
  178. package/dist/blueprints/documentation-copy-fr.js.map +1 -0
  179. package/dist/blueprints/documentation-copy.d.ts +52 -0
  180. package/dist/blueprints/documentation-copy.d.ts.map +1 -0
  181. package/dist/blueprints/documentation-copy.js +2 -0
  182. package/dist/blueprints/documentation-copy.js.map +1 -0
  183. package/dist/blueprints/documentation-writer.d.ts +67 -0
  184. package/dist/blueprints/documentation-writer.d.ts.map +1 -0
  185. package/dist/blueprints/documentation-writer.js +190 -0
  186. package/dist/blueprints/documentation-writer.js.map +1 -0
  187. package/dist/blueprints/documentation.d.ts +26 -29
  188. package/dist/blueprints/documentation.d.ts.map +1 -1
  189. package/dist/blueprints/documentation.js +106 -1064
  190. package/dist/blueprints/documentation.js.map +1 -1
  191. package/dist/blueprints/magazine-copy-en.d.ts +13 -0
  192. package/dist/blueprints/magazine-copy-en.d.ts.map +1 -0
  193. package/dist/blueprints/magazine-copy-en.js +553 -0
  194. package/dist/blueprints/magazine-copy-en.js.map +1 -0
  195. package/dist/blueprints/magazine-copy-fr.d.ts +14 -0
  196. package/dist/blueprints/magazine-copy-fr.d.ts.map +1 -0
  197. package/dist/blueprints/magazine-copy-fr.js +568 -0
  198. package/dist/blueprints/magazine-copy-fr.js.map +1 -0
  199. package/dist/blueprints/magazine-copy.d.ts +166 -0
  200. package/dist/blueprints/magazine-copy.d.ts.map +1 -0
  201. package/dist/blueprints/magazine-copy.js +58 -0
  202. package/dist/blueprints/magazine-copy.js.map +1 -0
  203. package/dist/blueprints/magazine-credits.d.ts +19 -0
  204. package/dist/blueprints/magazine-credits.d.ts.map +1 -0
  205. package/dist/blueprints/magazine-credits.js +109 -0
  206. package/dist/blueprints/magazine-credits.js.map +1 -0
  207. package/dist/blueprints/magazine.d.ts +30 -78
  208. package/dist/blueprints/magazine.d.ts.map +1 -1
  209. package/dist/blueprints/magazine.js +269 -690
  210. package/dist/blueprints/magazine.js.map +1 -1
  211. package/dist/blueprints/portfolio-copy-en.d.ts +4 -0
  212. package/dist/blueprints/portfolio-copy-en.d.ts.map +1 -0
  213. package/dist/blueprints/portfolio-copy-en.js +745 -0
  214. package/dist/blueprints/portfolio-copy-en.js.map +1 -0
  215. package/dist/blueprints/portfolio-copy-fr.d.ts +9 -0
  216. package/dist/blueprints/portfolio-copy-fr.d.ts.map +1 -0
  217. package/dist/blueprints/portfolio-copy-fr.js +737 -0
  218. package/dist/blueprints/portfolio-copy-fr.js.map +1 -0
  219. package/dist/blueprints/portfolio-copy.d.ts +204 -0
  220. package/dist/blueprints/portfolio-copy.d.ts.map +1 -0
  221. package/dist/blueprints/portfolio-copy.js +15 -0
  222. package/dist/blueprints/portfolio-copy.js.map +1 -0
  223. package/dist/blueprints/portfolio-credits.d.ts +18 -0
  224. package/dist/blueprints/portfolio-credits.d.ts.map +1 -0
  225. package/dist/blueprints/portfolio-credits.js +239 -0
  226. package/dist/blueprints/portfolio-credits.js.map +1 -0
  227. package/dist/blueprints/portfolio-typography.d.ts +19 -0
  228. package/dist/blueprints/portfolio-typography.d.ts.map +1 -0
  229. package/dist/blueprints/portfolio-typography.js +41 -0
  230. package/dist/blueprints/portfolio-typography.js.map +1 -0
  231. package/dist/blueprints/portfolio.d.ts +68 -157
  232. package/dist/blueprints/portfolio.d.ts.map +1 -1
  233. package/dist/blueprints/portfolio.js +475 -727
  234. package/dist/blueprints/portfolio.js.map +1 -1
  235. package/dist/blueprints/restaurant-copy-en.d.ts +3 -0
  236. package/dist/blueprints/restaurant-copy-en.d.ts.map +1 -0
  237. package/dist/blueprints/restaurant-copy-en.js +641 -0
  238. package/dist/blueprints/restaurant-copy-en.js.map +1 -0
  239. package/dist/blueprints/restaurant-copy-fr.d.ts +3 -0
  240. package/dist/blueprints/restaurant-copy-fr.d.ts.map +1 -0
  241. package/dist/blueprints/restaurant-copy-fr.js +665 -0
  242. package/dist/blueprints/restaurant-copy-fr.js.map +1 -0
  243. package/dist/blueprints/restaurant-copy.d.ts +234 -0
  244. package/dist/blueprints/restaurant-copy.d.ts.map +1 -0
  245. package/dist/blueprints/restaurant-copy.js +15 -0
  246. package/dist/blueprints/restaurant-copy.js.map +1 -0
  247. package/dist/blueprints/restaurant-credits.d.ts +18 -0
  248. package/dist/blueprints/restaurant-credits.d.ts.map +1 -0
  249. package/dist/blueprints/restaurant-credits.js +121 -0
  250. package/dist/blueprints/restaurant-credits.js.map +1 -0
  251. package/dist/blueprints/restaurant.d.ts +55 -128
  252. package/dist/blueprints/restaurant.d.ts.map +1 -1
  253. package/dist/blueprints/restaurant.js +425 -770
  254. package/dist/blueprints/restaurant.js.map +1 -1
  255. package/dist/blueprints/saas-copy-en.d.ts +3 -0
  256. package/dist/blueprints/saas-copy-en.d.ts.map +1 -0
  257. package/dist/blueprints/saas-copy-en.js +901 -0
  258. package/dist/blueprints/saas-copy-en.js.map +1 -0
  259. package/dist/blueprints/saas-copy-fr.d.ts +3 -0
  260. package/dist/blueprints/saas-copy-fr.d.ts.map +1 -0
  261. package/dist/blueprints/saas-copy-fr.js +912 -0
  262. package/dist/blueprints/saas-copy-fr.js.map +1 -0
  263. package/dist/blueprints/saas-copy.d.ts +266 -0
  264. package/dist/blueprints/saas-copy.d.ts.map +1 -0
  265. package/dist/blueprints/saas-copy.js +18 -0
  266. package/dist/blueprints/saas-copy.js.map +1 -0
  267. package/dist/blueprints/saas-credits.d.ts +22 -0
  268. package/dist/blueprints/saas-credits.d.ts.map +1 -0
  269. package/dist/blueprints/saas-credits.js +44 -0
  270. package/dist/blueprints/saas-credits.js.map +1 -0
  271. package/dist/blueprints/saas.d.ts +59 -203
  272. package/dist/blueprints/saas.d.ts.map +1 -1
  273. package/dist/blueprints/saas.js +505 -1108
  274. package/dist/blueprints/saas.js.map +1 -1
  275. package/dist/blueprints/store-copy-en.d.ts +4 -0
  276. package/dist/blueprints/store-copy-en.d.ts.map +1 -0
  277. package/dist/blueprints/store-copy-en.js +521 -0
  278. package/dist/blueprints/store-copy-en.js.map +1 -0
  279. package/dist/blueprints/store-copy-fr.d.ts +4 -0
  280. package/dist/blueprints/store-copy-fr.d.ts.map +1 -0
  281. package/dist/blueprints/store-copy-fr.js +521 -0
  282. package/dist/blueprints/store-copy-fr.js.map +1 -0
  283. package/dist/blueprints/store-copy.d.ts +219 -0
  284. package/dist/blueprints/store-copy.d.ts.map +1 -0
  285. package/dist/blueprints/store-copy.js +16 -0
  286. package/dist/blueprints/store-copy.js.map +1 -0
  287. package/dist/blueprints/store-credits.d.ts +3 -0
  288. package/dist/blueprints/store-credits.d.ts.map +1 -0
  289. package/dist/blueprints/store-credits.js +117 -0
  290. package/dist/blueprints/store-credits.js.map +1 -0
  291. package/dist/blueprints/store.d.ts +43 -231
  292. package/dist/blueprints/store.d.ts.map +1 -1
  293. package/dist/blueprints/store.js +466 -789
  294. package/dist/blueprints/store.js.map +1 -1
  295. package/dist/blueprints/vitrine-copy-en.d.ts.map +1 -1
  296. package/dist/blueprints/vitrine-copy-en.js +1 -0
  297. package/dist/blueprints/vitrine-copy-en.js.map +1 -1
  298. package/dist/blueprints/vitrine-copy-fr.d.ts.map +1 -1
  299. package/dist/blueprints/vitrine-copy-fr.js +1 -0
  300. package/dist/blueprints/vitrine-copy-fr.js.map +1 -1
  301. package/dist/blueprints/vitrine-copy.d.ts +1 -0
  302. package/dist/blueprints/vitrine-copy.d.ts.map +1 -1
  303. package/dist/blueprints/vitrine.d.ts +1 -0
  304. package/dist/blueprints/vitrine.d.ts.map +1 -1
  305. package/dist/blueprints/vitrine.js +24 -3
  306. package/dist/blueprints/vitrine.js.map +1 -1
  307. package/package.json +10 -10
  308. package/dist/blueprints/assets/photos/association/fundraising-dinner.jpg +0 -0
  309. package/dist/blueprints/assets/photos/association/volunteer-orientation.jpg +0 -0
  310. package/dist/blueprints/assets/photos/association/volunteer-portrait.jpg +0 -0
  311. package/dist/blueprints/assets/photos/association/winter-coat-collection.jpg +0 -0
  312. package/dist/blueprints/assets/photos/documentation/delivery-flow.png +0 -0
  313. package/dist/blueprints/assets/photos/magazine/archive-room.jpg +0 -0
  314. package/dist/blueprints/assets/photos/magazine/banquet-hall.jpg +0 -0
  315. package/dist/blueprints/assets/photos/magazine/city-hall.jpg +0 -0
  316. package/dist/blueprints/assets/photos/magazine/downtown-towers.jpg +0 -0
  317. package/dist/blueprints/assets/photos/magazine/gallery-opening.jpg +0 -0
  318. package/dist/blueprints/assets/photos/magazine/garden-volunteers.jpg +0 -0
  319. package/dist/blueprints/assets/photos/magazine/station-platform.jpg +0 -0
  320. package/dist/blueprints/assets/photos/magazine/theater-stage.jpg +0 -0
  321. package/dist/blueprints/assets/photos/portfolio/fenmore-branch.jpg +0 -0
  322. package/dist/blueprints/assets/photos/portfolio/fenmore-cover.jpg +0 -0
  323. package/dist/blueprints/assets/photos/portfolio/fenmore-stationery.jpg +0 -0
  324. package/dist/blueprints/assets/photos/portfolio/gannet-cover.jpg +0 -0
  325. package/dist/blueprints/assets/photos/portfolio/gannet-sardines.jpg +0 -0
  326. package/dist/blueprints/assets/photos/portfolio/gannet-shelf.jpg +0 -0
  327. package/dist/blueprints/assets/photos/portfolio/hollis-crane-cover.jpg +0 -0
  328. package/dist/blueprints/assets/photos/portfolio/hollis-crane-grid.jpg +0 -0
  329. package/dist/blueprints/assets/photos/portfolio/hollis-crane-salt-year.jpg +0 -0
  330. package/dist/blueprints/assets/photos/portfolio/hollis-crane-spines.jpg +0 -0
  331. package/dist/blueprints/assets/photos/portfolio/rookery-hall-brochure.jpg +0 -0
  332. package/dist/blueprints/assets/photos/portfolio/rookery-hall-cover.jpg +0 -0
  333. package/dist/blueprints/assets/photos/portfolio/rookery-hall-poster-carnival.jpg +0 -0
  334. package/dist/blueprints/assets/photos/portfolio/rookery-hall-poster-sibelius.jpg +0 -0
  335. package/dist/blueprints/assets/photos/portfolio/rookery-hall-series.jpg +0 -0
  336. package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-cover.jpg +0 -0
  337. package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-gate.jpg +0 -0
  338. package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-pictograms.jpg +0 -0
  339. package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-totem.jpg +0 -0
  340. package/dist/blueprints/assets/photos/portfolio/tidewater-cover.jpg +0 -0
  341. package/dist/blueprints/assets/photos/portfolio/tidewater-data.jpg +0 -0
  342. package/dist/blueprints/assets/photos/portfolio/tidewater-report-cover.jpg +0 -0
  343. package/dist/blueprints/assets/photos/portfolio/warp-and-weft-cover.jpg +0 -0
  344. package/dist/blueprints/assets/photos/portfolio/warp-and-weft-room.jpg +0 -0
  345. package/dist/blueprints/assets/photos/portfolio/westmoor-rail-components.jpg +0 -0
  346. package/dist/blueprints/assets/photos/portfolio/westmoor-rail-cover.jpg +0 -0
  347. package/dist/blueprints/assets/photos/portfolio/westmoor-rail-departures.jpg +0 -0
  348. package/dist/blueprints/assets/photos/restaurant/beetroot-goats-curd.jpg +0 -0
  349. package/dist/blueprints/assets/photos/restaurant/ceps-risotto.jpg +0 -0
  350. package/dist/blueprints/assets/photos/restaurant/duck-leg-red-cabbage.jpg +0 -0
  351. package/dist/blueprints/assets/photos/restaurant/hake-green-peppercorns.jpg +0 -0
  352. package/dist/blueprints/assets/photos/restaurant/vegetable-broth.jpg +0 -0
  353. package/dist/blueprints/assets/photos/saas/adrian-tan.jpg +0 -0
  354. package/dist/blueprints/assets/photos/saas/api-webhooks.png +0 -0
  355. package/dist/blueprints/assets/photos/saas/approvals-queue.png +0 -0
  356. package/dist/blueprints/assets/photos/saas/audit-log.png +0 -0
  357. package/dist/blueprints/assets/photos/saas/erp-sync.png +0 -0
  358. package/dist/blueprints/assets/photos/saas/policy-editor.png +0 -0
  359. package/dist/blueprints/assets/photos/saas/spend-reporting.png +0 -0
  360. package/dist/blueprints/assets/photos/saas/sso-scim.png +0 -0
  361. package/dist/blueprints/assets/photos/store/brushed-cotton-overshirt.jpg +0 -0
  362. package/dist/blueprints/assets/photos/store/cable-knit-beanie.jpg +0 -0
  363. package/dist/blueprints/assets/photos/store/canvas-shoulder-bag.jpg +0 -0
  364. package/dist/blueprints/assets/photos/store/canvas-tote.jpg +0 -0
  365. package/dist/blueprints/assets/photos/store/cast-iron-skillet.jpg +0 -0
  366. package/dist/blueprints/assets/photos/store/field-jacket.jpg +0 -0
  367. package/dist/blueprints/assets/photos/store/heavyweight-t-shirt.jpg +0 -0
  368. package/dist/blueprints/assets/photos/store/leather-card-holder.jpg +0 -0
  369. package/dist/blueprints/assets/photos/store/linen-table-runner.jpg +0 -0
  370. package/dist/blueprints/assets/photos/store/porcelain-pour-over-set.jpg +0 -0
  371. package/dist/blueprints/assets/photos/store/striped-wool-blanket.jpg +0 -0
@@ -1,144 +1,166 @@
1
1
  import { CogentaError } from '@cogenta/core';
2
2
  import { createContentStore, createMenuStore, createSiteSettingsStore, defineCollection, ensureMenuTables, ensureSiteSettingsTables, f, SITE_SETTINGS_SITE_SCOPE, siteSettingByKey, validateCollectionSet, } from '@cogenta/schema';
3
3
  import { coverArt, heroArt, logoArt } from '../demo-art/compositions.js';
4
+ import { ASSOCIATION_COPY_EN } from './association-copy-en.js';
5
+ import { ASSOCIATION_COPY_FR } from './association-copy-fr.js';
6
+ import { ASSOCIATION_PHOTO_CREDITS } from './association-credits.js';
7
+ import { typesetFrench } from './association-typography.js';
4
8
  import { definePageCollection, SEO_FIELDS, toBlockZoneEntry, } from './content-pack.js';
5
9
  import { STARTING_SKINS } from './starting-skins.js';
6
10
  /**
7
11
  * The `association` blueprint (L27, `@cogenta/theme-association`): a
8
- * neighbourhood charity in a northern English town, run from an old library
9
- * on Elm Street. It keeps a Thursday food bank, a homework club, a community
10
- * garden and a winter coat bank going with three hundred volunteers and two
11
- * part-time staff, and its site has the pages such a charity actually needs:
12
- * what it does, a calendar of dated events with a page each, how to
13
- * volunteer, how to give, where the money goes, its story and trustees, how
14
- * to reach it, and its privacy notice.
12
+ * neighbourhood charity run from an old library. It keeps a Thursday food
13
+ * bank, a homework club, a community garden and a winter clothing bank going
14
+ * with three hundred volunteers and two part-time staff, and its site has the
15
+ * pages such a charity actually needs: what it does, a calendar of dated events
16
+ * with a page each, how to volunteer, how to give, where the money goes, its
17
+ * story and trustees, how to reach it, and its privacy notice.
18
+ *
19
+ * Written in French and in English, not translated at render time: the
20
+ * installer picks the copy for the site's default locale, and with it the
21
+ * addresses (`/nos-actions/…` in French, `/what-we-do/…` in English), the
22
+ * collection names the administration shows and every word of demo content.
23
+ * The French site is a French "association loi 1901" (membership, general
24
+ * assembly, tax receipts at 66 %); the English one an English charity (Gift
25
+ * Aid, trustees, a charity number).
15
26
  *
16
27
  * **No payment system, and nothing that pretends to be one.** A blueprint
17
28
  * writes content; contract B has no payment block and nothing in a page can
18
- * take a card. So every "Donate" leads to a Donate page that says how to give
19
- * for real: a standing order set up with the donor's own bank from details
20
- * the treasurer sends on request, a cheque by post, cash at the hall on a
21
- * Thursday, Gift Aid, payroll giving and gifts in wills, with an email and a
22
- * telephone number that work on any device. The bank account number is sent
23
- * by the treasurer rather than printed on a page: a demonstration site left
24
- * online with a plausible account number on it would send real money to a
29
+ * take a card. So every "Donate" leads to a page that says how to give for
30
+ * real: a standing order set up with the donor's own bank from details the
31
+ * treasurer sends on request, a cheque by post, cash at the hall, with an email
32
+ * and a telephone number that work on any device. The bank account number is
33
+ * sent by the treasurer rather than printed on a page: a demonstration site
34
+ * left online with a plausible account number on it would send real money to a
25
35
  * stranger.
26
36
  *
27
- * The same honesty holds for volunteering: the Volunteer page names each
28
- * role and its hours, and signing up is an email, a telephone call or
29
- * turning up at the next orientation evening, which is a real event page.
30
- *
31
- * The organisation is the site: its name comes from `SeedContext.siteName`,
32
- * in the copy, the testimonial and the email addresses, and falls back to
33
- * `DEFAULT_ASSOCIATION_NAME`. The town, the street and the people are
34
- * invented. The telephone number is in Ofcom's range reserved for drama
35
- * (01632 960xxx), so it can never ring anyone.
37
+ * The organisation is the site: its name comes from `SeedContext.siteName`, in
38
+ * the copy, the testimonial and the email addresses, and falls back to the
39
+ * copy's `defaultName`. The town, the street and the people are invented, and
40
+ * so are the partners: their wordmarks were drawn for this blueprint. Every
41
+ * photograph is a real one under an open licence, credited on its own page;
42
+ * none shows a person presented as a volunteer or a beneficiary.
36
43
  */
37
- export const DEFAULT_ASSOCIATION_NAME = 'Riverside Neighbours';
38
- export const ASSOCIATION_PHONE = '01632 960418';
39
- const PHONE_LINK = 'tel:+441632960418';
40
- export const ASSOCIATION_ADDRESS = 'The Old Library, 220 Elm Street, Ashworth AW4 2LT';
41
- const HALL = 'The Old Library';
42
- const HALL_ADDRESS = '220 Elm Street, Ashworth AW4 2LT';
43
- export const CHARITY_NUMBER = '1299418';
44
+ export const DEFAULT_ASSOCIATION_NAME = ASSOCIATION_COPY_EN.defaultName;
45
+ /** The copy for a site's default locale: French for any `fr*` locale, English otherwise. */
46
+ export function associationCopyFor(locale) {
47
+ return locale.toLowerCase().startsWith('fr') ? FRENCH_COPY : ASSOCIATION_COPY_EN;
48
+ }
49
+ const FRENCH_COPY = typesetFrench(ASSOCIATION_COPY_FR);
44
50
  const PERMISSIONS = {
45
51
  read: ['public'],
46
52
  create: ['editor', 'admin'],
47
53
  update: ['editor', 'admin'],
48
54
  delete: ['admin'],
55
+ // An undeclared action grants nobody, admin included: without this line
56
+ // nothing on this site could ever leave draft.
57
+ publish: ['admin'],
49
58
  };
50
59
  /**
51
- * An event: a date and time (`date`, with `endsAt` when it has an end), a
52
- * place and its street address, what it costs and whether to book. The
53
- * theme reads these by name, lists events as a dated calendar and opens each
54
- * event's own page on its date.
60
+ * The content model, named and routed in the copy's language.
61
+ *
62
+ * An event has a date and time (`date`, with `endsAt` when it has an end), a
63
+ * place and its street address, what it costs and whether to book. The theme
64
+ * reads these by name, lists events as a dated calendar and opens each event's
65
+ * own page on its date. A programme is something the charity runs every week
66
+ * or every season.
55
67
  */
56
- export const event = defineCollection({
57
- name: 'event',
58
- labels: { singular: 'Event', plural: 'Events' },
59
- routing: { pattern: '/events/:slug' },
60
- fields: {
61
- title: f.text({ required: true, max: 200 }),
62
- slug: f.slug({ from: 'title', unique: true }),
63
- date: f.datetime({ required: true, admin: { label: 'Starts' } }),
64
- endsAt: f.datetime({ admin: { label: 'Ends' } }),
65
- location: f.text({
66
- max: 120,
67
- admin: { label: 'Place', help: 'The name of the room or venue.' },
68
- }),
69
- address: f.text({ max: 160, admin: { label: 'Street address' } }),
70
- cost: f.text({ max: 120, admin: { help: 'For example "Free" or "£25, £12 for under-16s".' } }),
71
- booking: f.text({ max: 160, admin: { help: 'Whether to book, and how.' } }),
72
- description: f.text({
73
- max: 300,
74
- multiline: true,
75
- admin: { label: 'Summary', help: 'One or two sentences, shown in the calendar.' },
76
- }),
77
- coverImage: f.media({ accept: ['image'] }),
78
- blocks: f.blocks(),
79
- ...SEO_FIELDS,
80
- },
81
- indexes: [['slug'], ['date']],
82
- permissions: PERMISSIONS,
83
- });
84
- /** A programme: something the charity runs every week or every season. */
85
- export const programme = defineCollection({
86
- name: 'programme',
87
- labels: { singular: 'Programme', plural: 'Programmes' },
88
- routing: { pattern: '/what-we-do/:slug' },
89
- fields: {
90
- title: f.text({ required: true, max: 120 }),
91
- slug: f.slug({ from: 'title', unique: true }),
92
- summary: f.text({ max: 300, multiline: true }),
93
- schedule: f.text({
94
- max: 120,
95
- admin: { label: 'When', help: 'For example "Thursdays, 5.30pm to 7.30pm".' },
96
- }),
97
- location: f.text({ max: 120, admin: { label: 'Place' } }),
98
- address: f.text({ max: 160, admin: { label: 'Street address' } }),
99
- audience: f.text({ max: 160, admin: { label: 'Who it is for' } }),
100
- cost: f.text({ max: 120 }),
101
- contact: f.text({ max: 120, admin: { help: 'An email address or a telephone number.' } }),
102
- coverImage: f.media({ accept: ['image'] }),
103
- blocks: f.blocks(),
104
- ...SEO_FIELDS,
105
- },
106
- indexes: [['slug']],
107
- permissions: PERMISSIONS,
108
- });
109
- export const page = definePageCollection('/:slug');
110
- export const ASSOCIATION_COLLECTIONS = [event, programme, page];
111
- validateCollectionSet(ASSOCIATION_COLLECTIONS);
68
+ export function associationSchema(copy) {
69
+ const { schema } = copy;
70
+ const event = defineCollection({
71
+ name: 'event',
72
+ versioning: { drafts: true, history: true },
73
+ labels: schema.labels.event,
74
+ routing: { pattern: schema.routes.event },
75
+ fields: {
76
+ title: f.text({ required: true, max: 200 }),
77
+ slug: f.slug({ from: 'title', unique: true }),
78
+ date: f.datetime({ required: true, admin: { label: schema.fields.starts } }),
79
+ endsAt: f.datetime({ admin: { label: schema.fields.ends } }),
80
+ location: f.text({
81
+ max: 120,
82
+ admin: { label: schema.fields.place, help: schema.help.place },
83
+ }),
84
+ address: f.text({ max: 160, admin: { label: schema.fields.address } }),
85
+ cost: f.text({ max: 120, admin: { help: schema.help.cost } }),
86
+ booking: f.text({ max: 160, admin: { help: schema.help.booking } }),
87
+ description: f.text({
88
+ max: 300,
89
+ multiline: true,
90
+ admin: { label: schema.fields.summary, help: schema.help.summary },
91
+ }),
92
+ coverImage: f.media({ accept: ['image'] }),
93
+ blocks: f.blocks(),
94
+ ...SEO_FIELDS,
95
+ },
96
+ indexes: [['slug'], ['date']],
97
+ permissions: PERMISSIONS,
98
+ });
99
+ const programme = defineCollection({
100
+ name: 'programme',
101
+ versioning: { drafts: true, history: true },
102
+ labels: schema.labels.programme,
103
+ routing: { pattern: schema.routes.programme },
104
+ fields: {
105
+ title: f.text({ required: true, max: 120 }),
106
+ slug: f.slug({ from: 'title', unique: true }),
107
+ summary: f.text({ max: 300, multiline: true }),
108
+ schedule: f.text({
109
+ max: 120,
110
+ admin: { label: schema.fields.when, help: schema.help.when },
111
+ }),
112
+ location: f.text({ max: 120, admin: { label: schema.fields.place } }),
113
+ address: f.text({ max: 160, admin: { label: schema.fields.address } }),
114
+ audience: f.text({ max: 160, admin: { label: schema.fields.audience } }),
115
+ cost: f.text({ max: 120 }),
116
+ contact: f.text({ max: 120, admin: { help: schema.help.contact } }),
117
+ coverImage: f.media({ accept: ['image'] }),
118
+ blocks: f.blocks(),
119
+ ...SEO_FIELDS,
120
+ },
121
+ indexes: [['slug']],
122
+ permissions: PERMISSIONS,
123
+ });
124
+ const page = definePageCollection('/:slug');
125
+ const collections = [event, programme, page];
126
+ validateCollectionSet(collections);
127
+ return { event, programme, page, collections };
128
+ }
112
129
  const BLOCK_VERSION = '1.0.0';
113
130
  // ---------------------------------------------------------------------------
114
131
  // The organisation's name and addresses
115
132
  // ---------------------------------------------------------------------------
116
- export function nameOf(siteName) {
133
+ export function nameOf(siteName, copy) {
117
134
  const trimmed = siteName?.trim();
118
- return trimmed === undefined || trimmed === '' ? DEFAULT_ASSOCIATION_NAME : trimmed;
135
+ return trimmed === undefined || trimmed === '' ? copy.defaultName : trimmed;
119
136
  }
120
137
  /** `volunteer@commonground.org.uk` for "Common Ground": the organisation's own name, as a mailbox. */
121
- export function associationEmail(siteName, mailbox = 'hello') {
122
- const domain = nameOf(siteName)
138
+ export function associationEmail(siteName, mailbox, copy) {
139
+ const domain = nameOf(siteName, copy)
123
140
  .normalize('NFKD')
124
141
  .replace(/[̀-ͯ]/g, '')
125
142
  .toLowerCase()
126
143
  .replace(/&/g, 'and')
127
144
  .replace(/[^a-z0-9]+/g, '');
128
- return `${mailbox}@${domain === '' ? 'neighbours' : domain}.org.uk`;
145
+ return `${mailbox}@${domain === '' ? copy.identity.emailFallback : domain}.${copy.identity.emailSuffix}`;
129
146
  }
130
- function mailto(siteName, mailbox, subject) {
131
- return `mailto:${associationEmail(siteName, mailbox)}?subject=${encodeURIComponent(subject)}`;
147
+ function contextFor(copy, siteName) {
148
+ const email = (mailbox = 'hello') => associationEmail(siteName, mailbox, copy);
149
+ return {
150
+ name: nameOf(siteName, copy),
151
+ email,
152
+ mailto: (mailbox, subject) => `mailto:${email(mailbox)}?subject=${encodeURIComponent(subject)}`,
153
+ };
132
154
  }
133
155
  /**
134
156
  * A paragraph where `[words](href)` becomes a link. Nothing else is
135
157
  * interpreted: the copy stays plain text a person can read in this file.
136
158
  */
137
159
  function textBlock(key, style, text, listItem) {
138
- const parts = text.split(/(\[[^\]]+\]\([^)]+\))/).filter((part) => part !== '');
160
+ const parts = text.split(/(\[[^\]]+\]\(\S+?\)(?=\s|$))/).filter((part) => part !== '');
139
161
  const markDefs = [];
140
162
  const children = parts.map((part, index) => {
141
- const link = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(part);
163
+ const link = /^\[([^\]]+)\]\((\S+?)\)$/.exec(part);
142
164
  if (link === null) {
143
165
  return { _key: `${key}-s${index}`, _type: 'span', text: part, marks: [] };
144
166
  }
@@ -177,23 +199,26 @@ function prose(key, lines) {
177
199
  };
178
200
  }
179
201
  function answers(key, items) {
180
- return items.map(([question, text], index) => ({
202
+ return items.map((item, index) => ({
181
203
  _key: `${key}-${index}`,
182
- question,
183
- answer: [textBlock(`${key}-${index}-a`, 'normal', text)],
204
+ question: item.question,
205
+ answer: [textBlock(`${key}-${index}-a`, 'normal', item.answer)],
184
206
  }));
185
207
  }
186
208
  function list(key, collection, options) {
209
+ // An event list is ordered by the event's own date and cut at "now" (L40,
210
+ // ADR-0038) — so a charity's "Coming up" really is what is coming up, and an
211
+ // evening that has happened leaves the page by itself. A programme has no
212
+ // date: it keeps the insertion order the seed writes it in.
213
+ const dated = collection === 'event';
187
214
  return {
188
215
  _key: key,
189
216
  _type: 'collectionList',
190
217
  _version: BLOCK_VERSION,
191
218
  ...(options.title === undefined ? {} : { title: options.title }),
192
219
  collection,
193
- // Insertion order: the seed writes programmes in the order the charity
194
- // presents them and events in date order; the theme sorts events by date
195
- // again, so an event added later still lands on its day.
196
- sort: { field: 'id', direction: 'asc' },
220
+ sort: dated ? { field: 'date', direction: 'asc' } : { field: 'id', direction: 'asc' },
221
+ ...(dated ? { filter: { date: { gte: '$now' } } } : {}),
197
222
  limit: options.limit,
198
223
  layout: options.layout,
199
224
  };
@@ -214,343 +239,125 @@ function mediaFigure(key, media, name, caption, ratio = 'original') {
214
239
  },
215
240
  ];
216
241
  }
217
- export const ASSOCIATION_PROGRAMMES = [
218
- {
219
- slug: 'thursday-food-bank',
220
- title: 'Thursday food bank',
221
- summary: 'A week of groceries for any household that asks, every Thursday evening. No referral, no forms, and fresh vegetables alongside the tins.',
222
- schedule: 'Thursdays, 5.30pm to 7.30pm',
223
- location: `${HALL}, main hall`,
224
- audience: 'Any household in Ashworth, with no referral needed',
225
- cost: 'Free',
226
- mailbox: 'foodbank',
227
- photo: 'programme-food-bank',
228
- body: [
229
- 'Every Thursday the main hall fills with tables of tins, pasta, rice, cereal, tea and toiletries, and a table of whatever the garden and the market traders have given that week. Each household leaves with a week of groceries chosen with a volunteer, about 14 kilos, packed into their own bags or one of ours.',
230
- '## How it works',
231
- 'Come to the side door on Elm Street any time between 5.30pm and 7.30pm. A volunteer takes your first name and the number of people at home, and nothing else. If you cannot carry a week of shopping, tell us: two drivers take parcels to 23 households who cannot get to the hall.',
232
- 'We ask no one to prove they need help. The three neighbours who started the food bank in 1994 decided that asking for proof would turn people away who needed it most, and every committee since has kept that rule.',
233
- '## Where the food comes from',
234
- 'About two thirds of what we hand out is given: collection points at Brindle & Sons and at two schools, two big collections a year at Brindle & Sons, and neighbours who leave a bag at the door. We buy the rest, mostly fresh food, milk, nappies and anything that runs short, which is where most of the money you give goes.',
235
- '## Giving food',
236
- 'The hall takes food on weekdays from 9.30am to 4pm. This month we are shortest of tinned fish, UHT milk, instant coffee and toiletries. Please do not bring anything past its date or opened.',
237
- ],
238
- },
239
- {
240
- slug: 'homework-club',
241
- title: 'Homework club',
242
- summary: 'Two quiet afternoons a week in the reading room for children aged 10 to 14, with volunteer tutors, laptops, a printer and something to eat.',
243
- schedule: 'Tuesdays and Thursdays, 3.30pm to 5.30pm, in term time',
244
- location: `${HALL}, upstairs reading room`,
245
- audience: 'Children aged 10 to 14 from any school in Ashworth',
246
- cost: 'Free',
247
- mailbox: 'homework',
248
- photo: 'programme-homework-club',
249
- body: [
250
- 'The homework club started in 2009, when a parent noticed that the reading room above the hall sat empty on weekday afternoons while her children had nowhere quiet to work. Today 46 children from nine schools come at least once a week.',
251
- '## An afternoon at the club',
252
- 'Children arrive from school, sign in and have toast or fruit first. Then they work at their own pace on whatever they have been set, with one tutor for every four children. Most of the tutors are sixth-form students from Ashworth College and two retired teachers who run the sessions.',
253
- 'We have twelve laptops, a printer, a shelf of revision guides for every GCSE subject we could find, and a rule that phones stay in bags until five.',
254
- '## Joining',
255
- 'A parent or carer fills in a one-page form at the hall or by email, with an emergency contact and anything we should know about health or learning. There is no waiting list at the moment for Tuesday; Thursday has four places left.',
256
- 'Every tutor has an enhanced DBS check, arranged and paid for by us, and completes our safeguarding session before their first afternoon.',
257
- ],
258
- },
259
- {
260
- slug: 'community-garden',
261
- title: 'Community garden',
262
- summary: 'Twelve raised beds behind the hall, planted and picked by anyone who wants to help. Last year they grew 1.4 tonnes of vegetables for the food bank.',
263
- schedule: 'Saturdays, 9.30am to 12.30pm, April to October',
264
- location: 'Elm Street garden, behind the hall',
265
- audience: 'Everyone, including children with an adult',
266
- cost: 'Free',
267
- mailbox: 'garden',
268
- photo: 'programme-garden',
269
- body: [
270
- 'The garden began as three raised beds on the edge of the car park in 2012. It now runs the length of the plot behind the hall: twelve beds, a polytunnel given by Kestrel Homes, two water butts and a shed of tools anyone can borrow on a Saturday.',
271
- '## What grows',
272
- 'We grow what the food bank hands out fastest: potatoes, onions, carrots, courgettes, beans, salad leaves and herbs. Everything picked on a Saturday morning is weighed, washed and on the food bank tables the following Thursday.',
273
- '## Coming along',
274
- 'Turn up on any Saturday from April to October. Gloves, tools and tea are provided, and one of the regular gardeners shows anyone new what needs doing. Nobody needs to know anything about plants; most of the jobs are digging, weeding and watering.',
275
- 'The raised beds are waist height at one end, so the garden works for anyone who cannot kneel.',
276
- ],
277
- },
278
- {
279
- slug: 'winter-coat-bank',
280
- title: 'Winter coat bank',
281
- summary: 'Warm coats, boots and school uniform jumpers, given by neighbours and sorted by size, free to any family who needs them from October to February.',
282
- schedule: 'Mondays, 10am to 1pm, October to February',
283
- location: `${HALL}, main hall`,
284
- audience: 'Adults and children in Ashworth',
285
- cost: 'Free',
286
- mailbox: 'coats',
287
- photo: 'programme-coat-bank',
288
- body: [
289
- 'Last winter the coat bank gave out 820 coats, 310 pairs of boots and 190 school jumpers. Every one of them was given by someone in the town, checked for zips and buttons, washed if it needed it and hung by size.',
290
- '## Choosing a coat',
291
- 'Come to the hall on a Monday morning between October and February and choose what fits. There is a changing screen, a mirror and a volunteer to help find a size. Families can take a coat for each person at home.',
292
- '## Giving a coat',
293
- 'We take clean coats, boots and jumpers in good condition at the hall on weekdays from September. We are always short of children’s coats in ages 8 to 13 and of men’s boots in large sizes.',
294
- '## Where the coats go',
295
- 'Anything we cannot use, because it is torn or the wrong season, goes to a textile recycler in Ashworth, who pays us by the kilo. Last winter that paid for the coat rails and the hangers.',
296
- ],
297
- },
298
- ];
299
- export function associationProgrammeBlocks(item, siteName) {
300
- const name = nameOf(siteName);
242
+ function iconItems(items, keyPrefix) {
243
+ return items.map((item, index) => ({
244
+ _key: `${keyPrefix}-${index}`,
245
+ ...(item.icon === undefined ? {} : { icon: item.icon }),
246
+ title: item.title,
247
+ text: item.text,
248
+ ...(item.href === undefined ? {} : { link: { href: item.href } }),
249
+ }));
250
+ }
251
+ function actions(items) {
252
+ return items.map((item) => ({
253
+ label: item.label,
254
+ target: { href: item.href },
255
+ ...(item.primary === true ? { emphasis: 'primary' } : {}),
256
+ }));
257
+ }
258
+ // ---------------------------------------------------------------------------
259
+ // Programmes and events
260
+ // ---------------------------------------------------------------------------
261
+ export function associationProgrammeBlocks(copy, item) {
301
262
  return [
302
- prose(`programme-${item.slug}-body`, item.body.map((line) => line.replaceAll('{name}', name))),
263
+ prose(`programme-${item.slug}-body`, item.body),
303
264
  list(`programme-${item.slug}-others`, 'programme', {
304
- title: 'More of what we do',
265
+ title: copy.programmeBlocks.othersTitle,
305
266
  layout: 'grid',
306
267
  limit: 4,
307
268
  }),
308
269
  ];
309
270
  }
310
- /**
311
- * Six dates, one to six weeks out. The copy never names a weekday, because
312
- * the day of the week depends on when the site is created.
313
- */
314
- export const ASSOCIATION_DEMO_EVENTS = [
315
- {
316
- title: 'Volunteer orientation evening',
317
- slug: 'volunteer-orientation-evening',
318
- daysFromNow: 6,
319
- start: [18, 0],
320
- end: [19, 30],
321
- location: `${HALL}, upstairs reading room`,
322
- address: HALL_ADDRESS,
323
- cost: 'Free',
324
- booking: 'No need to book, but tell us you are coming',
325
- description: 'An hour and a half for anyone thinking of volunteering: what each role involves, who runs it, and which shifts need people now.',
326
- photo: 'event-orientation',
327
- body: [
328
- 'Most of our 312 volunteers started at one of these evenings. The volunteer coordinator and the people who lead the food bank, the homework club, the garden and the coat bank each talk for ten minutes about what a shift is really like, then stay to answer questions over tea.',
329
- '## What happens next',
330
- 'Nobody signs up for anything on the night. If a role suits you, the coordinator arranges a first shift alongside an experienced volunteer. For the homework club we start the DBS check that week, which usually takes three to four weeks.',
331
- 'The reading room is up one flight of stairs. If stairs are difficult, tell us and we will meet in the main hall instead.',
332
- ],
333
- },
334
- {
335
- title: 'Food collection at Brindle & Sons',
336
- slug: 'food-collection-brindle-and-sons',
337
- daysFromNow: 11,
338
- start: [10, 0],
339
- end: [14, 0],
340
- location: 'Brindle & Sons car park',
341
- address: '14 Market Row, Ashworth AW4 1BS',
342
- cost: 'Free',
343
- booking: 'No booking needed',
344
- description: 'Our biggest collection of the year. Bring tins and dry food to the market car park, or give two hours to sort what arrives.',
345
- photo: 'programme-food-bank',
346
- body: [
347
- 'The last time we held this collection, one day brought in 2.1 tonnes of food, enough to fill the food bank shelves for eight weeks. Brindle & Sons lend us their car park and a van, and match every tenth tin with one of their own.',
348
- '## What to bring',
349
- 'Tinned fish and meat, tinned vegetables, UHT milk, rice, pasta sauce, tea, coffee, and toiletries. Please nothing opened, homemade or past its date.',
350
- '## Helping on the day',
351
- 'We need about 30 people across the day, in two-hour slots, to take donations at the tables and to sort them into crates inside the shop’s storeroom. Come for one slot or stay for all of it.',
352
- ],
353
- },
354
- {
355
- title: 'Planting morning in the garden',
356
- slug: 'planting-morning-in-the-garden',
357
- daysFromNow: 17,
358
- start: [9, 30],
359
- end: [12, 30],
360
- location: 'Elm Street garden, behind the hall',
361
- address: HALL_ADDRESS,
362
- cost: 'Free',
363
- booking: 'No booking needed',
364
- description: 'A morning of planting and clearing the raised beds and the polytunnel. Tools, gloves and tea provided, children welcome with an adult.',
365
- photo: 'programme-garden',
366
- body: [
367
- 'Six times a year the garden needs more hands than a regular session brings. We plant whatever the time of year allows in the open beds, sow salad leaves and herbs under the polytunnel, clear what has finished, and give the empty beds a fresh layer of compost.',
368
- '## Bring',
369
- 'Clothes that can get muddy and boots if you have them. We have gloves in every size, tools for about forty people and a kettle in the shed. There is a bed kept for children, with fast-growing radishes and sunflowers.',
370
- ],
371
- },
372
- {
373
- title: 'Coat and boot collection',
374
- slug: 'coat-and-boot-collection',
375
- daysFromNow: 24,
376
- start: [10, 0],
377
- end: [15, 0],
378
- location: `${HALL}, main hall`,
379
- address: HALL_ADDRESS,
380
- cost: 'Free',
381
- booking: 'Drop in any time',
382
- description: 'Bring clean coats, boots and school jumpers you no longer need. Everything is checked, sorted by size and kept for the coat bank.',
383
- photo: 'programme-coat-bank',
384
- body: [
385
- 'The coat bank is stocked by collections through the year, so there is a rail ready for every family when it opens. Bring what you can carry to the main hall between 10am and 3pm; a volunteer checks each coat with you at the door, so nothing needs to be sorted beforehand.',
386
- '## What we need most',
387
- 'Children’s coats for ages 8 to 13, men’s coats and boots in large sizes, and grey or black school jumpers in any size. We cannot take coats that are torn, stained or missing a zip.',
388
- ],
389
- },
390
- {
391
- title: 'Annual general meeting',
392
- slug: 'annual-general-meeting',
393
- daysFromNow: 31,
394
- start: [19, 0],
395
- end: [20, 30],
396
- location: `${HALL}, main hall`,
397
- address: HALL_ADDRESS,
398
- cost: 'Free',
399
- booking: 'Open to everyone; members vote',
400
- description: 'The trustees present the year’s accounts and annual report, two trustees stand for election, and anyone can ask a question.',
401
- body: [
402
- 'Once a year the trustees account for every pound we received and spent, and for what the four programmes achieved. The treasurer presents the accounts for the year to 31 March, independently examined, and the chair presents the annual report.',
403
- '## On the agenda',
404
- '- The annual report and accounts',
405
- '- The election of two trustees, one of them a volunteer at the food bank',
406
- '- The reserves policy, which the trustees propose to keep at three months of spending',
407
- '- Questions from the floor',
408
- 'Anyone may attend and ask a question. Members, meaning anyone who has volunteered or given in the past year and asked to join, may vote. Printed copies of the accounts are at the hall from two weeks before.',
409
- ],
410
- },
411
- {
412
- title: 'Community supper',
413
- slug: 'community-supper',
414
- daysFromNow: 38,
415
- start: [18, 30],
416
- end: [22, 0],
417
- location: 'Ashworth Town Hall, Wardle Room',
418
- address: 'Market Square, Ashworth AW4 1AA',
419
- cost: '£25, or £12 for under-16s',
420
- booking: 'Tickets from the hall or by email; 180 places',
421
- description: 'Three courses cooked by volunteers, a raffle, and a short word from the food bank team. Every ticket pays for four food parcels.',
422
- photo: 'event-supper',
423
- body: [
424
- 'Our one fundraising dinner of the year. Last year 170 people came and the evening raised £9,400, which paid for the fresh food at Thursday’s food bank for three months.',
425
- '## The evening',
426
- 'Doors open at 6.30pm, dinner is at 7pm, and the raffle is drawn between the main course and pudding. The menu is soup, a vegetable and bean casserole or roast chicken, and a fruit crumble, with a vegetarian or vegan option at every course if you tell us when you book.',
427
- 'The Wardle Room is step-free with an accessible toilet. The Market Square car park is free in the evening.',
428
- ],
429
- },
430
- ];
431
- /** ISO 8601, computed from "now" at scaffold time: never a fixed calendar date that ages into the past. */
432
- function futureIso(daysFromNow, [hour, minute]) {
433
- const date = new Date();
434
- date.setUTCDate(date.getUTCDate() + daysFromNow);
435
- date.setUTCHours(hour, minute, 0, 0);
436
- return date.toISOString();
437
- }
438
- export function associationEventBlocks(item) {
271
+ export function associationEventBlocks(copy, item) {
439
272
  return [
440
273
  prose(`event-${item.slug}-body`, item.body),
441
274
  list(`event-${item.slug}-more`, 'event', {
442
- title: 'Other dates at the hall',
275
+ title: copy.programmeBlocks.eventOthersTitle,
443
276
  layout: 'list',
444
277
  limit: 4,
445
278
  }),
446
279
  ];
447
280
  }
448
- export const ASSOCIATION_PARTNERS = [
449
- { key: 'partner-ashworth-college', name: 'Ashworth College' },
450
- { key: 'partner-ashworth-council', name: 'Ashworth Borough Council' },
451
- { key: 'partner-brindle-and-sons', name: 'Brindle & Sons' },
452
- { key: 'partner-kestrel-homes', name: 'Kestrel Homes' },
453
- { key: 'partner-linden-trust', name: 'The Linden Trust' },
454
- { key: 'partner-marlow-street-pharmacy', name: 'Marlow Street Pharmacy' },
455
- ];
456
- function homeBlocks(media, name) {
457
- const partners = ASSOCIATION_PARTNERS.flatMap((partner) => {
281
+ /** ISO 8601, computed from "now" at scaffold time: never a fixed calendar date that ages into the past. */
282
+ function futureIso(daysFromNow, [hour, minute]) {
283
+ const date = new Date();
284
+ date.setUTCDate(date.getUTCDate() + daysFromNow);
285
+ date.setUTCHours(hour, minute, 0, 0);
286
+ return date.toISOString();
287
+ }
288
+ /** The path of an entry of a routed collection: `/nos-actions/jardin-partage`. */
289
+ export function associationEntryPath(pattern, slug) {
290
+ return pattern.replace(':slug', slug);
291
+ }
292
+ /**
293
+ * Every page, in the order the charity would present them. A function of
294
+ * `media` (`SeedContext.media`) and of the site's name: several blocks need ids
295
+ * `seedDemoMedia` only knows at scaffold time, and the copy names the
296
+ * organisation the site belongs to. With no media, every picture block is left
297
+ * out rather than emitted empty.
298
+ */
299
+ export function buildAssociationDemoPages(copy, media, siteName) {
300
+ const context = contextFor(copy, siteName);
301
+ const words = copy.pages(context);
302
+ const slug = copy.pageSlugs;
303
+ const path = (key) => `/${slug[key]}`;
304
+ const partners = copy.partners.flatMap((partner) => {
458
305
  const id = media[partner.key];
459
- return id === undefined ? [] : [{ _key: partner.key, media: id }];
306
+ return id === undefined ? [] : [{ partner, id }];
460
307
  });
461
- return [
308
+ const home = [
462
309
  {
463
310
  _key: 'home-hero',
464
311
  _type: 'hero',
465
312
  _version: BLOCK_VERSION,
466
- eyebrow: 'A neighbourhood charity in Ashworth since 1994',
467
- title: 'No one in Ashworth should go hungry or face winter alone',
468
- subtitle: `${name} runs a food bank, a homework club, a community garden and a winter coat bank from the old library on Elm Street, with 312 volunteers and two part-time staff.`,
313
+ eyebrow: words.home.heroEyebrow,
314
+ title: words.home.heroTitle,
315
+ subtitle: words.home.heroSubtitle,
469
316
  ...(media.hero === undefined ? {} : { media: media.hero }),
470
- actions: [
471
- { label: 'Donate', target: { href: '/donate' }, emphasis: 'primary' },
472
- { label: 'Volunteer with us', target: { href: '/volunteer' } },
473
- ],
317
+ actions: actions([
318
+ { label: words.home.heroPrimary, href: path('donate'), primary: true },
319
+ { label: words.home.heroSecondary, href: path('volunteer') },
320
+ ]),
474
321
  },
475
322
  {
476
323
  _key: 'home-impact',
477
324
  _type: 'stats',
478
325
  _version: BLOCK_VERSION,
479
- title: 'Last year, in numbers',
480
- items: [
481
- {
482
- _key: 'parcels',
483
- value: '7,280',
484
- unit: 'parcels',
485
- label: 'of food handed out on Thursday evenings, each a week of groceries for one household.',
486
- },
487
- {
488
- _key: 'children',
489
- value: '46',
490
- unit: 'children',
491
- label: 'from nine schools came to homework club at least once a week.',
492
- },
493
- {
494
- _key: 'vegetables',
495
- value: '1.4',
496
- unit: 'tonnes',
497
- label: 'of vegetables grown in the garden went straight onto the food bank tables.',
498
- },
499
- {
500
- _key: 'volunteers',
501
- value: '312',
502
- unit: 'volunteers',
503
- label: 'gave 21,600 hours between them. Two part-time staff organise the rotas.',
504
- },
505
- ],
326
+ title: words.home.impactTitle,
327
+ items: words.home.impact.map((item, index) => ({ _key: `impact-${index}`, ...item })),
506
328
  },
507
- list('home-programmes', 'programme', { title: 'What we do', layout: 'list', limit: 4 }),
508
- list('home-events', 'event', { title: 'Coming up', layout: 'list', limit: 4 }),
329
+ list('home-programmes', 'programme', {
330
+ title: words.home.programmesTitle,
331
+ layout: 'list',
332
+ limit: 4,
333
+ }),
334
+ list('home-events', 'event', { title: words.home.eventsTitle, layout: 'list', limit: 4 }),
509
335
  {
510
336
  _key: 'home-story',
511
337
  _type: 'testimonial',
512
338
  _version: BLOCK_VERSION,
513
- quote: [
514
- textBlock('home-story-0', 'normal', 'My son was falling behind in maths and our flat is not a quiet place. I brought him to homework club for a term, and by the summer I was one of the people helping.'),
515
- textBlock('home-story-1', 'normal', `Five years on I run the Tuesday session. What keeps me at ${name} is that nobody here has ever made a child feel they needed to be helped.`),
516
- ],
517
- attribution: {
518
- name: 'Joanne Pryce',
519
- role: 'Homework club volunteer since 2019',
520
- ...(media.portrait === undefined ? {} : { avatar: media.portrait }),
521
- },
339
+ quote: words.home.storyQuote.map((text, index) => textBlock(`home-story-${index}`, 'normal', text)),
340
+ // No portrait: a testimonial's author is a character of the fiction, and a
341
+ // photograph of a real stranger must not stand for one.
342
+ attribution: { name: words.home.storyName, role: words.home.storyRole },
522
343
  },
523
344
  {
524
345
  _key: 'home-money',
525
346
  _type: 'statCounter',
526
347
  _version: BLOCK_VERSION,
527
- title: 'Where each pound goes',
528
- stats: [
529
- { _key: 'food', value: '38%', label: 'Food bought to fill the gaps in what is given' },
530
- {
531
- _key: 'programmes',
532
- value: '23%',
533
- label: 'The homework club, the garden and the winter coat bank',
534
- },
535
- { _key: 'hall', value: '17%', label: 'Heating, lighting, repairing and insuring the hall' },
536
- { _key: 'rest', value: '12%', label: 'The van, the accounts and fundraising' },
537
- {
538
- _key: 'staff',
539
- value: '10%',
540
- label: 'Two part-time staff who organise 312 volunteers',
541
- },
542
- ],
348
+ title: words.home.moneyTitle,
349
+ stats: words.home.money.map((item, index) => ({ _key: `money-${index}`, ...item })),
543
350
  },
544
351
  {
545
352
  _key: 'home-give',
546
353
  _type: 'cta',
547
354
  _version: BLOCK_VERSION,
548
- title: 'A monthly gift keeps Thursday going',
549
- text: 'We plan the food bank a month ahead, so a regular gift helps most. £5 a month buys the bread for one Thursday. £12 a month keeps one family in fresh vegetables all year. £30 a month pays for a child’s place at homework club.',
550
- actions: [
551
- { label: 'Ways to give', target: { href: '/donate' }, emphasis: 'primary' },
552
- { label: 'Read our accounts', target: { href: '/finances' } },
553
- ],
355
+ title: words.home.giveTitle,
356
+ text: words.home.giveText,
357
+ actions: actions([
358
+ { label: words.home.givePrimary, href: path('donate'), primary: true },
359
+ { label: words.home.giveSecondary, href: path('finances') },
360
+ ]),
554
361
  },
555
362
  ...(partners.length === 0
556
363
  ? []
@@ -559,425 +366,166 @@ function homeBlocks(media, name) {
559
366
  _key: 'home-partners',
560
367
  _type: 'logoStrip',
561
368
  _version: BLOCK_VERSION,
562
- caption: 'Working alongside us in Ashworth',
563
- logos: partners,
369
+ caption: words.home.partnersCaption,
370
+ logos: partners.map(({ partner, id }) => ({ _key: partner.key, media: id })),
564
371
  },
565
372
  ]),
566
373
  {
567
374
  _key: 'home-faq',
568
375
  _type: 'faq',
569
376
  _version: BLOCK_VERSION,
570
- title: 'Questions people ask us',
571
- items: answers('home-faq', [
572
- [
573
- 'Do I need a referral to use the food bank?',
574
- 'No. Come to the side door on a Thursday between 5.30pm and 7.30pm. We ask for a first name and how many people are at home, and nothing else.',
575
- ],
576
- [
577
- 'How much time do I need to give to volunteer?',
578
- 'As much as suits you. Some volunteers come every week for years; others help at two collections a year. Both keep things running.',
579
- ],
580
- [
581
- 'Can I give food instead of money?',
582
- 'Yes, at the hall on weekdays from 9.30am to 4pm. Money goes further for fresh food, so we buy that ourselves.',
583
- ],
584
- [
585
- 'Is the hall accessible?',
586
- 'The main hall and the toilets are step-free from the Elm Street entrance. The reading room is upstairs; homework club moves downstairs for any child who needs it.',
587
- ],
588
- ]),
377
+ title: words.home.faqTitle,
378
+ items: answers('home-faq', words.home.faq),
589
379
  },
590
380
  ];
591
- }
592
- function whatWeDoBlocks(name) {
593
- return [
594
- prose('what-intro', [
595
- `${name} runs four programmes from the old library on Elm Street. Each one started because a neighbour noticed something missing, and each is run by volunteers with a lead volunteer and one of our two part-time staff.`,
596
- 'None of them asks anyone to prove they need help, and none of them costs anything to use.',
597
- ]),
381
+ const whatWeDo = [
382
+ prose('what-intro', words.whatWeDo.intro),
598
383
  list('what-programmes', 'programme', { layout: 'list', limit: 20 }),
599
384
  {
600
385
  _key: 'what-steps',
601
386
  _type: 'featureGrid',
602
387
  _version: BLOCK_VERSION,
603
- title: 'Getting help from us',
604
- items: [
605
- {
606
- _key: 'come',
607
- title: 'Come to the hall',
608
- text: 'Every programme is open to anyone in Ashworth. Turn up at the times on each page, or ring the hall first if you would rather.',
609
- },
610
- {
611
- _key: 'ask',
612
- title: 'Tell us what you need',
613
- text: 'A volunteer asks your first name and what would help. Nothing is written down beyond that.',
614
- },
615
- {
616
- _key: 'more',
617
- title: 'Ask about anything else',
618
- text: 'If we cannot help, we know who can: debt advice, housing, benefits and the council’s support fund all hold sessions at the hall.',
619
- },
620
- ],
388
+ title: words.whatWeDo.stepsTitle,
389
+ items: iconItems(words.whatWeDo.steps, 'what-step'),
621
390
  },
622
391
  ];
623
- }
624
- function eventsBlocks(name) {
625
- return [
626
- prose('events-intro', [
627
- `Everything ${name} has planned for the next six weeks. Almost all of it is free and needs no booking; where it does, the event’s page says how.`,
628
- ]),
392
+ const events = [
393
+ prose('events-intro', words.events.intro),
629
394
  list('events-calendar', 'event', { layout: 'list', limit: 50 }),
630
395
  {
631
396
  _key: 'events-host',
632
397
  _type: 'cta',
633
398
  _version: BLOCK_VERSION,
634
399
  variant: { background: 'muted' },
635
- title: 'Holding something for the food bank',
636
- text: 'Schools, workplaces and clubs run collections and sponsored events for us every year. Tell us your date and we will lend you crates, posters and a volunteer to collect.',
637
- actions: [
400
+ title: words.events.hostTitle,
401
+ text: words.events.hostText,
402
+ actions: actions([
638
403
  {
639
- label: 'Write to the events team',
640
- target: { href: mailto(name, 'events', 'An event for the food bank') },
641
- emphasis: 'primary',
404
+ label: words.events.hostAction,
405
+ href: context.mailto(copy.identity.mailboxes.events, words.events.hostSubject),
406
+ primary: true,
642
407
  },
643
- ],
408
+ ]),
644
409
  },
645
410
  ];
646
- }
647
- function volunteerBlocks(media, name) {
648
- return [
649
- prose('volunteer-intro', [
650
- `${name} is 312 volunteers and two part-time staff. Volunteers pack the food parcels, tutor at homework club, dig the garden, sort the coats, drive the van and sit on the board of trustees.`,
651
- 'You do not need experience for any of it. Every new volunteer does a first shift alongside someone who has done it for years.',
652
- ]),
411
+ const volunteer = [
412
+ prose('volunteer-intro', words.volunteer.intro),
653
413
  {
654
414
  _key: 'volunteer-roles',
655
415
  _type: 'featureGrid',
656
416
  _version: BLOCK_VERSION,
657
- title: 'Where you could help',
658
- items: [
659
- {
660
- _key: 'food',
661
- icon: 'heart',
662
- title: 'Food bank',
663
- text: 'Thursdays, 5pm to 8pm. Setting out tables, helping households choose, packing bags. About 25 volunteers a week.',
664
- link: { href: '/what-we-do/thursday-food-bank' },
665
- },
666
- {
667
- _key: 'homework',
668
- icon: 'book',
669
- title: 'Homework club tutor',
670
- text: 'Tuesdays or Thursdays, 3.15pm to 5.45pm in term time. Aged 16 or over, with an enhanced DBS check we arrange.',
671
- link: { href: '/what-we-do/homework-club' },
672
- },
673
- {
674
- _key: 'garden',
675
- icon: 'leaf',
676
- title: 'Garden',
677
- text: 'Saturdays, 9.30am to 12.30pm, April to October. Digging, weeding, watering and picking. Children welcome with an adult.',
678
- link: { href: '/what-we-do/community-garden' },
679
- },
680
- {
681
- _key: 'coats',
682
- icon: 'gift',
683
- title: 'Coat bank',
684
- text: 'Mondays, 9.30am to 1.30pm, September to February. Checking, sorting and hanging coats, and helping families find a size.',
685
- link: { href: '/what-we-do/winter-coat-bank' },
686
- },
687
- {
688
- _key: 'driver',
689
- icon: 'truck',
690
- title: 'Driver',
691
- text: 'Thursdays, 4pm to 7pm. Collecting from shops and taking parcels to 23 households who cannot get to the hall. A full licence held for two years.',
692
- },
693
- {
694
- _key: 'visits',
695
- icon: 'users',
696
- title: 'Trustee',
697
- text: 'Six evening meetings a year. We are looking for a trustee with experience of property or building maintenance.',
698
- },
699
- ],
417
+ title: words.volunteer.rolesTitle,
418
+ items: iconItems(words.volunteer.roles, 'role'),
700
419
  },
701
420
  {
702
421
  _key: 'volunteer-steps',
703
422
  _type: 'featureGrid',
704
423
  _version: BLOCK_VERSION,
705
- title: 'How to start',
706
- items: [
707
- {
708
- _key: 'write',
709
- title: 'Write or ring',
710
- text: `Email ${associationEmail(name, 'volunteer')} or ring ${ASSOCIATION_PHONE}, and tell us what you would like to do and when you are free.`,
711
- },
712
- {
713
- _key: 'evening',
714
- title: 'Come to an orientation evening',
715
- text: 'Once a month, an hour and a half in the reading room with the people who run each programme.',
716
- link: { href: '/events/volunteer-orientation-evening' },
717
- },
718
- {
719
- _key: 'shift',
720
- title: 'Do a first shift alongside someone',
721
- text: 'Our coordinator pairs you with an experienced volunteer. After that, you choose how often to come.',
722
- },
723
- ],
424
+ title: words.volunteer.stepsTitle,
425
+ items: iconItems(words.volunteer.steps, 'step'),
724
426
  },
725
- ...mediaFigure('volunteer-photo', media, 'event-orientation', 'New volunteers meet the programme leads at an orientation evening in the reading room.', '16:9'),
427
+ ...mediaFigure('volunteer-photo', media, 'event-orientation', words.volunteer.photoCaption, '16:9'),
726
428
  {
727
429
  _key: 'volunteer-notes',
728
430
  _type: 'accordion',
729
431
  _version: BLOCK_VERSION,
730
- title: 'Before your first shift',
731
- items: answers('volunteer-notes', [
732
- [
733
- 'Is there a minimum age?',
734
- 'Sixteen for most roles and eighteen for driving. Younger children can help in the garden and at collections with a parent or carer.',
735
- ],
736
- [
737
- 'Do I need a DBS check?',
738
- 'Only for the homework club. We apply for an enhanced check for you and pay for it; it usually takes three to four weeks.',
739
- ],
740
- [
741
- 'What should I wear?',
742
- 'Closed shoes and clothes that can get dirty. We provide aprons, gloves and tools.',
743
- ],
744
- [
745
- 'Are expenses paid?',
746
- 'Yes. We pay mileage for drivers and bus fares for anyone who asks, with no receipts needed for under £10.',
747
- ],
748
- [
749
- 'Can my workplace volunteer as a team?',
750
- 'Yes, for the food collections, the coat collections and garden mornings, for groups of up to twelve. Write to us with a date.',
751
- ],
752
- ]),
432
+ title: words.volunteer.notesTitle,
433
+ items: answers('volunteer-notes', words.volunteer.notes),
753
434
  },
754
435
  {
755
436
  _key: 'volunteer-ask',
756
437
  _type: 'cta',
757
438
  _version: BLOCK_VERSION,
758
439
  variant: { background: 'muted' },
759
- title: 'Tell us you would like to help',
760
- text: `Our volunteer coordinator, Priya Nair, answers every message within two working days.`,
761
- actions: [
440
+ title: words.volunteer.askTitle,
441
+ text: words.volunteer.askText,
442
+ actions: actions([
762
443
  {
763
- label: 'Email the volunteer team',
764
- target: { href: mailto(name, 'volunteer', 'I would like to volunteer') },
765
- emphasis: 'primary',
444
+ label: words.volunteer.askAction,
445
+ href: context.mailto(copy.identity.mailboxes.volunteer, words.volunteer.askSubject),
446
+ primary: true,
766
447
  },
767
- { label: `Ring ${ASSOCIATION_PHONE}`, target: { href: PHONE_LINK } },
768
- ],
448
+ { label: words.volunteer.phoneAction, href: copy.identity.phoneHref },
449
+ ]),
769
450
  },
770
451
  ];
771
- }
772
- function donateBlocks(name) {
773
- const treasurer = associationEmail(name, 'treasurer');
774
- return [
775
- prose('donate-intro', [
776
- `Every pound given to ${name} is spent in Ashworth, on food, on the homework club, the garden and the coat bank, and on keeping the hall warm and open. Last year 62% of our income came from people in the town.`,
777
- 'We do not take card payments on this site. Here are the ways to give, and the people to ask.',
778
- ]),
452
+ const donate = [
453
+ prose('donate-intro', words.donate.intro),
779
454
  {
780
455
  _key: 'donate-monthly',
781
456
  _type: 'pricingTable',
782
457
  _version: BLOCK_VERSION,
783
- title: 'Give every month',
784
- tiers: [
785
- {
786
- _key: 'five',
787
- name: 'Bread',
788
- price: '£5',
789
- interval: 'a month',
790
- features: [
791
- 'Buys the bread for one Thursday food bank',
792
- 'With Gift Aid, worth £6.25 to us',
793
- ],
794
- },
795
- {
796
- _key: 'twelve',
797
- name: 'Vegetables',
798
- price: '£12',
799
- interval: 'a month',
800
- features: [
801
- 'Keeps one family in fresh vegetables all year',
802
- 'With Gift Aid, worth £15 to us',
803
- ],
804
- highlighted: true,
805
- },
806
- {
807
- _key: 'thirty',
808
- name: 'A place at homework club',
809
- price: '£30',
810
- interval: 'a month',
811
- features: [
812
- 'Pays for one child’s place, books and snacks included',
813
- 'With Gift Aid, worth £37.50 to us',
814
- ],
815
- },
816
- ],
458
+ title: words.donate.monthlyTitle,
459
+ tiers: words.donate.tiers.map((tier, index) => ({
460
+ _key: `tier-${index}`,
461
+ name: tier.name,
462
+ price: tier.price,
463
+ interval: tier.interval,
464
+ features: tier.features,
465
+ ...(tier.highlighted === true ? { highlighted: true } : {}),
466
+ })),
817
467
  },
818
- prose('donate-ways', [
819
- '## By standing order',
820
- `A standing order goes straight from your bank to ours, so none of your gift goes on fees. Email our treasurer at [${treasurer}](mailto:${treasurer}) or ring ${ASSOCIATION_PHONE}, and we send you our account details, a reference and a Gift Aid form. You set up the payment with your own bank, and can stop it at any time the same way.`,
821
- '## By cheque or cash',
822
- `Make cheques payable to ${name} and post them to ${ASSOCIATION_ADDRESS}. You can also give cash or a cheque at the hall on weekdays from 9.30am to 4pm, and on Thursday evenings; we give a receipt for every gift.`,
823
- '## Gift Aid',
824
- 'If you pay UK income tax, Gift Aid adds 25p to every pound you give, at no cost to you. Tick the box on our form or tell the person who takes your gift. Last year Gift Aid brought in £25,800.',
825
- '## Through your employer or your will',
826
- 'Payroll giving takes your gift before tax. Ask your payroll team for the Payroll Giving scheme and give our charity number, 1299418. A gift in your will, of any size, can be left to us by name; we are happy to talk it through in confidence.',
827
- ]),
468
+ prose('donate-ways', words.donate.ways),
828
469
  {
829
470
  _key: 'donate-questions',
830
471
  _type: 'accordion',
831
472
  _version: BLOCK_VERSION,
832
- title: 'Before you give',
833
- items: answers('donate-questions', [
834
- [
835
- 'Can I choose which programme my gift pays for?',
836
- 'Yes. Tell us when you give, and the treasurer records it against that programme. Gifts with no programme named go where they are needed most, usually the food bank.',
837
- ],
838
- [
839
- 'How much of my gift is spent on fundraising?',
840
- 'Three pence in every pound goes on fundraising, accounts and the independent examination. The full breakdown is on our finances page.',
841
- ],
842
- [
843
- 'Can I give food or coats instead?',
844
- 'Yes. The food bank and the coat bank pages list what we need most this month and when to bring it.',
845
- ],
846
- [
847
- 'How do I cancel a standing order?',
848
- 'With your bank, in the same way you set it up. You do not need to tell us, though we are always glad to hear why.',
849
- ],
850
- ]),
473
+ title: words.donate.questionsTitle,
474
+ items: answers('donate-questions', words.donate.questions),
851
475
  },
852
476
  {
853
477
  _key: 'donate-ask',
854
478
  _type: 'cta',
855
479
  _version: BLOCK_VERSION,
856
- title: 'Talk to our treasurer',
857
- text: 'Our treasurer, Martin Okafor, answers questions about giving, Gift Aid and gifts in wills, usually the same week.',
858
- actions: [
480
+ title: words.donate.askTitle,
481
+ text: words.donate.askText,
482
+ actions: actions([
859
483
  {
860
- label: 'Email the treasurer',
861
- target: { href: mailto(name, 'treasurer', 'Giving to the charity') },
862
- emphasis: 'primary',
484
+ label: words.donate.askAction,
485
+ href: context.mailto(copy.identity.mailboxes.treasurer, words.donate.askSubject),
486
+ primary: true,
863
487
  },
864
- { label: `Ring ${ASSOCIATION_PHONE}`, target: { href: PHONE_LINK } },
865
- ],
488
+ { label: words.donate.phoneAction, href: copy.identity.phoneHref },
489
+ ]),
866
490
  },
867
491
  ];
868
- }
869
- function financesBlocks(name) {
870
- return [
871
- prose('finances-intro', [
872
- `${name} is a charitable incorporated organisation registered in England and Wales, number ${CHARITY_NUMBER}. Our accounts for the year to 31 March are independently examined and filed with the Charity Commission.`,
873
- 'These are the figures from our last annual report. A printed copy of the full accounts is at the hall, and we post one to anyone who asks.',
874
- ]),
492
+ const finances = [
493
+ prose('finances-intro', words.finances.intro),
875
494
  {
876
495
  _key: 'finances-year',
877
496
  _type: 'stats',
878
497
  _version: BLOCK_VERSION,
879
- title: 'The year to 31 March',
880
- items: [
881
- {
882
- _key: 'income',
883
- value: '£286,400',
884
- label: 'received, from 1,940 people, 14 local businesses and 6 charitable trusts.',
885
- },
886
- {
887
- _key: 'spent',
888
- value: '£271,900',
889
- label: 'spent on the four programmes, the hall and the people who organise them.',
890
- },
891
- {
892
- _key: 'reserves',
893
- value: '£68,000',
894
- label: 'held in reserve, three months of spending, as our reserves policy requires.',
895
- },
896
- {
897
- _key: 'hours',
898
- value: '21,600',
899
- unit: 'hours',
900
- label: 'given by volunteers. We do not put a value on them in the accounts.',
901
- },
902
- ],
498
+ title: words.finances.yearTitle,
499
+ items: words.finances.year.map((item, index) => ({ _key: `year-${index}`, ...item })),
903
500
  },
904
501
  {
905
502
  _key: 'finances-spending',
906
503
  _type: 'statCounter',
907
504
  _version: BLOCK_VERSION,
908
- title: 'How we spent it',
909
- stats: [
910
- { _key: 'food', value: '38%', label: 'Food bought for the food bank' },
911
- {
912
- _key: 'hall',
913
- value: '17%',
914
- label: 'Running the hall: heating, lighting, repairs, insurance',
915
- },
916
- {
917
- _key: 'homework',
918
- value: '12%',
919
- label: 'Homework club: books, laptops, snacks and DBS checks',
920
- },
921
- { _key: 'coats', value: '11%', label: 'The coat bank, the garden and emergency help' },
922
- {
923
- _key: 'staff',
924
- value: '10%',
925
- label: 'A part-time coordinator and a part-time volunteer manager',
926
- },
927
- { _key: 'van', value: '9%', label: 'The van: fuel, insurance and repairs' },
928
- {
929
- _key: 'admin',
930
- value: '3%',
931
- label: 'Fundraising, accounts and the independent examination',
932
- },
933
- ],
505
+ title: words.finances.spendingTitle,
506
+ stats: words.finances.spending.map((item, index) => ({ _key: `spent-${index}`, ...item })),
934
507
  },
935
508
  {
936
509
  _key: 'finances-income',
937
510
  _type: 'statCounter',
938
511
  _version: BLOCK_VERSION,
939
- title: 'Where it came from',
940
- stats: [
941
- { _key: 'people', value: '46%', label: 'Gifts from individuals, most of them monthly' },
942
- { _key: 'trusts', value: '27%', label: 'Grants from six charitable trusts' },
943
- { _key: 'business', value: '11%', label: 'Local businesses, in money and in food' },
944
- { _key: 'giftaid', value: '9%', label: 'Gift Aid claimed on individual gifts' },
945
- { _key: 'events', value: '7%', label: 'The community supper and sponsored events' },
946
- ],
512
+ title: words.finances.incomeTitle,
513
+ stats: words.finances.income.map((item, index) => ({ _key: `income-${index}`, ...item })),
947
514
  },
948
- prose('finances-notes', [
949
- '## Why the hall costs so little',
950
- 'We bought the old library outright in 2001, with money raised over six years, so we pay no rent. The 17% it costs is heating a Victorian building, keeping the roof sound and insuring it.',
951
- '## What we do not spend money on',
952
- 'Nobody is paid to fundraise, the trustees are unpaid and claim no expenses, and we have no offices other than the hall. When we spend on something new, the trustees agree it at a meeting and the minutes are available at the hall.',
953
- ]),
515
+ prose('finances-notes', words.finances.notes),
954
516
  ];
955
- }
956
- function aboutBlocks(media, name) {
957
- const gallery = ['event-supper', 'programme-garden', 'programme-coat-bank', 'event-orientation']
517
+ const gallery = ['event-planting', 'event-supper', 'programme-garden', 'programme-coat-bank']
958
518
  .map((key) => media[key])
959
519
  .filter((id) => id !== undefined);
960
- const partners = ASSOCIATION_PARTNERS.flatMap((partner) => {
961
- const id = media[partner.key];
962
- return id === undefined ? [] : [{ _key: partner.key, media: id, name: partner.name }];
963
- });
964
- return [
965
- prose('about-story', [
966
- `${name} started in the winter of 1994, when three neighbours on Elm Street, a retired teacher, a bus driver and a nurse working nights, borrowed a van and took hot meals to eleven households who had run out of money for food.`,
967
- 'By the next winter they had forty volunteers and a spare room above the hardware shop. In 2001, after six years of jumble sales and sponsored walks, the charity bought the old public library on Elm Street, so that no landlord could ever close the food bank.',
968
- 'The homework club opened in the library’s reading room in 2009, the garden in 2012 and the coat bank in 2016. Each began because someone who came to the hall noticed something missing and offered to start it.',
969
- ]),
520
+ const about = [
521
+ prose('about-story', words.about.story),
970
522
  {
971
523
  _key: 'about-years',
972
524
  _type: 'statCounter',
973
525
  _version: BLOCK_VERSION,
974
- stats: [
975
- { _key: 'founded', value: '1994', label: 'The first van of hot meals' },
976
- { _key: 'hall', value: '2001', label: 'The old library bought outright' },
977
- { _key: 'programmes', value: '4', label: 'Programmes running every week' },
978
- { _key: 'volunteers', value: '312', label: 'Volunteers on the rota today' },
979
- ],
526
+ stats: words.about.years.map((item, index) => ({ _key: `year-${index}`, ...item })),
980
527
  },
528
+ ...mediaFigure('about-hall', media, 'hall', words.about.hallCaption, '16:9'),
981
529
  ...(gallery.length < 2
982
530
  ? []
983
531
  : [
@@ -989,25 +537,14 @@ function aboutBlocks(media, name) {
989
537
  items: gallery.map((id, index) => ({ _key: `about-gallery-${index}`, media: id })),
990
538
  },
991
539
  ]),
992
- prose('about-trustees', [
993
- '## Our trustees',
994
- 'Six unpaid trustees are responsible for the charity. They are elected at the annual general meeting for three years, and at least two of them are always volunteers.',
995
- '- Helen Ashby, chair. A retired head teacher and a homework club tutor since 2010.',
996
- '- Martin Okafor, treasurer. A chartered accountant who works for the NHS trust.',
997
- '- Sarah Whitlock, secretary. A solicitor, and a driver on Thursdays.',
998
- '- Daniel Reyes. Runs a building firm in the town and looks after the hall.',
999
- '- Amina Hussain. A pharmacist on Marlow Street and a food bank volunteer.',
1000
- '- Tom Garside. A student at Ashworth College, elected by the volunteers.',
1001
- '## Our staff',
1002
- 'Priya Nair coordinates the programmes and Colin Birch manages volunteers. Both work three days a week, and both started as volunteers.',
1003
- ]),
540
+ prose('about-people', words.about.people),
1004
541
  {
1005
542
  _key: 'about-quote',
1006
543
  _type: 'quote',
1007
544
  _version: BLOCK_VERSION,
1008
- text: 'We never set out to be a charity. We just could not stand the thought of a neighbour going without dinner.',
1009
- author: 'Margaret Heald',
1010
- role: 'One of the three founders, in 1994',
545
+ text: words.about.quote,
546
+ author: words.about.quoteAuthor,
547
+ role: words.about.quoteRole,
1011
548
  },
1012
549
  ...(partners.length === 0
1013
550
  ? []
@@ -1016,297 +553,154 @@ function aboutBlocks(media, name) {
1016
553
  _key: 'about-partners',
1017
554
  _type: 'logos',
1018
555
  _version: BLOCK_VERSION,
1019
- title: 'The organisations we work with',
1020
- items: partners,
556
+ title: words.about.partnersTitle,
557
+ items: partners.map(({ partner, id }) => ({
558
+ _key: partner.key,
559
+ media: id,
560
+ name: partner.name,
561
+ })),
1021
562
  },
1022
563
  ]),
1023
564
  ];
1024
- }
1025
- function contactBlocks(name) {
1026
- return [
1027
- prose('contact-intro', [
1028
- `The quickest way to reach ${name} is to ring the hall or come in. Someone is there on weekdays from 9.30am to 4pm, and on Thursday evenings until 8pm.`,
1029
- ]),
565
+ const contact = [
566
+ prose('contact-intro', words.contact.intro),
1030
567
  {
1031
568
  _key: 'contact-ways',
1032
569
  _type: 'featureGrid',
1033
570
  _version: BLOCK_VERSION,
1034
- items: [
1035
- {
1036
- _key: 'phone',
1037
- icon: 'phone',
1038
- title: `Ring ${ASSOCIATION_PHONE}`,
1039
- text: 'Weekdays from 9.30am to 4pm. Leave a message at other times and we ring back the next working day.',
1040
- link: { href: PHONE_LINK },
1041
- },
1042
- {
1043
- _key: 'email',
1044
- icon: 'mail',
1045
- title: 'Write to us',
1046
- text: `${associationEmail(name)}, for anything at all. Each programme page also gives the address of the people who run it.`,
1047
- link: { href: `mailto:${associationEmail(name)}` },
1048
- },
1049
- {
1050
- _key: 'visit',
1051
- icon: 'map-pin',
1052
- title: 'Visit the hall',
1053
- text: `${ASSOCIATION_ADDRESS}. The 14 and 22 buses stop outside; the Elm Street entrance is step-free.`,
1054
- link: { href: 'https://www.openstreetmap.org/' },
1055
- },
1056
- {
1057
- _key: 'hours',
1058
- icon: 'clock',
1059
- title: 'Opening hours',
1060
- text: 'Monday to Friday, 9.30am to 4pm. Thursday evenings, 5.30pm to 8pm. Saturday mornings in the garden, April to October.',
1061
- },
1062
- ],
571
+ items: iconItems(words.contact.ways, 'way'),
1063
572
  },
1064
- prose('contact-concerns', [
1065
- '## Raising a concern',
1066
- `If you are worried about the safety of a child or an adult at any of our programmes, speak to Priya Nair, our safeguarding lead, on ${ASSOCIATION_PHONE}, or to any trustee. If someone is in immediate danger, ring 999.`,
1067
- `If you are unhappy with how we have treated you, write to the chair of trustees at ${ASSOCIATION_ADDRESS}. We reply within ten working days.`,
1068
- ]),
573
+ prose('contact-concerns', words.contact.concerns),
1069
574
  ];
1070
- }
1071
- function privacyBlocks(name) {
1072
- return [
1073
- prose('privacy-body', [
1074
- `${name} (registered charity ${CHARITY_NUMBER}) is responsible for the personal information described on this page. You can reach us at ${ASSOCIATION_ADDRESS}, or by email at ${associationEmail(name, 'privacy')}.`,
1075
- '## What we keep',
1076
- '- For the food bank: a first name and the number of people at home, on paper, destroyed at the end of each month.',
1077
- '- For donors: name, address and the gifts you make, and a Gift Aid declaration if you give one.',
1078
- '- For volunteers: contact details, an emergency contact, the shifts you do and, for homework club tutors, the date of a DBS check.',
1079
- '- For homework club: a parent or carer’s contact details and anything they tell us about the child’s health or learning.',
1080
- '## Why, and for how long',
1081
- 'We keep donation records for six years after your last gift, because HMRC requires it for Gift Aid. We keep volunteer details while you volunteer and for a year afterwards. We never sell or share personal information, and we do not use it for anything other than the reason you gave it.',
1082
- '## This website',
1083
- 'This site sets no advertising or tracking cookies. If you choose a light or dark appearance, your browser remembers the choice on your own device.',
1084
- '## Your rights',
1085
- 'You can ask to see, correct or delete what we hold about you, at any time and at no cost. If you are unhappy with how we have handled your information, you can complain to the Information Commissioner’s Office.',
575
+ const credits = [
576
+ prose('credits-body', [
577
+ ...words.credits.intro,
578
+ ...ASSOCIATION_PHOTO_CREDITS.map((credit) => `- ${credit.title} · ${credit.author} · ${credit.licenceUrl === undefined
579
+ ? credit.licence
580
+ : `[${credit.licence}](${credit.licenceUrl})`} · [${words.credits.sourceWord}](${credit.source})`),
1086
581
  ]),
1087
582
  ];
1088
- }
1089
- /**
1090
- * Every page, in the order the charity would present them. A function of
1091
- * `media` (`SeedContext.media`) and of the site's name: several blocks need
1092
- * ids `seedDemoMedia` only knows at scaffold time, and the copy names the
1093
- * organisation the site belongs to. With no media, every picture block is
1094
- * left out rather than emitted empty.
1095
- */
1096
- export function buildAssociationDemoPages(media, siteName) {
1097
- const name = nameOf(siteName);
1098
583
  return [
1099
- { title: 'Home', slug: 'home', blocks: homeBlocks(media, name) },
1100
- { title: 'What we do', slug: 'what-we-do', blocks: whatWeDoBlocks(name) },
1101
- { title: 'Events', slug: 'events', blocks: eventsBlocks(name) },
1102
- { title: 'Volunteer with us', slug: 'volunteer', blocks: volunteerBlocks(media, name) },
1103
- { title: 'Ways to give', slug: 'donate', blocks: donateBlocks(name) },
1104
- { title: 'Where the money goes', slug: 'finances', blocks: financesBlocks(name) },
1105
- { title: 'Our story', slug: 'about', blocks: aboutBlocks(media, name) },
1106
- { title: 'Contact us', slug: 'contact', blocks: contactBlocks(name) },
1107
- { title: 'Privacy', slug: 'privacy', blocks: privacyBlocks(name) },
584
+ { title: words.titles.home, slug: slug.home, blocks: home },
585
+ { title: words.titles.whatWeDo, slug: slug.whatWeDo, blocks: whatWeDo },
586
+ { title: words.titles.events, slug: slug.events, blocks: events },
587
+ { title: words.titles.volunteer, slug: slug.volunteer, blocks: volunteer },
588
+ { title: words.titles.donate, slug: slug.donate, blocks: donate },
589
+ { title: words.titles.finances, slug: slug.finances, blocks: finances },
590
+ { title: words.titles.about, slug: slug.about, blocks: about },
591
+ { title: words.titles.contact, slug: slug.contact, blocks: contact },
592
+ {
593
+ title: words.titles.privacy,
594
+ slug: slug.privacy,
595
+ blocks: [prose('privacy-body', words.privacy)],
596
+ },
597
+ { title: words.titles.legal, slug: slug.legal, blocks: [prose('legal-body', words.legal)] },
598
+ { title: words.titles.credits, slug: slug.credits, blocks: credits },
1108
599
  ];
1109
600
  }
1110
601
  // ---------------------------------------------------------------------------
1111
602
  // Navigation, settings, pictures
1112
603
  // ---------------------------------------------------------------------------
1113
- /** Header navigation and the header's standing ask. The footer is grouped in columns: see `ASSOCIATION_FOOTER`. */
1114
- export const ASSOCIATION_MENUS = {
1115
- header: [
1116
- { label: 'What we do', url: '/what-we-do' },
1117
- { label: 'Events', url: '/events' },
1118
- { label: 'Volunteer', url: '/volunteer' },
1119
- { label: 'About us', url: '/about' },
1120
- ],
1121
- // Seeded with the demo content instead (`seedFooterMenu`): a footer in
1122
- // columns needs an unlinked heading per column, which `BlueprintMenus`
1123
- // cannot express.
1124
- footer: [],
1125
- headerAction: { label: 'Donate', url: '/donate' },
1126
- };
1127
- export const ASSOCIATION_FOOTER = [
1128
- {
1129
- heading: 'Get involved',
1130
- links: [
1131
- { label: 'Ways to give', url: '/donate' },
1132
- { label: 'Volunteer with us', url: '/volunteer' },
1133
- { label: 'Events', url: '/events' },
1134
- ],
1135
- },
1136
- {
1137
- heading: 'What we do',
1138
- links: [
1139
- { label: 'Thursday food bank', url: '/what-we-do/thursday-food-bank' },
1140
- { label: 'Homework club', url: '/what-we-do/homework-club' },
1141
- { label: 'Community garden', url: '/what-we-do/community-garden' },
1142
- { label: 'Winter coat bank', url: '/what-we-do/winter-coat-bank' },
1143
- ],
1144
- },
1145
- {
1146
- heading: 'About us',
1147
- links: [
1148
- { label: 'Our story', url: '/about' },
1149
- { label: 'Where the money goes', url: '/finances' },
1150
- { label: 'Contact us', url: '/contact' },
1151
- { label: 'Privacy', url: '/privacy' },
604
+ /** Header navigation and the header's standing ask. The footer is grouped in columns: see `associationFooter`. */
605
+ export function associationMenus(copy) {
606
+ const url = (page) => `/${copy.pageSlugs[page]}`;
607
+ return {
608
+ header: copy.menus.header.map((item) => ({ label: item.label, url: url(item.page) })),
609
+ // Seeded with the demo content instead (`seedFooterMenu`): a footer in
610
+ // columns needs an unlinked heading per column, which `BlueprintMenus`
611
+ // cannot express.
612
+ footer: [],
613
+ headerAction: { label: copy.menus.action, url: url('donate') },
614
+ };
615
+ }
616
+ export function associationFooter(copy) {
617
+ return copy.footer;
618
+ }
619
+ export function associationFooterNote(copy, siteName) {
620
+ return copy.footerNote(contextFor(copy, siteName));
621
+ }
622
+ export function associationSiteSettings(copy) {
623
+ return {
624
+ 'general.tagline': copy.settings.tagline,
625
+ 'general.socialLinks': [
626
+ { label: 'Facebook', url: 'https://facebook.com/example' },
627
+ { label: 'Instagram', url: 'https://instagram.com/example' },
1152
628
  ],
1153
- },
1154
- ];
1155
- export function associationFooterNote(siteName) {
1156
- return [
1157
- `Registered charity in England and Wales, no. ${CHARITY_NUMBER}`,
1158
- HALL,
1159
- '220 Elm Street',
1160
- 'Ashworth AW4 2LT',
1161
- '',
1162
- `${ASSOCIATION_PHONE}`,
1163
- associationEmail(siteName),
1164
- '',
1165
- 'The hall is open Monday to Friday, 9.30am to 4pm, and on Thursday evenings.',
1166
- ].join('\n');
629
+ // Rewritten with the site's own email address when the demo content is
630
+ // seeded (`seedAssociationDemoContent`).
631
+ 'general.footerNote': associationFooterNote(copy, undefined),
632
+ // A food bank page is not a discussion: no comment form under an event.
633
+ 'discussion.enabled': false,
634
+ };
635
+ }
636
+ function onlyOn(...targets) {
637
+ return { pages: { mode: 'only', targets } };
1167
638
  }
1168
- export const ASSOCIATION_SITE_SETTINGS = {
1169
- 'general.tagline': 'Food, homework help, a garden and warm coats, from the old library on Elm Street.',
1170
- 'general.socialLinks': [
1171
- { label: 'Facebook', url: 'https://facebook.com/example' },
1172
- { label: 'Instagram', url: 'https://instagram.com/example' },
1173
- ],
1174
- // Rewritten with the site's own email address when the demo content is
1175
- // seeded (`seedAssociationDemoContent`).
1176
- 'general.footerNote': associationFooterNote(undefined),
1177
- // A food bank page is not a discussion: no comment form under an event.
1178
- 'discussion.enabled': false,
1179
- };
1180
639
  /**
1181
640
  * The side column a neighbour expects beside the charity's own pages. On a
1182
- * programme, the ask to keep it free and where else the hall can help (the
1183
- * advice sessions the What we do page names); on an event, the four weekly
1184
- * programmes with their photographs and the call to lend a hand on the day;
1185
- * on search results, the programmes and the same ask to give.
641
+ * programme, the ask to keep it free and where else the hall can help; on an
642
+ * event, the four weekly programmes with their photographs and the call to lend
643
+ * a hand on the day; on search results, the programmes and the same ask to
644
+ * give.
1186
645
  *
1187
- * What the pages already show is never repeated: an event already ends on
1188
- * the other dates at the hall, in date order, and a programme on the other
646
+ * What the pages already show is never repeated: an event already ends on the
647
+ * other dates at the hall, in date order, and a programme on the other
1189
648
  * programmes, so neither gets a second list of its own kind. The home page
1190
- * opens on its hero and keeps its full width, and every site page (What we
1191
- * do, Events, Volunteer, Ways to give, finances, Our story, Contact,
1192
- * Privacy) already carries its own lists and asks. No contact widget: the
1193
- * footer note on every page already gives the hall's address, telephone,
1194
- * email and opening hours. No widget names the charity, so none goes stale
1195
- * when the site is called something else.
649
+ * opens on its hero and keeps its full width, and every site page already
650
+ * carries its own lists and asks. No contact widget: the footer note on every
651
+ * page already gives the hall's address, telephone, email and opening hours. No
652
+ * widget names the charity, so none goes stale when the site is called
653
+ * something else.
1196
654
  */
1197
- const ASSOCIATION_EVENT = { kind: 'collection', collection: 'event' };
1198
- const ASSOCIATION_PROGRAMME = { kind: 'collection', collection: 'programme' };
1199
- const ASSOCIATION_SEARCH = { kind: 'search' };
1200
- function onlyOn(...targets) {
1201
- return { pages: { mode: 'only', targets } };
1202
- }
1203
- export const ASSOCIATION_WIDGETS = [
1204
- {
1205
- area: 'sidebar',
1206
- type: 'cta',
1207
- settings: {
1208
- heading: 'Keep it free for everyone',
1209
- body: 'Nobody pays for a food parcel, a place at homework club or a winter coat. £12 a month keeps one family in fresh vegetables all year.',
1210
- label: 'Ways to give',
1211
- href: '/donate',
655
+ export function associationWidgets(copy) {
656
+ const w = copy.widgets;
657
+ const eventTarget = { kind: 'collection', collection: 'event' };
658
+ const programmeTarget = { kind: 'collection', collection: 'programme' };
659
+ const searchTarget = { kind: 'search' };
660
+ const fill = (line) => line
661
+ .replaceAll('{phone}', copy.identity.phone)
662
+ .replaceAll('{phoneHref}', copy.identity.phoneHref);
663
+ return [
664
+ {
665
+ area: 'sidebar',
666
+ type: 'cta',
667
+ settings: {
668
+ heading: w.keepFreeHeading,
669
+ body: w.keepFreeBody,
670
+ label: w.keepFreeLabel,
671
+ href: `/${copy.pageSlugs.donate}`,
672
+ },
673
+ visibility: onlyOn(programmeTarget, searchTarget),
1212
674
  },
1213
- visibility: onlyOn(ASSOCIATION_PROGRAMME, ASSOCIATION_SEARCH),
1214
- },
1215
- {
1216
- area: 'sidebar',
1217
- type: 'text',
1218
- title: 'Help with something else',
1219
- settings: {
1220
- body: [
1221
- textBlock('widget-advice-0', 'normal', 'Debt advice, housing, benefits and the council’s support fund all hold sessions at the hall.'),
1222
- textBlock('widget-advice-1', 'normal', `Ring [${ASSOCIATION_PHONE}](${PHONE_LINK}) on a weekday and we will tell you when the next one is.`),
1223
- ],
675
+ {
676
+ area: 'sidebar',
677
+ type: 'text',
678
+ title: w.adviceTitle,
679
+ settings: {
680
+ body: w.adviceLines.map((line, index) => textBlock(`widget-advice-${index}`, 'normal', fill(line))),
681
+ },
682
+ visibility: onlyOn(programmeTarget),
1224
683
  },
1225
- visibility: onlyOn(ASSOCIATION_PROGRAMME),
1226
- },
1227
- {
1228
- area: 'sidebar',
1229
- type: 'recentEntries',
1230
- title: 'Every week at the hall',
1231
- settings: { collection: 'programme', count: 4, showDate: false, showImage: true },
1232
- visibility: onlyOn(ASSOCIATION_EVENT, ASSOCIATION_SEARCH),
1233
- },
1234
- {
1235
- area: 'sidebar',
1236
- type: 'cta',
1237
- settings: {
1238
- heading: 'Lend a hand on the day',
1239
- body: 'Collections, garden mornings and the supper all run on volunteers. Come for two hours or stay all day; nobody needs experience.',
1240
- label: 'Volunteer with us',
1241
- href: '/volunteer',
684
+ {
685
+ area: 'sidebar',
686
+ type: 'recentEntries',
687
+ title: w.weeklyTitle,
688
+ settings: { collection: 'programme', count: 4, showDate: false, showImage: true },
689
+ visibility: onlyOn(eventTarget, searchTarget),
1242
690
  },
1243
- visibility: onlyOn(ASSOCIATION_EVENT),
1244
- },
1245
- ];
1246
- /**
1247
- * Bundled photographs (`assets/photos/association/`), every one looked at
1248
- * full size: lettering printed on aprons, T-shirts, caps, boxes and signs was
1249
- * retouched or cropped out, and a picture with too much of it (a river
1250
- * clean-up among labelled bins) was dropped. The partner wordmarks were
1251
- * rendered once from OFL typefaces. `spec` is only the fallback a missing
1252
- * file would get.
1253
- */
1254
- export const ASSOCIATION_MEDIA_SPECS = [
1255
- {
1256
- name: 'hero',
1257
- spec: heroArt(associationPalette(), 'mesh', 21),
1258
- alt: 'Around twenty volunteers in work aprons and caps gathered outdoors, smiling',
1259
- photo: 'association/hero.jpg',
1260
- },
1261
- ...[
1262
- [
1263
- 'programme-food-bank',
1264
- 'association/food-bank.jpg',
1265
- 'Volunteers packing tins and dry food into boxes for the Thursday food bank',
1266
- ],
1267
- [
1268
- 'programme-homework-club',
1269
- 'association/homework-club.jpg',
1270
- 'Student volunteers around a table at the start of a homework club afternoon',
1271
- ],
1272
- [
1273
- 'programme-garden',
1274
- 'association/garden-planting.jpg',
1275
- 'Neighbours kneeling at a raised bed, planting out seedlings in the sun',
1276
- ],
1277
- [
1278
- 'programme-coat-bank',
1279
- 'association/winter-coat-collection.jpg',
1280
- 'Donated winter coats on rails, with two people looking for a size',
1281
- ],
1282
- [
1283
- 'event-orientation',
1284
- 'association/volunteer-orientation.jpg',
1285
- 'New volunteers listening around a table in a bright meeting room',
1286
- ],
1287
- [
1288
- 'event-supper',
1289
- 'association/fundraising-dinner.jpg',
1290
- 'Guests talking at round tables in a panelled hall during a charity supper',
1291
- ],
1292
- [
1293
- 'portrait',
1294
- 'association/volunteer-portrait.jpg',
1295
- 'Joanne Pryce, a homework club volunteer, talking at a table',
1296
- ],
1297
- ].map(([name, photo, alt], index) => ({
1298
- name,
1299
- spec: coverArt(associationPalette(), index + 1),
1300
- alt,
1301
- photo,
1302
- })),
1303
- ...ASSOCIATION_PARTNERS.map((partner, index) => ({
1304
- name: partner.key,
1305
- spec: logoArt(index + 1),
1306
- alt: partner.name,
1307
- photo: `association/${partner.key}.png`,
1308
- })),
1309
- ];
691
+ {
692
+ area: 'sidebar',
693
+ type: 'cta',
694
+ settings: {
695
+ heading: w.handHeading,
696
+ body: w.handBody,
697
+ label: w.handLabel,
698
+ href: `/${copy.pageSlugs.volunteer}`,
699
+ },
700
+ visibility: onlyOn(eventTarget),
701
+ },
702
+ ];
703
+ }
1310
704
  /** The starting skin's palette, for the fallback art a missing photograph would get. */
1311
705
  function associationPalette() {
1312
706
  const skin = STARTING_SKINS.association;
@@ -1319,6 +713,29 @@ function associationPalette() {
1319
713
  }
1320
714
  return skin.color;
1321
715
  }
716
+ /**
717
+ * Bundled photographs (`assets/photos/association/`), every one looked at full
718
+ * size and credited (`association-credits.ts`). The partner wordmarks were
719
+ * rendered once from OFL typefaces. `spec` is only the fallback a missing file
720
+ * would get.
721
+ */
722
+ export function associationMediaSpecs(copy) {
723
+ const palette = associationPalette();
724
+ return [
725
+ ...copy.media.map((item, index) => ({
726
+ name: item.name,
727
+ spec: item.name === 'hero' ? heroArt(palette, 'sun', 21) : coverArt(palette, index + 1),
728
+ alt: item.alt,
729
+ photo: `association/${item.file}`,
730
+ })),
731
+ ...copy.partners.map((partner, index) => ({
732
+ name: partner.key,
733
+ spec: logoArt(index + 1),
734
+ alt: partner.name,
735
+ photo: `association/${partner.file}`,
736
+ })),
737
+ ];
738
+ }
1322
739
  export const ASSOCIATION_RECOMMENDED_AGENTS = [
1323
740
  {
1324
741
  name: 'contentAgent',
@@ -1331,14 +748,17 @@ export const ASSOCIATION_RECOMMENDED_AGENTS = [
1331
748
  reason: 'Audits the events and programme pages so people nearby find them from a search engine.',
1332
749
  },
1333
750
  ];
751
+ // ---------------------------------------------------------------------------
752
+ // Seeding
753
+ // ---------------------------------------------------------------------------
1334
754
  /** Seeds the footer menu at the `footer` location, one placeholder per column with its links as children. */
1335
- async function seedFooterMenu(db, locale) {
755
+ async function seedFooterMenu(db, locale, copy) {
1336
756
  await ensureMenuTables(db);
1337
757
  const store = createMenuStore({ db });
1338
758
  if ((await store.byLocation('footer', locale)) !== null)
1339
759
  return;
1340
760
  const menu = await store.create({ name: 'footer', locale, label: 'footer', location: 'footer' });
1341
- for (const column of ASSOCIATION_FOOTER) {
761
+ for (const column of associationFooter(copy)) {
1342
762
  const heading = await store.createItem(menu.id, {
1343
763
  label: column.heading,
1344
764
  kind: 'submenu-placeholder',
@@ -1354,87 +774,95 @@ async function seedFooterMenu(db, locale) {
1354
774
  }
1355
775
  }
1356
776
  /** Writes the footer note again with the site's own email address. */
1357
- async function seedFooterNote(ctx) {
777
+ async function seedFooterNote(ctx, copy) {
1358
778
  const definition = siteSettingByKey('general.footerNote');
1359
779
  if (definition === undefined)
1360
780
  return;
1361
781
  await ensureSiteSettingsTables(ctx.db);
1362
782
  const store = createSiteSettingsStore({ db: ctx.db });
1363
783
  const locale = definition.scope === 'site' ? SITE_SETTINGS_SITE_SCOPE : ctx.defaultLocale;
1364
- await store.set('general.footerNote', locale, associationFooterNote(ctx.siteName), ctx.adminId);
784
+ await store.set('general.footerNote', locale, associationFooterNote(copy, ctx.siteName), ctx.adminId);
1365
785
  }
1366
786
  /**
1367
787
  * Inserts the blueprint's content through the real `ContentStore` and
1368
788
  * `MenuStore`, never mocked (house rule): programmes first, in the order the
1369
- * charity presents them, then events in date order, then the pages.
1370
- * Everything is published, so every list and every link has something real
1371
- * behind it.
789
+ * charity presents them, then events in date order, then the pages. Everything
790
+ * is published, so every list and every link has something real behind it.
1372
791
  */
1373
- async function seedAssociationDemoContent(ctx) {
1374
- const { db, defaultLocale, adminId, media } = ctx;
1375
- const name = nameOf(ctx.siteName);
1376
- const programmeStore = createContentStore({ db, collection: programme, defaultLocale });
1377
- const eventStore = createContentStore({ db, collection: event, defaultLocale });
1378
- const pageStore = createContentStore({ db, collection: page, defaultLocale });
1379
- for (const item of ASSOCIATION_PROGRAMMES) {
1380
- const photo = media[item.photo];
1381
- await programmeStore.create({
1382
- status: 'published',
1383
- createdBy: adminId,
1384
- values: {
1385
- title: item.title,
1386
- slug: item.slug,
1387
- summary: item.summary,
1388
- schedule: item.schedule,
1389
- location: item.location,
1390
- address: HALL_ADDRESS,
1391
- audience: item.audience,
1392
- cost: item.cost,
1393
- contact: associationEmail(name, item.mailbox),
1394
- ...(photo === undefined ? {} : { coverImage: photo }),
1395
- },
1396
- blocks: { blocks: associationProgrammeBlocks(item, name).map(toBlockZoneEntry) },
1397
- });
1398
- }
1399
- for (const item of ASSOCIATION_DEMO_EVENTS) {
1400
- const photo = item.photo === undefined ? undefined : media[item.photo];
1401
- await eventStore.create({
1402
- status: 'published',
1403
- createdBy: adminId,
1404
- values: {
1405
- title: item.title,
1406
- slug: item.slug,
1407
- date: futureIso(item.daysFromNow, item.start),
1408
- endsAt: futureIso(item.daysFromNow, item.end),
1409
- location: item.location,
1410
- address: item.address,
1411
- cost: item.cost,
1412
- booking: item.booking,
1413
- description: item.description,
1414
- ...(photo === undefined ? {} : { coverImage: photo }),
1415
- },
1416
- blocks: { blocks: associationEventBlocks(item).map(toBlockZoneEntry) },
1417
- });
1418
- }
1419
- for (const demo of buildAssociationDemoPages(media, name)) {
1420
- await pageStore.create({
1421
- status: 'published',
1422
- createdBy: adminId,
1423
- values: { title: demo.title, slug: demo.slug },
1424
- blocks: { blocks: demo.blocks.map(toBlockZoneEntry) },
1425
- });
1426
- }
1427
- await seedFooterMenu(db, defaultLocale);
1428
- await seedFooterNote(ctx);
792
+ function seedAssociationDemoContent(copy) {
793
+ return async (ctx) => {
794
+ const { db, defaultLocale, adminId, media } = ctx;
795
+ const model = associationSchema(copy);
796
+ const context = contextFor(copy, ctx.siteName);
797
+ const programmeStore = createContentStore({ db, collection: model.programme, defaultLocale });
798
+ const eventStore = createContentStore({ db, collection: model.event, defaultLocale });
799
+ const pageStore = createContentStore({ db, collection: model.page, defaultLocale });
800
+ for (const item of copy.programmes) {
801
+ const photo = media[item.photo];
802
+ await programmeStore.create({
803
+ status: 'published',
804
+ createdBy: adminId,
805
+ values: {
806
+ title: item.title,
807
+ slug: item.slug,
808
+ summary: item.summary,
809
+ schedule: item.schedule,
810
+ location: item.location,
811
+ address: `${copy.identity.street}, ${copy.identity.town}`,
812
+ audience: item.audience,
813
+ cost: item.cost,
814
+ contact: context.email(item.mailbox),
815
+ ...(photo === undefined ? {} : { coverImage: photo }),
816
+ },
817
+ blocks: { blocks: associationProgrammeBlocks(copy, item).map(toBlockZoneEntry) },
818
+ });
819
+ }
820
+ for (const item of copy.events) {
821
+ const photo = item.photo === undefined ? undefined : media[item.photo];
822
+ await eventStore.create({
823
+ status: 'published',
824
+ createdBy: adminId,
825
+ values: {
826
+ title: item.title,
827
+ slug: item.slug,
828
+ date: futureIso(item.daysFromNow, item.start),
829
+ endsAt: futureIso(item.daysFromNow, item.end),
830
+ location: item.location,
831
+ address: item.address,
832
+ cost: item.cost,
833
+ booking: item.booking,
834
+ description: item.description,
835
+ ...(photo === undefined ? {} : { coverImage: photo }),
836
+ },
837
+ blocks: { blocks: associationEventBlocks(copy, item).map(toBlockZoneEntry) },
838
+ });
839
+ }
840
+ for (const demo of buildAssociationDemoPages(copy, media, ctx.siteName)) {
841
+ await pageStore.create({
842
+ status: 'published',
843
+ createdBy: adminId,
844
+ values: { title: demo.title, slug: demo.slug },
845
+ blocks: { blocks: demo.blocks.map(toBlockZoneEntry) },
846
+ });
847
+ }
848
+ await seedFooterMenu(db, defaultLocale, copy);
849
+ await seedFooterNote(ctx, copy);
850
+ };
1429
851
  }
1430
- export const associationContentPack = {
1431
- collections: ASSOCIATION_COLLECTIONS,
1432
- recommendedAgents: ASSOCIATION_RECOMMENDED_AGENTS,
1433
- seedDemoContent: seedAssociationDemoContent,
1434
- defaultTheme: '@cogenta/theme-association',
1435
- menus: ASSOCIATION_MENUS,
1436
- widgets: ASSOCIATION_WIDGETS,
1437
- siteSettings: ASSOCIATION_SITE_SETTINGS,
1438
- mediaSpecs: ASSOCIATION_MEDIA_SPECS,
1439
- };
852
+ /** The whole blueprint, in one language. */
853
+ export function createAssociationContentPack(locale) {
854
+ const copy = associationCopyFor(locale);
855
+ return {
856
+ collections: associationSchema(copy).collections,
857
+ recommendedAgents: ASSOCIATION_RECOMMENDED_AGENTS,
858
+ seedDemoContent: seedAssociationDemoContent(copy),
859
+ defaultTheme: '@cogenta/theme-association',
860
+ menus: associationMenus(copy),
861
+ widgets: associationWidgets(copy),
862
+ siteSettings: associationSiteSettings(copy),
863
+ mediaSpecs: associationMediaSpecs(copy),
864
+ };
865
+ }
866
+ /** The English pack, which `BLUEPRINT_CONTENT_PACKS` lists; `contentPackFor` picks by locale. */
867
+ export const associationContentPack = createAssociationContentPack('en');
1440
868
  //# sourceMappingURL=association.js.map