@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,731 +1,159 @@
1
1
  import { CogentaError } from '@cogenta/core';
2
2
  import { createContentStore, createTaxonomyStore, defineCollection, defineTaxonomy, f, validateCollectionSet, validateTaxonomySet, } from '@cogenta/schema';
3
3
  import { coverArt } from '../demo-art/compositions.js';
4
+ import { BLOG_COPY_EN } from './blog-copy-en.js';
5
+ import { BLOG_COPY_FR } from './blog-copy-fr.js';
6
+ import { BLOG_PHOTO_CREDITS } from './blog-credits.js';
7
+ import { answer, bullet, p } from './blog-rich.js';
4
8
  import { SEO_FIELDS, toBlockZoneEntry, } from './content-pack.js';
5
9
  import { STARTING_SKINS } from './starting-skins.js';
6
- /**
7
- * The `blog` blueprint: a personal publication of essays and letters about
8
- * reading, writing and walking (L9 task 3; richened by L25; rewritten to
9
- * studio level by L27).
10
- *
11
- * `post` reuses contract A's system fields for publish state and authorship
12
- * rather than declaring its own. `category`/`tag` are `defineTaxonomy()`
13
- * declarations (ADR-0022): `category` stays hierarchical (its default),
14
- * `tag` is flat.
15
- *
16
- * The demo writing is written as the site of one writer, in the first
17
- * person, and names the site the person scaffolding it chose
18
- * (`SeedContext.siteName`), falling back to a fictional title only outside a
19
- * real scaffold. Only some essays carry a photograph, on purpose: an index
20
- * of writing reads better when pictures are the exception.
21
- */
22
- export const DEFAULT_PUBLICATION_NAME = 'Field Notes';
23
- export const category = defineTaxonomy({
24
- name: 'category',
25
- labels: {
26
- singular: { en: 'Category', fr: 'Catégorie' },
27
- plural: { en: 'Categories', fr: 'Catégories' },
28
- },
29
- permissions: {
30
- read: ['public'],
31
- create: ['editor', 'admin'],
32
- update: ['editor', 'admin'],
33
- delete: ['admin'],
34
- },
35
- });
36
- export const tag = defineTaxonomy({
37
- name: 'tag',
38
- labels: {
39
- singular: { en: 'Tag', fr: 'Étiquette' },
40
- plural: { en: 'Tags', fr: 'Étiquettes' },
41
- },
42
- hierarchical: false,
43
- permissions: {
44
- read: ['public'],
45
- create: ['editor', 'admin'],
46
- update: ['editor', 'admin'],
47
- delete: ['admin'],
48
- },
49
- });
50
- export const post = defineCollection({
51
- name: 'post',
52
- labels: { singular: 'Post', plural: 'Posts' },
53
- routing: { pattern: '/blog/:slug' },
54
- versioning: { drafts: true, history: true },
55
- fields: {
56
- title: f.text({ required: true, max: 200 }),
57
- slug: f.slug({ from: 'title', unique: true }),
58
- body: f.richText({ required: true }),
59
- excerpt: f.text({ max: 300, multiline: true }),
60
- // Read by `entryImage` (`@cogenta/theme-kit`) for the index, the shelf
61
- // and the essay's own header. Optional: most letters have no picture.
62
- coverImage: f.media({ accept: ['image'] }),
63
- category: f.taxonomy({ of: 'category', many: false }),
64
- tags: f.taxonomy({ of: 'tag', many: true }),
65
- // Declared so the store's publish-time default fires and so a seeded
66
- // essay can carry the date it was really published: without it every
67
- // entry's date line would be the instant of the scaffold.
68
- publishedAt: f.datetime(),
69
- ...SEO_FIELDS,
70
- },
71
- indexes: [['publishedAt', 'desc'], ['slug']],
72
- permissions: {
73
- read: ['public'],
74
- create: ['editor', 'admin'],
75
- update: ['editor', 'admin'],
76
- delete: ['admin'],
77
- publish: ['admin'],
78
- },
79
- });
80
- export const page = defineCollection({
81
- name: 'page',
82
- labels: { singular: 'Page', plural: 'Pages' },
83
- routing: { pattern: '/:slug' },
84
- fields: {
85
- title: f.text({ required: true, max: 200 }),
86
- slug: f.slug({ from: 'title', unique: true }),
87
- blocks: f.blocks({ required: true }),
88
- ...SEO_FIELDS,
89
- },
90
- indexes: [['slug']],
91
- permissions: {
92
- read: ['public'],
93
- create: ['editor', 'admin'],
94
- update: ['editor', 'admin'],
95
- delete: ['admin'],
96
- },
97
- });
98
- export const BLOG_COLLECTIONS = [post, page];
99
- export const BLOG_TAXONOMIES = [category, tag];
100
- validateCollectionSet(BLOG_COLLECTIONS);
101
- validateTaxonomySet(BLOG_TAXONOMIES, BLOG_COLLECTIONS);
102
- let nodeKey = 0;
103
- function nextKey(prefix) {
104
- nodeKey += 1;
105
- return `${prefix}-${nodeKey}`;
106
- }
107
- /**
108
- * A run of text, where `*words*` is set in italic (book titles, a stressed
109
- * word). Nothing else is interpreted: this is plain text with one mark.
110
- */
111
- function spans(text, key) {
112
- return text
113
- .split(/(\*[^*]+\*)/)
114
- .filter((part) => part !== '')
115
- .map((part, index) => {
116
- const italic = part.startsWith('*') && part.endsWith('*') && part.length > 2;
117
- return {
118
- _key: `${key}-s${index}`,
119
- _type: 'span',
120
- text: italic ? part.slice(1, -1) : part,
121
- marks: italic ? ['em'] : [],
122
- };
123
- });
10
+ export const DEFAULT_PUBLICATION_NAME = BLOG_COPY_EN.defaultPublicationName;
11
+ /** The copy for a site's default locale: French for any `fr*` locale, English otherwise. */
12
+ export function blogCopyFor(locale) {
13
+ return locale.toLowerCase().startsWith('fr') ? BLOG_COPY_FR : BLOG_COPY_EN;
124
14
  }
125
- function textBlock(style, text, listItem) {
126
- const key = nextKey(`blog-${listItem ?? style}`);
15
+ // ---------------------------------------------------------------------------
16
+ // Content model
17
+ // ---------------------------------------------------------------------------
18
+ const TERM_PERMISSIONS = {
19
+ read: ['public'],
20
+ create: ['editor', 'admin'],
21
+ update: ['editor', 'admin'],
22
+ delete: ['admin'],
23
+ };
24
+ const ENTRY_PERMISSIONS = { ...TERM_PERMISSIONS, publish: ['admin'] };
25
+ /** Labels in both languages, whatever the copy: an admin can read either on any site. */
26
+ function taxonomyLabels(pick) {
127
27
  return {
128
- _key: key,
129
- _type: 'block',
130
- style,
131
- ...(listItem === undefined ? {} : { listItem, level: 1 }),
132
- children: spans(text, key),
133
- markDefs: [],
28
+ singular: { en: pick(BLOG_COPY_EN).singular, fr: pick(BLOG_COPY_FR).singular },
29
+ plural: { en: pick(BLOG_COPY_EN).plural, fr: pick(BLOG_COPY_FR).plural },
134
30
  };
135
31
  }
136
- const p = (text) => textBlock('normal', text);
137
- const h2 = (text) => textBlock('h2', text);
138
- const h4 = (text) => textBlock('h4', text);
139
- const quote = (text) => textBlock('blockquote', text);
140
- const bullet = (text) => textBlock('normal', text, 'bullet');
141
- const numbered = (text) => textBlock('normal', text, 'number');
142
- const rule = () => ({ _key: nextKey('blog-hr'), _type: 'hr' });
143
- /** A photograph inside the text, when the scaffold ingested it; nothing otherwise. */
144
- function figure(mediaId, caption) {
145
- if (mediaId === undefined)
146
- return [];
147
- return [{ _key: nextKey('blog-figure'), _type: 'media', id: mediaId, caption }];
32
+ /** The content model, named in the copy's language. */
33
+ export function blogSchema(copy) {
34
+ const { schema } = copy;
35
+ const category = defineTaxonomy({
36
+ name: schema.category.name,
37
+ labels: taxonomyLabels((c) => c.schema.category),
38
+ permissions: TERM_PERMISSIONS,
39
+ });
40
+ const tag = defineTaxonomy({
41
+ name: schema.tag.name,
42
+ labels: taxonomyLabels((c) => c.schema.tag),
43
+ hierarchical: false,
44
+ permissions: TERM_PERMISSIONS,
45
+ });
46
+ const post = defineCollection({
47
+ name: 'post',
48
+ labels: schema.post,
49
+ routing: { pattern: '/blog/:slug' },
50
+ versioning: { drafts: true, history: true },
51
+ fields: {
52
+ title: f.text({ required: true, max: 200 }),
53
+ slug: f.slug({ from: 'title', unique: true }),
54
+ body: f.richText({ required: true }),
55
+ excerpt: f.text({ max: 300, multiline: true }),
56
+ // Read by `entryTopic` (`@cogenta/theme-blog`) for the small-caps label
57
+ // a listing card sets above its title. A plain-text field, not the
58
+ // `category` relation: a taxonomy term stores an id, never a label a
59
+ // reader can be shown — the same reasoning as `@cogenta/theme-magazine`'s
60
+ // own `kicker` field.
61
+ topic: f.text({ max: 60 }),
62
+ // Read by `entryImage` (`@cogenta/theme-kit`) for the index, the shelf
63
+ // and the essay's own header. Optional: most letters have no picture.
64
+ coverImage: f.media({ accept: ['image'] }),
65
+ category: f.taxonomy({ of: schema.category.name, many: false }),
66
+ tags: f.taxonomy({ of: schema.tag.name, many: true }),
67
+ // Declared so the store's publish-time default fires and so a seeded
68
+ // essay can carry the date it was really published: without it every
69
+ // entry's date line would be the instant of the scaffold.
70
+ publishedAt: f.datetime(),
71
+ ...SEO_FIELDS,
72
+ },
73
+ indexes: [['publishedAt', 'desc'], ['slug']],
74
+ permissions: ENTRY_PERMISSIONS,
75
+ });
76
+ const page = defineCollection({
77
+ name: 'page',
78
+ versioning: { drafts: true, history: true },
79
+ labels: schema.page,
80
+ routing: { pattern: '/:slug' },
81
+ fields: {
82
+ title: f.text({ required: true, max: 200 }),
83
+ slug: f.slug({ from: 'title', unique: true }),
84
+ blocks: f.blocks({ required: true }),
85
+ ...SEO_FIELDS,
86
+ },
87
+ indexes: [['slug']],
88
+ permissions: ENTRY_PERMISSIONS,
89
+ });
90
+ const collections = [post, page];
91
+ const taxonomies = [category, tag];
92
+ validateCollectionSet(collections);
93
+ validateTaxonomySet(taxonomies, collections);
94
+ return { category, tag, post, page, collections, taxonomies };
148
95
  }
149
- export const BLOG_DEMO_CATEGORIES = [
150
- {
151
- name: 'Reading',
152
- slug: 'reading',
153
- description: 'Books reread and books abandoned, and what a train does for attention.',
154
- },
155
- {
156
- name: 'Writing',
157
- slug: 'writing',
158
- description: 'Drafts, revision, notebooks, and the unglamorous work of finishing.',
159
- },
160
- {
161
- name: 'Walking',
162
- slug: 'walking',
163
- description: 'Routes taken slowly, and the thinking that happens along them.',
164
- },
165
- {
166
- name: 'Letters',
167
- slug: 'letters',
168
- description: 'Shorter notes written every other Sunday, closer to correspondence.',
169
- },
170
- ];
171
- export const BLOG_DEMO_TAGS = [
172
- { name: 'Drafts', slug: 'drafts' },
173
- { name: 'Revision', slug: 'revision' },
174
- { name: 'Notebooks', slug: 'notebooks' },
175
- { name: 'Habits', slug: 'habits' },
176
- { name: 'Trains', slug: 'trains' },
177
- { name: 'Libraries', slug: 'libraries' },
178
- { name: 'Rereading', slug: 'rereading' },
179
- { name: 'Attention', slug: 'attention' },
180
- ];
181
- export const BLOG_DEMO_POSTS = [
182
- {
183
- title: 'Why I still draft in plain text',
184
- slug: 'plain-text-editor',
185
- excerpt: 'Nine writing apps in eight years, and the one file format that outlasted every one of them.',
186
- publishedAt: '2024-02-11T08:30:00.000Z',
187
- categorySlug: 'writing',
188
- tagSlugs: ['drafts', 'habits'],
189
- body: () => [
190
- p('Between 2016 and 2024 I paid for nine writing apps. I kept the receipts because I was ' +
191
- 'embarrassed by them, which is its own kind of record keeping. Each one promised a calmer ' +
192
- 'place to think. Each one, within two years, had added a sidebar, a sync indicator, a ' +
193
- 'streak counter and a banner about the new plan.'),
194
- p('The drafts from those years are now spread across export formats I can no longer open ' +
195
- 'without the app that made them. Two of the apps no longer exist. One of them turned my ' +
196
- 'footnotes into a proprietary block that exports as a row of question marks.'),
197
- h2('What a text file keeps'),
198
- p('A plain text file keeps three things the others eventually lost for me. It opens on any ' +
199
- 'machine I have owned, including a borrowed laptop at my sister’s kitchen table. It will ' +
200
- 'open in twenty years, because the format is the words and nothing else. And it asks ' +
201
- 'nothing of me while I write: there is no count to beat and no badge to earn.'),
202
- bullet('One folder per piece, named with the date I started it.'),
203
- bullet('One file per draft, numbered, never overwritten.'),
204
- bullet('A notes file beside them for everything I cut, because I always want it back.'),
205
- p('The folder for this piece holds four drafts and a notes file longer than any of them. ' +
206
- 'Nearly everything I still like in the final version came out of that notes file on the ' +
207
- 'third pass.'),
208
- p('Formatting waits until the end. Italics are underscores, headings are a line of capitals, ' +
209
- 'and a link is an address on a line of its own. When a piece is finished I paste it into ' +
210
- 'the site and spend ten minutes setting it properly, and those ten minutes are pleasant ' +
211
- 'precisely because the writing is already done.'),
212
- quote('The tool I trust is the one that has nothing to say about my writing.'),
213
- p('I have nothing against good software. I use a very good editor every day at work, where I ' +
214
- 'correct other people’s manuscripts and need its tracked changes. For my own first drafts ' +
215
- 'I want less than that: a blinking cursor, and the knowledge that the file will still be ' +
216
- 'there when I come back to it, in whatever year that turns out to be.'),
217
- ],
218
- },
219
- {
220
- title: 'The long way to the library',
221
- slug: 'the-long-way-to-the-library',
222
- excerpt: 'Forty minutes on foot instead of twelve on the bus, twice a week, and what the extra half hour turned out to be for.',
223
- publishedAt: '2024-05-19T07:15:00.000Z',
224
- categorySlug: 'walking',
225
- tagSlugs: ['libraries', 'attention'],
226
- body: () => [
227
- p('Twice a week I walk to the central library to write. The bus takes twelve minutes and ' +
228
- 'stops outside. The walk takes forty, through the park, over the canal bridge and up the ' +
229
- 'long hill past the old tram depot. For a year I told myself the walk was exercise. It ' +
230
- 'took most of that year to admit it was the most productive part of the morning.'),
231
- p('I leave the house with a question about whatever I am writing, usually a small one: why ' +
232
- 'the second section drags, whether an example belongs, what the last line should do. I ' +
233
- 'take no notes on the way. By the canal the question has usually changed shape, and by ' +
234
- 'the top of the hill I tend to know which paragraph to open first.'),
235
- h2('Why the route matters'),
236
- p('I have tried other routes. The shortest follows the main road, and it is useless for ' +
237
- 'thinking, because every crossing asks for attention and every bus stop is a small ' +
238
- 'decision about whether to give up and get on. The long route has three road crossings in ' +
239
- 'forty minutes and a stretch of towpath where nobody needs anything from me.'),
240
- p('The park helps too, though in a plainer way than a wellness article would put it. It is ' +
241
- 'the one part of the walk where I see the same people on the same days: a man training a ' +
242
- 'very reluctant spaniel, and two women who run the same loop in opposite directions and ' +
243
- 'wave each time they pass. Their routine makes mine feel less like a trick I am playing on ' +
244
- 'myself.'),
245
- h2('The reading room'),
246
- p('The reading room opens at nine. There are twenty-four desks, green lamps that nobody ' +
247
- 'switches on in summer, and a rule of silence the staff enforce with a look rather than a ' +
248
- 'sign. I take the same desk when I can, fourth from the window, and write by hand for the ' +
249
- 'first hour before I open the laptop.'),
250
- p('The walk home is for reading back what I wrote, in my head, and it is where I find most ' +
251
- 'of the sentences that need to go. I have learned to trust that verdict more than the one ' +
252
- 'I reach at the desk, perhaps because by then I have forgotten how hard each sentence was ' +
253
- 'to write.'),
254
- bullet('Leave the phone in the bag until the library door.'),
255
- bullet('Carry one question, never a list of them.'),
256
- bullet('Take the same route, so that the route stops asking for decisions.'),
257
- p('None of this survives a deadline, and I do not pretend it would. When work is heavy I ' +
258
- 'take the bus like everyone else. The pieces on this site that I still like were, almost ' +
259
- 'without exception, started on the towpath.'),
260
- ],
261
- },
262
- {
263
- title: 'Letter: what a commonplace book is for',
264
- slug: 'letter-commonplace-book',
265
- excerpt: 'A reader asked how I keep the passages I want to remember from the books I read. The answer involves a pencil and a lot of empty pages.',
266
- publishedAt: '2024-08-04T06:45:00.000Z',
267
- categorySlug: 'letters',
268
- tagSlugs: ['notebooks', 'rereading'],
269
- body: () => [
270
- p('This letter began as a reply to a reader in Utrecht who asked how I keep track of ' +
271
- 'quotations, and whether I use an app for it. I keep a commonplace book, which is the old ' +
272
- 'name for a notebook of passages copied out by hand, and I have kept one since 2015.'),
273
- p('The rules are few. A passage only goes in if I copy it by hand, with the page number and ' +
274
- 'the date. If I am not willing to copy it, I did not care about it enough, and that rule ' +
275
- 'alone removes nine tenths of what I would otherwise have highlighted.'),
276
- bullet('Copy by hand, never paste.'),
277
- bullet('Write the date, the book and the page on the same line as the passage.'),
278
- bullet('Leave the facing page empty for whatever you think of it later.'),
279
- p('The facing page is the part that matters. Months later, reading back, I write a line or ' +
280
- 'two opposite the quotation: where it came up again, what it argued with, whether I still ' +
281
- 'believe it. The best entries have three or four of those notes, in different pens, ' +
282
- 'across several years.'),
283
- p('I used to call it a record of reading. It is closer to a record of rereading. A passage ' +
284
- 'is rarely useful the day I copy it out. It becomes useful the third time I open the book ' +
285
- 'at random on a train and find that a sentence from 2014 has quietly answered a question ' +
286
- 'I am stuck on this week.'),
287
- p('Thank you for writing in. Replies to these letters reach me directly, and they are often ' +
288
- 'where the next one begins.'),
289
- ],
290
- },
291
- {
292
- title: 'What the second draft is for',
293
- slug: 'what-the-second-draft-is-for',
294
- excerpt: 'A first draft tells you what a piece is about. The second is where you find out what it is for, and most of the work is deciding what it can live without.',
295
- publishedAt: '2024-10-20T08:00:00.000Z',
296
- cover: 'notebook-and-coffee',
297
- categorySlug: 'writing',
298
- tagSlugs: ['revision', 'drafts'],
299
- body: () => [
300
- p('I spend three days a week correcting other people’s manuscripts for a university press, ' +
301
- 'and the most common thing I see is a finished first draft that its author believes is a ' +
302
- 'second one. The sentences have been improved and the commas are in the right places. The ' +
303
- 'piece still does what it did the first time: it wanders towards its point and arrives ' +
304
- 'there on the last page, a little surprised.'),
305
- p('I recognise it because I do it too. A first draft is how a writer finds out what they ' +
306
- 'think, and it is almost always organised in the order the thinking happened, which is ' +
307
- 'rarely the order a reader needs. The second draft exists to fix that, and polishing ' +
308
- 'sentences is the easiest way to avoid doing it.'),
309
- h2('Read it as a stranger'),
310
- p('The first step takes no writing at all. I print the draft and read it somewhere I would ' +
311
- 'read somebody else’s work: on the train, or in the chair by the window with a cup of ' +
312
- 'coffee going cold. I correct nothing on this pass. I only put a mark in the margin ' +
313
- 'wherever my attention drifts.'),
314
- p('Those marks are the most honest editor I have, and they cluster. There is nearly always a ' +
315
- 'run of three or four paragraphs in the middle where I, the author, the one reader ' +
316
- 'guaranteed to be interested, started thinking about lunch. That run is where the draft is ' +
317
- 'explaining something to itself.'),
318
- p('When the marks are in, I read the marked stretches aloud. My voice finds what my eye ' +
319
- 'forgives: a sentence I run out of breath in, a word repeated three lines apart, a joke ' +
320
- 'that only works in silence. I do it quietly, in an empty room, and I have stopped feeling ' +
321
- 'foolish about it.'),
322
- h2('Write the one sentence'),
323
- p('Before touching the text I write, on a separate page, one sentence that says what the ' +
324
- 'piece should leave a reader able to see or do that they could not before. It takes ' +
325
- 'several attempts, and the failed attempts are useful, because each one describes a ' +
326
- 'different essay I could write instead.'),
327
- p('For this essay the sentence was: a reader should come away able to tell a revised draft ' +
328
- 'from a polished one. Everything that does not serve that sentence becomes a candidate for ' +
329
- 'the notes file.'),
330
- quote('Most of revision is deciding what the piece can live without.'),
331
- h2('Take things out in the right order'),
332
- p('Cutting is where most revisions stall, because it feels like losing work. I cut in a ' +
333
- 'fixed order that makes it bearable.'),
334
- numbered('Whole sections that serve a different sentence. They go to the notes file intact, often ' +
335
- 'with a line about the other essay they belong to.'),
336
- numbered('Paragraphs that repeat an idea the piece has already made clear. The second statement is ' +
337
- 'usually the better one, so I move it up and delete the first.'),
338
- numbered('Examples that illustrate the same point twice. One exact example does more than three ' +
339
- 'adequate ones.'),
340
- numbered('Hedges: *probably*, *perhaps*, *in some ways*. I keep one where the doubt is real and remove ' +
341
- 'the rest.'),
342
- p('Only after those four passes do I look at sentences. By then there are fewer of them, and ' +
343
- 'the ones that remain have a job, which makes it much clearer how each should be written.'),
344
- h2('Move the ending to the beginning'),
345
- p('The most reliable change I make, in my own drafts and in the manuscripts I edit, is to ' +
346
- 'find the paragraph where the writer finally states their point and ask whether it could ' +
347
- 'open the piece. About half the time it can. The draft then has to be rebuilt around a ' +
348
- 'reader who already knows where they are going, and that rebuilding is most of what a ' +
349
- 'second draft is.'),
350
- p('It is uncomfortable, because the old opening is often the part I liked best. It was the ' +
351
- 'part I wrote while the idea was new. I keep those openings in the notes file too, and ' +
352
- 'every so often one of them becomes the first line of something else.'),
353
- h2('An example from this spring'),
354
- p('This spring I worked on a history of a Pennine canal, written by a retired lock-keeper who ' +
355
- 'knew more about the subject than anyone alive. His first draft ran to four hundred pages ' +
356
- 'and followed the canal from its source to its end, one lock at a time, because that was ' +
357
- 'the order in which he had walked it for forty years.'),
358
- p('The sentences were fine. The trouble was that the book’s real subject, the families who ' +
359
- 'had worked the locks across three generations, appeared only in the final chapter, where ' +
360
- 'he wrote about his own grandfather. We spent two months moving that chapter to the front ' +
361
- 'and rebuilding everything else as a way of following those families down the water.'),
362
- p('He cut ninety pages, most of them descriptions of locks built exactly like the ones before. ' +
363
- 'He kept every page in a folder, and he told me at the end that the folder was the book he ' +
364
- 'had needed to write in order to find the other one.'),
365
- h2('Know when to stop'),
366
- p('A second draft is finished when I can read it as a stranger without making a single mark ' +
367
- 'in the margin, and when the sentence I wrote at the start is still true of it. It is ' +
368
- 'rarely finished when it feels finished. That feeling tends to arrive one draft early, at ' +
369
- 'the point where I have stopped finding problems because I have stopped looking for them.'),
370
- p('When I am unsure I leave it for a week. The piece that still reads well after seven days ' +
371
- 'goes out. The one that does not goes back into its folder with the new marks, and the ' +
372
- 'train does the rest.'),
373
- rule(),
374
- h4('Notes'),
375
- numbered('Paper works best for me, but the setting matters more than the medium. The same draft in ' +
376
- 'a different typeface on a different screen shows up nearly as many problems.'),
377
- numbered('The notes file for this essay runs to 2,300 words. Three earlier openings are in it, and ' +
378
- 'one of them is now the first paragraph of a letter.'),
379
- ],
380
- },
381
- {
382
- title: 'Books I reread every winter',
383
- slug: 'books-i-reread-every-winter',
384
- excerpt: 'Five books that come off the shelf in the first week of January, and why rereading has become more useful to me than reading something new.',
385
- publishedAt: '2025-01-12T09:10:00.000Z',
386
- categorySlug: 'reading',
387
- tagSlugs: ['rereading', 'habits'],
388
- body: () => [
389
- p('Every year, in the first week of January, the same five books come off the shelf. I did ' +
390
- 'not plan the ritual. It collected itself over about a decade, one book at a time, and at ' +
391
- 'some point I noticed I was reaching for them in the same order.'),
392
- p('Rereading has a poor reputation among people who keep reading lists, because it looks ' +
393
- 'like a failure to move on. I have come to think it is the only reading I do where I ' +
394
- 'notice myself changing: the book stays exactly where it was, and I do not.'),
395
- bullet('*A Month in the Country*, by J. L. Carr. A short novel about a summer spent uncovering a ' +
396
- 'medieval wall painting in a Yorkshire church. I read it in one sitting on the first ' +
397
- 'Sunday of the year, and it resets my sense of how much a small book can hold.'),
398
- bullet('*The Summer Book*, by Tove Jansson. A grandmother and a granddaughter on an island in the ' +
399
- 'Gulf of Finland. Every year a different chapter is my favourite, which tells me more about ' +
400
- 'the year than about the book.'),
401
- bullet('*The Rings of Saturn*, by W. G. Sebald. A walk along the Suffolk coast that keeps turning ' +
402
- 'into something else. It taught me that an essay may wander for as long as the reader ' +
403
- 'trusts the person walking.'),
404
- bullet('*The Writing Life*, by Annie Dillard. Read over two evenings, mostly for the pages about ' +
405
- 'the cabin where she worked, and then put back until next year.'),
406
- bullet('*Middlemarch*, by George Eliot. The one that lasts all of January. I read about sixty pages ' +
407
- 'a night and still find sentences I have no memory of.'),
408
- h2('What I look for the second time'),
409
- p('The first reading of a good book is mostly about what happens. By the third or fourth, ' +
410
- 'the story is out of the way and I can watch how the book is made: where a chapter chooses ' +
411
- 'to end, how long a scene runs before the writer cuts away, which details are planted early ' +
412
- 'to be picked up two hundred pages later.'),
413
- p('I keep a single page of notes for each of the five in the back of my commonplace book, and ' +
414
- 'I add a dated line to it every year. The lines for *A Month in the Country* now run from ' +
415
- '2016 to 2025. Reading them in order is a little like reading a diary kept by a patient, ' +
416
- 'slightly slower version of myself.'),
417
- p('By February I am hungry for something new, and the new books benefit from the wait. I ' +
418
- 'read them with more attention after a month of watching how the old ones work.'),
419
- ],
420
- },
421
- {
422
- title: 'A desk with almost nothing on it',
423
- slug: 'a-desk-with-almost-nothing-on-it',
424
- excerpt: 'Every version of my desk that lasted more than a few weeks had fewer things on it than the one before.',
425
- publishedAt: '2025-04-06T08:20:00.000Z',
426
- cover: 'desk',
427
- categorySlug: 'writing',
428
- tagSlugs: ['habits', 'attention'],
429
- body: () => [
430
- p('For years I rebuilt my desk every time I read a good account of how somebody else works. ' +
431
- 'A monitor arm, a better lamp, a keyboard that clicked in a satisfying way. Each change ' +
432
- 'brought a week of feeling organised and then a slow return to the same unfinished drafts.'),
433
- p('The desk I have now has stayed the same for two years, which is a record. It is a plain ' +
434
- 'white table by the window with a laptop, two notebooks, a pen and a small plant that has ' +
435
- 'survived by being ignored. Everything else lives in a drawer I have to stand up to open.'),
436
- h2('The drawer rule'),
437
- p('The drawer is the whole system. Anything I want on the desk has to earn its place by being ' +
438
- 'taken out of the drawer at least once a day for a week. Headphones failed. A second ' +
439
- 'monitor failed within two days. The notebooks have never once been put away.'),
440
- p('It works because a clear desk asks nothing of me when I sit down. I do not have to decide ' +
441
- 'what to deal with first. The only object that asks for attention is the notebook on the ' +
442
- 'left, which holds yesterday’s last line.'),
443
- h2('What stayed'),
444
- bullet('A laptop with the brightness turned down and no notifications from nine to twelve.'),
445
- bullet('A spiral notebook for the day’s work, and a bound one for anything worth keeping.'),
446
- bullet('One pen, a cheap one I can replace from any corner shop.'),
447
- bullet('A lamp for winter afternoons, which lives in the drawer from April to October.'),
448
- p('The window matters more than anything on the desk. It faces a row of back gardens, and in ' +
449
- 'the mornings I watch the light move across them while I wait for the first sentence. I ' +
450
- 'used to count that as time lost. I now think it is the part of the morning when the ' +
451
- 'sentence is being written somewhere I cannot see.'),
452
- p('I have no advice about equipment. The desk that works is the one you stop noticing, and ' +
453
- 'for me that happened only when there was almost nothing left on it to notice.'),
454
- ],
455
- },
456
- {
457
- title: 'Forty notebooks',
458
- slug: 'forty-notebooks',
459
- excerpt: 'Fourteen years of notebooks fill a shelf and a half. This autumn I read all forty in order, and they turned out to contain something other than ideas.',
460
- publishedAt: '2025-11-09T08:05:00.000Z',
461
- cover: 'notebooks',
462
- categorySlug: 'writing',
463
- tagSlugs: ['notebooks', 'habits'],
464
- body: () => [
465
- p('In September I took every notebook I have filled since 2011 down from the shelf and read ' +
466
- 'them in order. There are forty. It took most of five weeks, an hour or so each evening, ' +
467
- 'and it was the most useful thing I have done for my writing in years, though very little ' +
468
- 'of that use was the kind I expected.'),
469
- p('I had imagined finding ideas I had forgotten, and there were a few. What I mostly found was ' +
470
- 'a record of how I work, kept without my noticing that I was keeping it.'),
471
- h2('What is in them'),
472
- p('The first eleven are the same size, a pocket format with squared paper that I bought in ' +
473
- 'packs of three. After that the sizes wander. There is a large sketchbook from 2016 that I ' +
474
- 'used for exactly one month, a run of cheap exercise books from the year I was trying to ' +
475
- 'spend less, and, since 2020, a steady line of plain A5 notebooks with a stitched binding ' +
476
- 'that lies flat.'),
477
- p('Across all forty, the pages fall into four kinds.'),
478
- bullet('Lists of things to write about, most of which I never wrote.'),
479
- bullet('Passages copied from books, until 2015, when they moved to a book of their own.'),
480
- bullet('Drafts of openings, often five or six versions of one first paragraph.'),
481
- bullet('Records of ordinary days: when I started, where I was, what got in the way.'),
482
- p('I would have guessed that the lists and the openings were the valuable part. They are the ' +
483
- 'least interesting. The records of ordinary days are what I could not stop reading.'),
484
- p('There are gaps, too. Notebook nineteen covers eleven months of 2018, and when I reached it ' +
485
- 'I remembered why: it was the year my father was ill, and I wrote in it only on the train ' +
486
- 'to the hospital in Wakefield. Those pages are short and very plain, and some of the best ' +
487
- 'sentences in all forty are on them.'),
488
- h2('The days I actually wrote'),
489
- p('From about 2014 I noted the time and the place at the top of each page. I did it to feel ' +
490
- 'accountable. Read together, those small headers make a map of when the writing happened.'),
491
- p('It happened in the morning, before nine, far more often than I believed. It happened on ' +
492
- 'trains out of all proportion to the time I spent on them. It almost never happened in the ' +
493
- 'evenings I set aside for it, and it did not happen once in the three months of 2017 when ' +
494
- 'I rented a room specifically to write in.'),
495
- quote('The notebooks remember my working life more accurately than I do, because they were written before I had a story about it.'),
496
- p('I had a story about that room for years. I thought of it as a good period. The notebook ' +
497
- 'from those months is mostly lists of what I meant to do the next day, in increasingly ' +
498
- 'neat handwriting.'),
499
- h2('Handwriting as a gauge'),
500
- p('The handwriting became the most reliable signal in the whole set. When I was writing well, ' +
501
- 'the pages are messy: crossed out, arrowed, a sentence squeezed sideways into the margin. ' +
502
- 'When I was avoiding the work, the pages are tidy and the lists are long. I now look at the ' +
503
- 'week’s pages on Friday afternoons for exactly this reason. Neat pages mean something is ' +
504
- 'wrong.'),
505
- p('It also shows the moment a piece takes hold. There is a page in notebook twenty-six where ' +
506
- 'the notes for an essay about my grandfather’s allotment stop being notes and become ' +
507
- 'sentences, halfway down, in the middle of a thought. The pen changes too. I must have gone ' +
508
- 'to find a better one.'),
509
- h2('The ideas that did survive'),
510
- p('A few ideas did come back from the shelf, and the way they came back was instructive. ' +
511
- 'Notebook seven, from 2013, has a line at the bottom of a page that reads, in full: the ' +
512
- 'walk is the draft. I wrote it on a bench by the canal and did nothing with it for eleven ' +
513
- 'years. It became the essay about the long way to the library, and when I wrote that essay ' +
514
- 'I had no memory of the line at all.'),
515
- p('The same thing happened three more times. Each idea that turned into a finished piece ' +
516
- 'appears in the notebooks at least twice, years apart and in different words, as if I had ' +
517
- 'to arrive at it more than once before it would stay. The ideas I wrote down only once, ' +
518
- 'however excited the handwriting, came to nothing.'),
519
- p('It has made me calmer about forgetting. An idea that is going to matter will turn up ' +
520
- 'again, and the notebook’s job is to catch it when it does.'),
521
- h2('What I changed'),
522
- p('Reading them has changed three things about how I work, all of them small.'),
523
- numbered('I write the time and place at the top of every page again. I drifted out of it around ' +
524
- '2021, and the notebooks from those years are much harder to read back.'),
525
- numbered('I protect mornings and trains, and I no longer plan writing evenings. The evidence ' +
526
- 'against them is fourteen years deep.'),
527
- numbered('I stopped keeping lists of ideas. Anything that matters comes back on its own, usually ' +
528
- 'within a month, and the lists mostly recorded what I felt guilty about.'),
529
- p('I also numbered the spines at last, with a white pencil. Forty is a satisfying number to ' +
530
- 'reach and a slightly alarming number to see on a shelf. The forty-first is on the desk as ' +
531
- 'I write this, a third full, with this essay’s first opening on page six.'),
532
- rule(),
533
- h4('Notes'),
534
- numbered('The squared paper was the right choice and I should never have left it. Lines impose a ' +
535
- 'size on handwriting that squares do not.'),
536
- numbered('Several readers have asked which notebooks I use. It changes with whatever the stationer ' +
537
- 'on the corner stocks, which may be why they get filled: none of them feels too good to ' +
538
- 'write in.'),
539
- ],
540
- },
541
- {
542
- title: 'Letter: the slow week',
543
- slug: 'letter-the-slow-week',
544
- excerpt: 'Snow closed the line to York for five days, and the unplanned mornings turned into the most rested writing I have done this year.',
545
- publishedAt: '2026-02-15T07:30:00.000Z',
546
- categorySlug: 'letters',
547
- tagSlugs: ['habits', 'attention'],
548
- body: ({ media }) => [
549
- p('Snow closed the line to York for five days last week, and the press told those of us who ' +
550
- 'commute to stay at home. I had a draft due at the end of the month and a plan to write it ' +
551
- 'in the evenings. Instead I had five empty mornings and no train.'),
552
- p('The first day was wasted, cheerfully. I made coffee slowly, read the Saturday paper on a ' +
553
- 'Monday, and walked to the end of the street to look at the canal, which had frozen at ' +
554
- 'the edges. On the second day I sat down at nine out of habit and wrote for three hours ' +
555
- 'without noticing the time.'),
556
- ...figure(media['pour-over'], 'The coffee that sets the pace of a slow morning. It takes about four minutes, which is roughly how long a first sentence takes.'),
557
- p('By Friday the draft was finished, a fortnight early, which has never happened before. I ' +
558
- 'have been trying to work out what was different, and the honest answer is almost nothing ' +
559
- 'apart from the timetable. Every morning was the same length, and nothing was waiting at ' +
560
- 'the end of it.'),
561
- p('I cannot make every week a snow week, but I have kept one thing from it. On the two days ' +
562
- 'I work from home, the first hour now has no fixed end. When the sentence is ready I start, ' +
563
- 'and when it is not I make another coffee. So far it has not cost me a single deadline.'),
564
- p('The line reopened on Monday. The train was full of people describing their own slow weeks, ' +
565
- 'and I suspect a few of them are also trying to work out how to keep one.'),
566
- ],
567
- },
568
- {
569
- title: 'Reading on the 7:52',
570
- slug: 'reading-on-the-752',
571
- excerpt: 'Three mornings a week, twenty-five minutes from Leeds to York. Over six years that train has become the best reading room I know, for reasons that have little to do with the view.',
572
- publishedAt: '2026-05-31T08:40:00.000Z',
573
- cover: 'platform',
574
- categorySlug: 'reading',
575
- tagSlugs: ['trains', 'attention'],
576
- body: () => [
577
- p('Three mornings a week I take the 7:52 from Leeds to York. It is a twenty-five minute ' +
578
- 'journey, a little longer when the train waits outside Church Fenton for a freight service ' +
579
- 'to clear, and over six years it has become the place where I do most of my reading. I ' +
580
- 'worked it out last month: something over three hundred hours, which at my pace is about ' +
581
- 'forty-five books.'),
582
- p('The train has no particular virtue as a place. It is a three-carriage unit that is usually ' +
583
- 'too warm and sometimes too full. What it has is a structure nothing else in my week offers: ' +
584
- 'a fixed start, a fixed end, and nothing I am allowed to do in between.'),
585
- h2('A reading room with a timetable'),
586
- p('Most advice about reading more is advice about finding time, and time was never my ' +
587
- 'problem. I have plenty of evenings with an hour free. What those evenings lack is a ' +
588
- 'boundary. At home an hour of reading is an hour I could spend on something else, and the ' +
589
- 'book has to win that argument every few pages.'),
590
- p('On the train the argument is over before it starts. There is nowhere to go, the work will ' +
591
- 'not begin until York, and twenty-five minutes is long enough to read a chapter and short ' +
592
- 'enough that I never feel I ought to be doing something more serious with it.'),
593
- quote('On the train the book does not have to win an argument with the rest of my life.'),
594
- h2('What I read there'),
595
- p('After the first year I noticed that I choose train books differently. They are rarely the ' +
596
- 'books I most want to read. They have sections short enough to survive an announcement ' +
597
- 'about the next station, and they are long enough to last a few weeks, so that the book ' +
598
- 'becomes part of the journey.'),
599
- bullet('Collections of essays, which end before York more often than chapters do.'),
600
- bullet('Long novels in short chapters. Dickens, written for monthly parts, is ideal.'),
601
- bullet('Books about places, read while passing through a different one.'),
602
- bullet('Anything I would feel self-conscious reading at home, which mostly means poetry.'),
603
- p('What I do not read on the train is anything for work. The press sends manuscripts as files, ' +
604
- 'and in the first month I made a rule that the laptop stays in the bag until the platform ' +
605
- 'at York. I have broken it perhaps five times, each time because I had to, and each time ' +
606
- 'the rest of the day felt slightly shorter.'),
607
- h2('Paper, mostly'),
608
- p('I read paper on the train. I tried an e-reader for most of 2022, and it was lighter, it ' +
609
- 'held more, and I read less. A book on a screen is one tap away from the next book, and on ' +
610
- 'a twenty-five minute journey I spent too many of those minutes choosing.'),
611
- p('A paperback has the advantage of being only itself. It keeps a record, too: a ticket used ' +
612
- 'as a bookmark, a coffee ring from a morning the carriage lurched at Micklefield, a corner ' +
613
- 'turned down at the paragraph I meant to copy out when I reached my desk. Some of the books ' +
614
- 'are now inseparable from the line.'),
615
- bullet('*Bleak House*, over eleven weeks in the winter of 2021, a chapter a journey, which is still ' +
616
- 'far quicker than its first readers had it.'),
617
- bullet('*The Old Ways*, by Robert Macfarlane, in the spring of 2023, while the fields outside turned ' +
618
- 'from brown to green.'),
619
- bullet('Montaigne’s essays, in a thick paperback that has travelled to York about two hundred times ' +
620
- 'and is still not finished.'),
621
- h2('The company'),
622
- p('The 7:52 has regulars. A man in a green coat does the cryptic crossword and finishes it, as ' +
623
- 'far as I can tell, every day before Garforth. A student reads law textbooks with a ' +
624
- 'highlighter in each hand. For three years a woman opposite me read the same thick ' +
625
- 'paperback, and I never found out what it was, because she kept a paper cover on it.'),
626
- p('Nobody speaks. A carriage of people reading in silence has a particular quality, and I have ' +
627
- 'come to think it does some of the work. It is easier to keep your attention when everyone ' +
628
- 'around you is keeping theirs.'),
629
- h2('Evenings are different'),
630
- p('The 17:20 home is a different train. It is fuller, people are tired, and the journey that ' +
631
- 'carries me to York so easily is somehow longer in the other direction. I have stopped ' +
632
- 'trying to read on it. I look out at the fields past Church Fenton and let the day’s ' +
633
- 'manuscript settle.'),
634
- p('For a long time I counted that as time wasted. The notebooks disagree: a surprising number ' +
635
- 'of first lines are dated in the evening, on the 17:20, somewhere between York and Leeds.'),
636
- p('If I left the press tomorrow, the train is what I would miss. I have wondered whether I ' +
637
- 'could recreate it at a kitchen table, with a fixed twenty-five minutes, a book chosen for ' +
638
- 'the purpose and the laptop in another room. I suspect the timetable is doing more than I ' +
639
- 'could reproduce. For now I am glad of the 7:52, delays and all.'),
640
- rule(),
641
- h4('Notes'),
642
- numbered('My pace is about forty pages an hour for fiction, and closer to twenty-five for anything ' +
643
- 'with footnotes.'),
644
- numbered('The man in the green coat was absent for most of March. He came back in April with a new ' +
645
- 'coat, also green.'),
646
- ],
647
- },
648
- {
649
- title: 'Letter: what I read in August',
650
- slug: 'letter-what-i-read-in-august',
651
- excerpt: 'A month with fewer trains and more time in the garden: three books finished, one abandoned, and a note on reading slowly in the heat.',
652
- publishedAt: '2026-08-30T07:00:00.000Z',
653
- categorySlug: 'letters',
654
- tagSlugs: ['rereading', 'trains'],
655
- body: () => [
656
- p('August is the quietest month at the press, and I took the train only four times. The ' +
657
- 'reading moved to the garden, where it is slower, because there is always a reason to look ' +
658
- 'up.'),
659
- bullet('*The Peregrine*, by J. A. Baker. A single winter of watching falcons in Essex, written so ' +
660
- 'intensely that I could manage only ten pages at a time. I finished it over three weeks and ' +
661
- 'would not have wanted it faster.'),
662
- bullet('*Wanderlust*, by Rebecca Solnit. A history of walking I had meant to read for years. The ' +
663
- 'chapters on walking in cities gave me the start of an essay about the canal, which may ' +
664
- 'appear here in the autumn.'),
665
- bullet('*Stoner*, by John Williams. A reread, the fourth. Every time I notice a different kindness ' +
666
- 'in it.'),
667
- bullet('A novel I abandoned at page ninety. I will not name it, because the fault was probably the ' +
668
- 'weather.'),
669
- p('Reading in the heat taught me something the notebooks already knew. I read better in ' +
670
- 'short sittings with a clear end, and in the garden there is no York to arrive at. So I ' +
671
- 'started setting a timer for thirty minutes, which felt ridiculous for about two days and ' +
672
- 'then felt like the train.'),
673
- p('September brings the timetable back, and the 7:52 with it. The first train book of the ' +
674
- 'autumn is already in my bag: a collection of essays short enough to finish before York.'),
675
- p('As ever, replies to this letter come straight to me. Two of this month’s books were ' +
676
- 'suggestions from readers, and I am grateful for both.'),
677
- ],
678
- },
679
- ];
96
+ const ENGLISH_SCHEMA = blogSchema(BLOG_COPY_EN);
97
+ export const category = ENGLISH_SCHEMA.category;
98
+ export const tag = ENGLISH_SCHEMA.tag;
99
+ export const post = ENGLISH_SCHEMA.post;
100
+ export const page = ENGLISH_SCHEMA.page;
101
+ export const BLOG_COLLECTIONS = ENGLISH_SCHEMA.collections;
102
+ export const BLOG_TAXONOMIES = ENGLISH_SCHEMA.taxonomies;
103
+ // ---------------------------------------------------------------------------
104
+ // Demo writing (English, kept as named exports for existing callers)
105
+ // ---------------------------------------------------------------------------
106
+ export const BLOG_DEMO_CATEGORIES = BLOG_COPY_EN.categories;
107
+ export const BLOG_DEMO_TAGS = BLOG_COPY_EN.tags;
108
+ export const BLOG_DEMO_POSTS = BLOG_COPY_EN.posts;
680
109
  const BLOCK_VERSION = '1.0.0';
681
- function requiredPost(slug) {
682
- const found = BLOG_DEMO_POSTS.find((demo) => demo.slug === slug);
110
+ function requiredPost(copy, slug) {
111
+ const found = copy.posts.find((demo) => demo.slug === slug);
683
112
  if (found === undefined) {
684
113
  throw new CogentaError({
685
114
  code: 'BLUEPRINT_REGISTRY_CORRUPT',
686
- message: `The blog blueprint names a demo post "${slug}" that BLOG_DEMO_POSTS does not declare.`,
687
- hint: 'Keep the home page references in buildBlogDemoPages in step with BLOG_DEMO_POSTS.',
115
+ message: `The blog blueprint names a demo post "${slug}" that its ${copy.locale} copy does not declare.`,
116
+ hint: 'Keep featuredSlug and latestLetterSlug in step with the posts of the copy.',
688
117
  });
689
118
  }
690
119
  return found;
691
120
  }
692
- function answer(text) {
693
- return [p(text)];
694
- }
695
121
  /**
696
- * `home`, `about`, `archive` and `newsletter`. A function of the ingested
697
- * media and of the site's name, for the same reason `store.ts`'s
698
- * `buildStoreDemoPages` is a function of `media`.
122
+ * `home`, `about`, `archive`, `newsletter` and `photo-credits`. A function of
123
+ * the ingested media and of the site's name, for the same reason
124
+ * `store.ts`'s `buildStoreDemoPages` is a function of `media`.
699
125
  *
700
126
  * The home page features an essay that is neither among the five newest
701
127
  * (the index right under it) nor among the three oldest (the shelf further
702
128
  * down), so no piece appears twice on the page.
703
129
  */
704
- export function buildBlogDemoPages(media, siteName = DEFAULT_PUBLICATION_NAME) {
705
- const featured = requiredPost('what-the-second-draft-is-for');
706
- const latestLetter = requiredPost('letter-what-i-read-in-august');
130
+ export function buildBlogDemoPages(media, siteName = DEFAULT_PUBLICATION_NAME, copy = BLOG_COPY_EN) {
131
+ const featured = requiredPost(copy, copy.featuredSlug);
132
+ const latestLetter = requiredPost(copy, copy.latestLetterSlug);
707
133
  const featuredCover = featured.cover === undefined ? undefined : media[featured.cover];
708
- const desk = media.desk;
134
+ const { home, about, archive, newsletter, credits, slugs } = copy;
135
+ const categoryPath = `/${copy.schema.category.name}`;
709
136
  return [
710
137
  {
711
- title: 'Home',
712
- slug: 'home',
138
+ title: home.title,
139
+ slug: slugs.home,
140
+ seoDescription: home.seoDescription(siteName).slice(0, 160),
713
141
  blocks: [
714
142
  {
715
143
  _key: 'demo-home-hero',
716
144
  _type: 'hero',
717
145
  _version: BLOCK_VERSION,
718
- eyebrow: 'Start here',
146
+ eyebrow: home.eyebrow,
719
147
  title: featured.title,
720
148
  subtitle: featured.excerpt,
721
149
  ...(featuredCover === undefined ? {} : { media: featuredCover }),
722
- actions: [{ label: 'Read the essay', target: { href: `/blog/${featured.slug}` } }],
150
+ actions: [{ label: home.readEssay, target: { href: `/blog/${featured.slug}` } }],
723
151
  },
724
152
  {
725
153
  _key: 'demo-home-latest',
726
154
  _type: 'collectionList',
727
155
  _version: BLOCK_VERSION,
728
- title: 'Latest',
156
+ title: home.latestTitle,
729
157
  collection: 'post',
730
158
  sort: { field: 'createdAt', direction: 'desc' },
731
159
  limit: 5,
@@ -735,114 +163,91 @@ export function buildBlogDemoPages(media, siteName = DEFAULT_PUBLICATION_NAME) {
735
163
  _key: 'demo-home-epigraph',
736
164
  _type: 'quote',
737
165
  _version: BLOCK_VERSION,
738
- text: 'The greatest part of a writer’s time is spent in reading, in order to write: a man will turn over half a library to make one book.',
739
- author: 'Samuel Johnson',
740
- role: 'In James Boswell’s Life of Johnson, 1775',
166
+ text: home.epigraph.text,
167
+ author: home.epigraph.author,
168
+ role: home.epigraph.role,
741
169
  },
742
170
  {
743
171
  _key: 'demo-home-archive',
744
172
  _type: 'collectionList',
745
173
  _version: BLOCK_VERSION,
746
- title: 'From the archive',
174
+ title: home.archiveTitle,
747
175
  collection: 'post',
748
176
  sort: { field: 'createdAt', direction: 'asc' },
749
177
  limit: 3,
750
178
  layout: 'grid',
751
179
  },
180
+ {
181
+ _key: 'demo-home-strip',
182
+ _type: 'collectionList',
183
+ _version: BLOCK_VERSION,
184
+ title: home.moreTitle,
185
+ collection: 'post',
186
+ sort: { field: 'createdAt', direction: 'desc' },
187
+ limit: 6,
188
+ layout: 'carousel',
189
+ },
752
190
  {
753
191
  _key: 'demo-home-subjects',
754
192
  _type: 'featureGrid',
755
193
  _version: BLOCK_VERSION,
756
- title: 'What I write about',
757
- items: BLOG_DEMO_CATEGORIES.map((demo) => ({
194
+ title: home.subjectsTitle,
195
+ items: copy.categories.map((demo) => ({
758
196
  _key: `demo-subject-${demo.slug}`,
759
197
  title: demo.name,
760
198
  text: demo.description,
761
- link: { href: `/category/${demo.slug}` },
199
+ link: { href: `${categoryPath}/${demo.slug}` },
762
200
  })),
763
201
  },
764
202
  {
765
203
  _key: 'demo-home-letter',
766
204
  _type: 'cta',
767
205
  _version: BLOCK_VERSION,
768
- title: 'The Sunday letter',
769
- text: 'Every other Sunday, a short letter about what I have been reading and what the notebooks keep circling. Each one is published here and in the feed.',
206
+ title: home.letterTitle,
207
+ text: home.letterText,
770
208
  actions: [
771
- { label: 'How to follow it', target: { href: '/newsletter' }, emphasis: 'primary' },
772
- { label: 'Read the latest letter', target: { href: `/blog/${latestLetter.slug}` } },
209
+ {
210
+ label: home.letterFollow,
211
+ target: { href: `/${slugs.newsletter}` },
212
+ emphasis: 'primary',
213
+ },
214
+ { label: home.letterLatest, target: { href: `/blog/${latestLetter.slug}` } },
773
215
  ],
774
216
  },
775
217
  ],
776
218
  },
777
219
  {
778
- title: 'About',
779
- slug: 'about',
220
+ title: about.title,
221
+ slug: slugs.about,
780
222
  blocks: [
781
223
  {
782
224
  _key: 'demo-about-prose',
783
225
  _type: 'prose',
784
226
  _version: BLOCK_VERSION,
785
- body: [
786
- p(`${siteName} is where I publish essays and letters about reading, writing and walking. ` +
787
- 'It started in 2024 as a way to finish things: I had thirteen years of notebooks and ' +
788
- 'almost nothing in them that anyone else could read.'),
789
- p('I live in Leeds and work three days a week as a copy editor at a university press in ' +
790
- 'York, which is why so much of what appears here was written on a train. On the other ' +
791
- 'two days I write, mostly in the morning, and mostly by hand first.'),
792
- ...figure(desk, 'The desk by the window, where the pieces that were not written on a train were written.'),
793
- h2('What you will find here'),
794
- p('Essays, roughly one a month, between a thousand and two thousand words, on whatever ' +
795
- 'the notebooks have been circling. Letters every other Sunday, which are shorter and ' +
796
- 'closer to what I would write to a friend. Everything is filed under one of four ' +
797
- 'subjects, and the archive lists all of it by year.'),
798
- h2('How it is made'),
799
- p('Every piece starts in a plain text file and goes through at least two full drafts ' +
800
- 'before it appears here. There is no advertising, no sponsored link and no tracking ' +
801
- 'beyond a count of visits that stores nothing about you. Corrections are made in place ' +
802
- 'and noted at the end of the piece.'),
803
- ],
227
+ body: [...about.body({ siteName, desk: media.desk })],
804
228
  },
805
229
  {
806
230
  _key: 'demo-about-questions',
807
231
  _type: 'faq',
808
232
  _version: BLOCK_VERSION,
809
- title: 'Questions readers ask',
810
- items: [
811
- {
812
- _key: 'demo-faq-cadence',
813
- question: 'How often do you publish?',
814
- answer: answer('An essay about once a month and a letter every other Sunday. Nothing goes out to fill a slot: an essay that is not ready waits for the next month.'),
815
- },
816
- {
817
- _key: 'demo-faq-quoting',
818
- question: 'Can I quote or republish a piece?',
819
- answer: answer('Quote as much as is useful, with a link back to the original. To republish a whole piece, write to me first. The answer is almost always yes.'),
820
- },
821
- {
822
- _key: 'demo-faq-guests',
823
- question: 'Do you publish guest essays?',
824
- answer: answer(`No. Everything on ${siteName} is written by one person, which is most of the point of it.`),
825
- },
826
- {
827
- _key: 'demo-faq-feed',
828
- question: 'Is there a feed?',
829
- answer: answer('Yes. It carries every essay and letter in full, and its link is at the foot of every page.'),
830
- },
831
- ],
233
+ title: about.faqTitle,
234
+ items: about.faq(siteName).map((item) => ({
235
+ _key: item.key,
236
+ question: item.question,
237
+ answer: answer(item.answer),
238
+ })),
832
239
  },
833
240
  ],
834
241
  },
835
242
  {
836
- title: 'Archive',
837
- slug: 'archive',
243
+ title: archive.title,
244
+ slug: slugs.archive,
838
245
  blocks: [
839
246
  {
840
247
  _key: 'demo-archive-intro',
841
248
  _type: 'prose',
842
249
  _version: BLOCK_VERSION,
843
- body: [
844
- p(`Every essay and letter published on ${siteName}, newest first, grouped by the year it appeared.`),
845
- ],
250
+ body: [p(archive.intro(siteName))],
846
251
  },
847
252
  {
848
253
  _key: 'demo-archive-index',
@@ -856,33 +261,42 @@ export function buildBlogDemoPages(media, siteName = DEFAULT_PUBLICATION_NAME) {
856
261
  ],
857
262
  },
858
263
  {
859
- title: 'The Sunday letter',
860
- slug: 'newsletter',
264
+ title: newsletter.title,
265
+ slug: slugs.newsletter,
861
266
  blocks: [
862
267
  {
863
268
  _key: 'demo-newsletter-prose',
864
269
  _type: 'prose',
865
270
  _version: BLOCK_VERSION,
866
- body: [
867
- p('Every other Sunday I write a short letter: what I have been reading, a paragraph or two ' +
868
- 'about whatever the notebooks are circling, and now and then the first version of an ' +
869
- 'idea that becomes an essay a few months later.'),
870
- p(`Each letter is published on ${siteName} on the Sunday morning, filed under Letters, ` +
871
- 'and the feed carries it in full the same hour. A feed reader is the simplest way to ' +
872
- 'follow along, and the one I use myself.'),
873
- p('There are no sponsors and no sequence of welcome messages. Replies reach me directly, ' +
874
- 'and several letters have begun as an answer to one.'),
875
- ],
271
+ body: newsletter.paragraphs(siteName).map((text) => p(text)),
876
272
  },
877
273
  {
878
274
  _key: 'demo-newsletter-follow',
879
275
  _type: 'cta',
880
276
  _version: BLOCK_VERSION,
881
- title: 'Follow the letters',
882
- text: 'The feed carries every letter and essay in full, the morning each one is published.',
277
+ title: newsletter.ctaTitle,
278
+ text: newsletter.ctaText,
883
279
  actions: [
884
- { label: 'Subscribe to the feed', target: { href: '/feed.xml' }, emphasis: 'primary' },
885
- { label: 'Read past letters', target: { href: '/category/letters' } },
280
+ { label: newsletter.subscribe, target: { href: '/feed.xml' }, emphasis: 'primary' },
281
+ {
282
+ label: newsletter.past,
283
+ target: { href: `${categoryPath}/${copy.lettersCategorySlug}` },
284
+ },
285
+ ],
286
+ },
287
+ ],
288
+ },
289
+ {
290
+ title: credits.title,
291
+ slug: slugs.credits,
292
+ blocks: [
293
+ {
294
+ _key: 'demo-credits-prose',
295
+ _type: 'prose',
296
+ _version: BLOCK_VERSION,
297
+ body: [
298
+ p(credits.intro),
299
+ ...BLOG_PHOTO_CREDITS.map((credit) => bullet(credits.line(credit))),
886
300
  ],
887
301
  },
888
302
  ],
@@ -908,67 +322,45 @@ function blogPalette() {
908
322
  }
909
323
  return skin.color;
910
324
  }
325
+ /** The bundled photographs and the file each one is cut from (`assets/photos/blog/`). */
326
+ const BLOG_PHOTO_FILES = [
327
+ { name: 'notebook-and-coffee', photo: 'blog/notebook-and-coffee.jpg' },
328
+ { name: 'desk', photo: 'blog/desk-setup.jpg' },
329
+ { name: 'notebooks', photo: 'blog/notebooks.jpg' },
330
+ { name: 'platform', photo: 'blog/platform.jpg' },
331
+ { name: 'pour-over', photo: 'blog/pour-over.jpg' },
332
+ { name: 'typing', photo: 'blog/typing.jpg' },
333
+ { name: 'library', photo: 'blog/library.jpg' },
334
+ ];
911
335
  /**
912
- * Five photographs, all bundled (`assets/photos/blog/`), each cropped once
913
- * to remove anything a reader would try and fail to read. `spec` is the
914
- * procedural fallback `seedDemoMedia` would use if a file were ever missing;
915
- * with every file present it is never rendered.
336
+ * Seven photographs, all bundled, each cropped once to remove anything a
337
+ * reader would try and fail to read. `spec` is the procedural fallback
338
+ * `seedDemoMedia` would use if a file were ever missing; with every file
339
+ * present it is never rendered.
340
+ *
341
+ * One per essay (a "Letter" stays text-only by design — a personal letter is
342
+ * not a produced piece the way an essay is), so the front of the site is
343
+ * never carrying the same photograph in three places at once.
916
344
  */
917
- export const BLOG_MEDIA_SPECS = [
918
- {
919
- name: 'notebook-and-coffee',
920
- spec: coverArt(blogPalette(), 1),
921
- alt: 'An open lined notebook with a pen across it, beside a cup of black coffee',
922
- photo: 'blog/notebook-and-coffee.jpg',
923
- },
924
- {
925
- name: 'desk',
926
- spec: coverArt(blogPalette(), 2),
927
- alt: 'A white desk by a window with a closed laptop, two notebooks and a small plant',
928
- photo: 'blog/desk-setup.jpg',
929
- },
930
- {
931
- name: 'notebooks',
932
- spec: coverArt(blogPalette(), 3),
933
- alt: 'A tall stack of worn notebooks with cloth and leather covers on a wooden desk',
934
- photo: 'blog/notebooks.jpg',
935
- },
936
- {
937
- name: 'platform',
938
- spec: coverArt(blogPalette(), 4),
939
- alt: 'Commuters with backpacks waiting on a station platform beside a train',
940
- photo: 'blog/platform.jpg',
941
- },
942
- {
943
- name: 'pour-over',
944
- spec: coverArt(blogPalette(), 5),
945
- alt: 'A ceramic coffee dripper on a glass pour-over jug against a pale wall',
946
- photo: 'blog/pour-over.jpg',
947
- },
948
- ];
345
+ export function blogMediaSpecs(copy) {
346
+ return BLOG_PHOTO_FILES.map(({ name, photo }, index) => ({
347
+ name,
348
+ spec: coverArt(blogPalette(), index + 1),
349
+ alt: copy.mediaAlt[name] ?? name,
350
+ photo,
351
+ }));
352
+ }
353
+ export const BLOG_MEDIA_SPECS = blogMediaSpecs(BLOG_COPY_EN);
949
354
  /** Header, footer and the header's call to action. Every path is a page this blueprint seeds or a route the server provides. */
950
- export const BLOG_MENUS = {
951
- header: [
952
- { label: 'Archive', url: '/archive' },
953
- { label: 'About', url: '/about' },
954
- ],
955
- footer: [
956
- { label: 'Archive', url: '/archive' },
957
- { label: 'About', url: '/about' },
958
- { label: 'The Sunday letter', url: '/newsletter' },
959
- { label: 'Feed', url: '/feed.xml' },
960
- ],
961
- headerAction: { label: 'Subscribe', url: '/newsletter' },
962
- };
963
- export const BLOG_SITE_SETTINGS = {
964
- 'general.tagline': 'Essays and letters on reading, writing and walking.',
965
- 'general.socialLinks': [
966
- { label: 'Mastodon', url: 'https://mastodon.social/@example' },
967
- { label: 'Bluesky', url: 'https://bsky.app/profile/example.bsky.social' },
968
- { label: 'Instagram', url: 'https://instagram.com/example' },
969
- ],
970
- 'general.footerNote': 'Written in Leeds and on the train to York. Quote freely, with a link back to the original.',
971
- };
355
+ export const BLOG_MENUS = BLOG_COPY_EN.menus;
356
+ export function blogSiteSettings(copy) {
357
+ return {
358
+ 'general.tagline': copy.settings.tagline,
359
+ 'general.socialLinks': copy.settings.socialLinks,
360
+ 'general.footerNote': copy.settings.footerNote,
361
+ };
362
+ }
363
+ export const BLOG_SITE_SETTINGS = blogSiteSettings(BLOG_COPY_EN);
972
364
  /**
973
365
  * The sidebar a reader of a personal blog expects beside an essay, a subject
974
366
  * or tag archive and search results: who writes it, a way to search, the
@@ -977,74 +369,80 @@ export const BLOG_SITE_SETTINGS = {
977
369
  * index, nor on About, Archive and the letter page, which say the same things
978
370
  * at length. Under an essay, the related pieces carry the reader on.
979
371
  */
980
- const BLOG_READING_PAGES = {
981
- pages: {
982
- mode: 'only',
983
- targets: [
984
- { kind: 'collection', collection: 'post' },
985
- { kind: 'taxonomy', taxonomy: 'category' },
986
- { kind: 'taxonomy', taxonomy: 'tag' },
987
- { kind: 'dateArchive' },
988
- { kind: 'search' },
989
- ],
990
- },
991
- };
992
- export const BLOG_WIDGETS = [
993
- {
994
- area: 'sidebar',
995
- type: 'about',
996
- title: 'About',
997
- settings: {
998
- body: 'Essays and letters by a copy editor who lives in Leeds and writes most mornings, often on the train to York. One essay a month, a letter every other Sunday.',
999
- link: { label: 'More about me', href: '/about' },
372
+ export function blogWidgets(copy) {
373
+ const w = copy.widgets;
374
+ const readingPages = {
375
+ pages: {
376
+ mode: 'only',
377
+ targets: [
378
+ { kind: 'collection', collection: 'post' },
379
+ { kind: 'taxonomy', taxonomy: copy.schema.category.name },
380
+ { kind: 'taxonomy', taxonomy: copy.schema.tag.name },
381
+ { kind: 'dateArchive' },
382
+ { kind: 'search' },
383
+ ],
1000
384
  },
1001
- visibility: BLOG_READING_PAGES,
1002
- },
1003
- {
1004
- area: 'sidebar',
1005
- type: 'search',
1006
- title: 'Search',
1007
- settings: { placeholder: 'Essays and letters' },
1008
- visibility: BLOG_READING_PAGES,
1009
- },
1010
- {
1011
- area: 'sidebar',
1012
- type: 'recentEntries',
1013
- title: 'Recently',
1014
- settings: { collection: 'post', count: 4, showDate: true },
1015
- visibility: BLOG_READING_PAGES,
1016
- },
1017
- {
1018
- area: 'sidebar',
1019
- type: 'terms',
1020
- title: 'Subjects',
1021
- settings: { taxonomy: 'category', showCounts: true, hierarchical: false, hideEmpty: true },
1022
- visibility: BLOG_READING_PAGES,
1023
- },
1024
- {
1025
- area: 'sidebar',
1026
- type: 'tagCloud',
1027
- title: 'Tags',
1028
- settings: { taxonomy: 'tag', maxTerms: 20, showCounts: false },
1029
- visibility: BLOG_READING_PAGES,
1030
- },
1031
- {
1032
- area: 'sidebar',
1033
- type: 'archives',
1034
- title: 'By year',
1035
- settings: { collection: 'post', granularity: 'year', showCounts: true },
1036
- visibility: BLOG_READING_PAGES,
1037
- },
1038
- {
1039
- area: 'content-after',
1040
- type: 'relatedEntries',
1041
- title: 'Further reading',
1042
- settings: { count: 3, showDate: true, showImage: false },
1043
- visibility: {
1044
- pages: { mode: 'only', targets: [{ kind: 'collection', collection: 'post' }] },
385
+ };
386
+ return [
387
+ {
388
+ area: 'sidebar',
389
+ type: 'about',
390
+ title: w.aboutTitle,
391
+ settings: { body: w.aboutBody, link: { label: w.aboutLink, href: `/${copy.slugs.about}` } },
392
+ visibility: readingPages,
1045
393
  },
1046
- },
1047
- ];
394
+ {
395
+ area: 'sidebar',
396
+ type: 'search',
397
+ title: w.searchTitle,
398
+ settings: { placeholder: w.searchPlaceholder },
399
+ visibility: readingPages,
400
+ },
401
+ {
402
+ area: 'sidebar',
403
+ type: 'recentEntries',
404
+ title: w.recentTitle,
405
+ settings: { collection: 'post', count: 4, showDate: true },
406
+ visibility: readingPages,
407
+ },
408
+ {
409
+ area: 'sidebar',
410
+ type: 'terms',
411
+ title: w.subjectsTitle,
412
+ settings: {
413
+ taxonomy: copy.schema.category.name,
414
+ showCounts: true,
415
+ hierarchical: false,
416
+ hideEmpty: true,
417
+ },
418
+ visibility: readingPages,
419
+ },
420
+ {
421
+ area: 'sidebar',
422
+ type: 'tagCloud',
423
+ title: w.tagsTitle,
424
+ settings: { taxonomy: copy.schema.tag.name, maxTerms: 20, showCounts: false },
425
+ visibility: readingPages,
426
+ },
427
+ {
428
+ area: 'sidebar',
429
+ type: 'archives',
430
+ title: w.yearTitle,
431
+ settings: { collection: 'post', granularity: 'year', showCounts: true },
432
+ visibility: readingPages,
433
+ },
434
+ {
435
+ area: 'content-after',
436
+ type: 'relatedEntries',
437
+ title: w.relatedTitle,
438
+ settings: { count: 3, showDate: true, showImage: false },
439
+ visibility: {
440
+ pages: { mode: 'only', targets: [{ kind: 'collection', collection: 'post' }] },
441
+ },
442
+ },
443
+ ];
444
+ }
445
+ export const BLOG_WIDGETS = blogWidgets(BLOG_COPY_EN);
1048
446
  export const BLOG_RECOMMENDED_AGENTS = [
1049
447
  {
1050
448
  name: 'seoAgent',
@@ -1068,65 +466,78 @@ export const BLOG_RECOMMENDED_AGENTS = [
1068
466
  * the `createdAt` ordering every `collectionList` sorts on agrees with the
1069
467
  * dates a reader sees.
1070
468
  */
1071
- async function seedBlogDemoContent(ctx) {
1072
- const { db, defaultLocale, adminId, media } = ctx;
1073
- const siteName = ctx.siteName?.trim() || DEFAULT_PUBLICATION_NAME;
1074
- const copy = { siteName, media };
1075
- const categoryStore = createTaxonomyStore({ db, taxonomy: category });
1076
- const tagStore = createTaxonomyStore({ db, taxonomy: tag });
1077
- const postStore = createContentStore({ db, collection: post, defaultLocale });
1078
- const pageStore = createContentStore({ db, collection: page, defaultLocale });
1079
- const categoryIdBySlug = new Map();
1080
- for (const demo of BLOG_DEMO_CATEGORIES) {
1081
- const term = await categoryStore.create({
1082
- slug: demo.slug,
1083
- labels: { [defaultLocale]: demo.name },
1084
- });
1085
- categoryIdBySlug.set(demo.slug, term.id);
1086
- }
1087
- const tagIdBySlug = new Map();
1088
- for (const demo of BLOG_DEMO_TAGS) {
1089
- const term = await tagStore.create({
1090
- slug: demo.slug,
1091
- labels: { [defaultLocale]: demo.name },
1092
- });
1093
- tagIdBySlug.set(demo.slug, term.id);
1094
- }
1095
- for (const demo of BLOG_DEMO_POSTS) {
1096
- const cover = demo.cover === undefined ? undefined : media[demo.cover];
1097
- await postStore.create({
1098
- status: 'published',
1099
- createdBy: adminId,
1100
- values: {
1101
- title: demo.title,
469
+ function seedBlogDemoContent(copy, model) {
470
+ return async (ctx) => {
471
+ const { db, defaultLocale, adminId, media } = ctx;
472
+ const siteName = ctx.siteName?.trim() || copy.defaultPublicationName;
473
+ const categoryStore = createTaxonomyStore({ db, taxonomy: model.category });
474
+ const tagStore = createTaxonomyStore({ db, taxonomy: model.tag });
475
+ const postStore = createContentStore({ db, collection: model.post, defaultLocale });
476
+ const pageStore = createContentStore({ db, collection: model.page, defaultLocale });
477
+ const categoryIdBySlug = new Map();
478
+ for (const demo of copy.categories) {
479
+ const term = await categoryStore.create({
1102
480
  slug: demo.slug,
1103
- excerpt: demo.excerpt,
1104
- body: demo.body(copy),
1105
- publishedAt: demo.publishedAt,
1106
- category: categoryIdBySlug.get(demo.categorySlug) ?? null,
1107
- tags: demo.tagSlugs.map((slug) => tagIdBySlug.get(slug)).filter((id) => id !== undefined),
1108
- ...(cover === undefined ? {} : { coverImage: cover }),
1109
- },
1110
- });
1111
- }
1112
- for (const demo of buildBlogDemoPages(media, siteName)) {
1113
- await pageStore.create({
1114
- status: 'published',
1115
- createdBy: adminId,
1116
- values: { title: demo.title, slug: demo.slug },
1117
- blocks: { blocks: demo.blocks.map(toBlockZoneEntry) },
1118
- });
1119
- }
481
+ labels: { [defaultLocale]: demo.name },
482
+ });
483
+ categoryIdBySlug.set(demo.slug, term.id);
484
+ }
485
+ const tagIdBySlug = new Map();
486
+ for (const demo of copy.tags) {
487
+ const term = await tagStore.create({
488
+ slug: demo.slug,
489
+ labels: { [defaultLocale]: demo.name },
490
+ });
491
+ tagIdBySlug.set(demo.slug, term.id);
492
+ }
493
+ for (const demo of copy.posts) {
494
+ const cover = demo.cover === undefined ? undefined : media[demo.cover];
495
+ await postStore.create({
496
+ status: 'published',
497
+ createdBy: adminId,
498
+ values: {
499
+ title: demo.title,
500
+ slug: demo.slug,
501
+ excerpt: demo.excerpt,
502
+ body: demo.body({ siteName, media }),
503
+ publishedAt: demo.publishedAt,
504
+ category: categoryIdBySlug.get(demo.categorySlug) ?? null,
505
+ tags: demo.tagSlugs.map((slug) => tagIdBySlug.get(slug)).filter((id) => id !== undefined),
506
+ ...(demo.topic === undefined ? {} : { topic: demo.topic }),
507
+ ...(cover === undefined ? {} : { coverImage: cover }),
508
+ },
509
+ });
510
+ }
511
+ for (const demo of buildBlogDemoPages(media, siteName, copy)) {
512
+ await pageStore.create({
513
+ status: 'published',
514
+ createdBy: adminId,
515
+ values: {
516
+ title: demo.title,
517
+ slug: demo.slug,
518
+ ...(demo.seoDescription === undefined ? {} : { seoDescription: demo.seoDescription }),
519
+ },
520
+ blocks: { blocks: demo.blocks.map(toBlockZoneEntry) },
521
+ });
522
+ }
523
+ };
1120
524
  }
1121
- export const blogContentPack = {
1122
- collections: BLOG_COLLECTIONS,
1123
- taxonomies: BLOG_TAXONOMIES,
1124
- recommendedAgents: BLOG_RECOMMENDED_AGENTS,
1125
- seedDemoContent: seedBlogDemoContent,
1126
- defaultTheme: '@cogenta/theme-blog',
1127
- menus: BLOG_MENUS,
1128
- widgets: BLOG_WIDGETS,
1129
- siteSettings: BLOG_SITE_SETTINGS,
1130
- mediaSpecs: BLOG_MEDIA_SPECS,
1131
- };
525
+ /** The whole blueprint, in one language. */
526
+ export function createBlogContentPack(locale) {
527
+ const copy = blogCopyFor(locale);
528
+ const model = copy === BLOG_COPY_EN ? ENGLISH_SCHEMA : blogSchema(copy);
529
+ return {
530
+ collections: model.collections,
531
+ taxonomies: model.taxonomies,
532
+ recommendedAgents: BLOG_RECOMMENDED_AGENTS,
533
+ seedDemoContent: seedBlogDemoContent(copy, model),
534
+ defaultTheme: '@cogenta/theme-blog',
535
+ menus: copy.menus,
536
+ widgets: blogWidgets(copy),
537
+ siteSettings: blogSiteSettings(copy),
538
+ mediaSpecs: blogMediaSpecs(copy),
539
+ };
540
+ }
541
+ /** The English pack, which `BLUEPRINT_CONTENT_PACKS` lists; `contentPackFor` picks by locale. */
542
+ export const blogContentPack = createBlogContentPack('en');
1132
543
  //# sourceMappingURL=blog.js.map