@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
@@ -2,15 +2,25 @@ import { CogentaError } from '@cogenta/core';
2
2
  import { createContentStore, defineCollection, f, validateCollectionSet, } from '@cogenta/schema';
3
3
  import { coverArt, heroArt } from '../demo-art/compositions.js';
4
4
  import { definePageCollection, SEO_FIELDS, toBlockZoneEntry, } from './content-pack.js';
5
+ import { RESTAURANT_COPY_EN } from './restaurant-copy-en.js';
6
+ import { RESTAURANT_COPY_FR } from './restaurant-copy-fr.js';
7
+ import { RESTAURANT_PHOTO_CREDITS } from './restaurant-credits.js';
5
8
  import { STARTING_SKINS } from './starting-skins.js';
6
9
  /**
7
10
  * The `restaurant` blueprint (L27, `@cogenta/theme-restaurant`): a
8
- * contemporary bistro in a former silk-weaving workshop on the slopes of the
9
- * Croix-Rousse in Lyon. A seasonal menu of nineteen dishes and wines in four
10
- * sections, a page for each of them, and the pages a real restaurant needs
11
- * before a guest arrives: the menu with its set menus and allergy notes, the
12
- * story of the kitchen and its suppliers, how to book, the private room,
13
- * opening hours and directions, and the legal notice.
11
+ * contemporary bistro in the former offices of a silk merchant on the slopes
12
+ * of the Croix-Rousse in Lyon. A seasonal menu of nineteen dishes and wines in
13
+ * four sections, a page for each of them, and the pages a real restaurant
14
+ * needs before a guest arrives: the menu with its set menus and allergy
15
+ * notes, the story of the kitchen and its suppliers, how to book, the private
16
+ * room, opening hours and directions, and the legal notice.
17
+ *
18
+ * Written in French and in English, not translated at render time (L36): the
19
+ * installer picks the copy for the site's default locale, and with it the
20
+ * addresses (`/carte/…` in French, `/menu/…` in English), the names of the
21
+ * sections of the menu that a dish stores, the collection names the
22
+ * administration shows and every word of demo content. Every photograph is a
23
+ * real one under an open licence, credited on its own page.
14
24
  *
15
25
  * **No booking engine, and nothing that pretends to be one.** A blueprint
16
26
  * writes content; contract B has no reservation block and nothing in a page
@@ -28,7 +38,10 @@ import { STARTING_SKINS } from './starting-skins.js';
28
38
  * (the lists sort on `id`, a UUIDv7, so insertion order is the menu's order).
29
39
  */
30
40
  export const DEFAULT_RESTAURANT_NAME = 'Laurier';
31
- const SECTIONS = ['Starters', 'Mains', 'Cheese and desserts', 'Wine by the glass'];
41
+ /** The copy for a site's default locale: French for any `fr*` locale, English otherwise. */
42
+ export function restaurantCopyFor(locale) {
43
+ return locale.toLowerCase().startsWith('fr') ? RESTAURANT_COPY_FR : RESTAURANT_COPY_EN;
44
+ }
32
45
  const PERMISSIONS = {
33
46
  read: ['public'],
34
47
  create: ['editor', 'admin'],
@@ -36,42 +49,45 @@ const PERMISSIONS = {
36
49
  delete: ['admin'],
37
50
  publish: ['admin'],
38
51
  };
39
- export const menuItem = defineCollection({
40
- name: 'menu_item',
41
- labels: { singular: 'Dish', plural: 'Menu' },
42
- routing: { pattern: '/menu/:slug' },
43
- fields: {
44
- name: f.text({ required: true, max: 120 }),
45
- slug: f.slug({ from: 'name', unique: true }),
46
- description: f.text({
47
- max: 300,
48
- multiline: true,
49
- admin: { label: 'Description', help: 'One or two sentences, printed under the name.' },
50
- }),
51
- price: f.number({ required: true, min: 0 }),
52
- currency: f.select({ options: ['EUR', 'CHF', 'GBP', 'DKK', 'USD'], default: 'EUR' }),
53
- category: f.select({
54
- options: [...SECTIONS],
55
- required: true,
56
- admin: { label: 'Section of the menu' },
57
- }),
58
- vegetarian: f.boolean({ default: false }),
59
- sourcing: f.text({
60
- max: 200,
61
- admin: { label: 'Where it comes from', help: 'The farm, boat or grower.' },
62
- }),
63
- pairing: f.text({ max: 160, admin: { label: 'To drink with it' } }),
64
- allergens: f.text({ max: 200 }),
65
- photo: f.media({ accept: ['image'] }),
66
- blocks: f.blocks(),
67
- ...SEO_FIELDS,
68
- },
69
- indexes: [['slug'], ['category']],
70
- permissions: PERMISSIONS,
71
- });
72
- export const page = definePageCollection('/:slug');
73
- export const RESTAURANT_COLLECTIONS = [menuItem, page];
74
- validateCollectionSet(RESTAURANT_COLLECTIONS);
52
+ /**
53
+ * The content model, named and routed in the copy's language. Field labels are
54
+ * not declared here: the administration translates them from its own
55
+ * dictionaries, and the options of `category` are the names of the sections
56
+ * the copy prints, so a French dish is filed under "Plats", not "Mains".
57
+ */
58
+ export function restaurantSchema(copy) {
59
+ const menuItem = defineCollection({
60
+ name: 'menu_item',
61
+ versioning: { drafts: true, history: true },
62
+ labels: copy.schema.dishLabels,
63
+ routing: { pattern: copy.schema.dishRoute },
64
+ fields: {
65
+ name: f.text({ required: true, max: 120 }),
66
+ slug: f.slug({ from: 'name', unique: true }),
67
+ description: f.text({ max: 300, multiline: true }),
68
+ price: f.number({ required: true, min: 0 }),
69
+ currency: f.select({ options: ['EUR', 'CHF', 'GBP', 'DKK', 'USD'], default: 'EUR' }),
70
+ category: f.select({ options: [...copy.sections], required: true }),
71
+ vegetarian: f.boolean({ default: false }),
72
+ sourcing: f.text({ max: 200 }),
73
+ pairing: f.text({ max: 160 }),
74
+ allergens: f.text({ max: 200 }),
75
+ photo: f.media({ accept: ['image'] }),
76
+ blocks: f.blocks(),
77
+ ...SEO_FIELDS,
78
+ },
79
+ indexes: [['slug'], ['category']],
80
+ permissions: PERMISSIONS,
81
+ });
82
+ const page = definePageCollection('/:slug');
83
+ const collections = [menuItem, page];
84
+ validateCollectionSet(collections);
85
+ return { menuItem, page, collections };
86
+ }
87
+ const ENGLISH_SCHEMA = restaurantSchema(RESTAURANT_COPY_EN);
88
+ export const menuItem = ENGLISH_SCHEMA.menuItem;
89
+ export const page = ENGLISH_SCHEMA.page;
90
+ export const RESTAURANT_COLLECTIONS = ENGLISH_SCHEMA.collections;
75
91
  const BLOCK_VERSION = '1.0.0';
76
92
  // ---------------------------------------------------------------------------
77
93
  // The house: address, telephone, mailboxes
@@ -95,7 +111,7 @@ export function restaurantEmail(siteName, mailbox = 'table') {
95
111
  }
96
112
  /**
97
113
  * A paragraph where `[words](href)` becomes a link. Nothing else is
98
- * interpreted: the copy stays plain text a person can read in this file.
114
+ * interpreted: the copy stays plain text a person can read in its file.
99
115
  */
100
116
  function textBlock(key, style, text) {
101
117
  const parts = text.split(/(\[[^\]]+\]\([^)]+\))/).filter((part) => part !== '');
@@ -153,245 +169,39 @@ function table(key, title, rows) {
153
169
  })),
154
170
  };
155
171
  }
172
+ function pricingTable(key, title, tiers) {
173
+ return {
174
+ _key: key,
175
+ _type: 'pricingTable',
176
+ _version: BLOCK_VERSION,
177
+ title,
178
+ tiers: tiers.map((tier, index) => ({
179
+ _key: `${key}-${index}`,
180
+ name: tier.name,
181
+ price: tier.price,
182
+ interval: tier.interval,
183
+ features: [...tier.features],
184
+ ...(tier.highlighted === true ? { highlighted: true } : {}),
185
+ })),
186
+ };
187
+ }
188
+ function withCategory(copy) {
189
+ return copy.dishes.map((dish) => ({ ...dish, category: copy.sections[dish.section] }));
190
+ }
156
191
  /**
157
192
  * Nineteen dishes and wines for a week in early autumn, in the order the menu
158
- * prints them. Eight of them have a photograph (`RESTAURANT_DISH_PHOTOS`),
159
- * and every photograph shows the dish it is filed under.
193
+ * prints them. Ten of them have a photograph (`RESTAURANT_DISH_PHOTOS`), and
194
+ * every photograph shows the dish it is filed under.
160
195
  */
161
- export const RESTAURANT_DEMO_DISHES = [
162
- {
163
- name: 'Beetroot, goat’s curd and walnuts',
164
- slug: 'beetroot-goats-curd',
165
- category: 'Starters',
166
- price: 12,
167
- vegetarian: true,
168
- description: 'Crapaudine beetroot baked in salt, fresh goat’s curd, Grenoble walnuts toasted in brown butter and a few leaves from the farm.',
169
- sourcing: 'Beetroot from the Morel farm in Vourles, curd from a goat dairy near Condrieu',
170
- pairing: 'Mâcon-Villages, 2023',
171
- allergens: 'Milk, walnuts',
172
- note: 'Crapaudine is an old variety with a rough, dark skin like bark. Baked slowly in a crust of salt it keeps all of its sweetness, and the walnuts are toasted to order so they are still warm when the plate leaves the pass.',
173
- },
174
- {
175
- name: 'Autumn vegetable broth, confit garlic',
176
- slug: 'vegetable-broth',
177
- category: 'Starters',
178
- price: 10,
179
- vegetarian: true,
180
- description: 'A clear tomato and vegetable broth with squash, turnip and white beans, a clove of garlic confit in olive oil and flat parsley.',
181
- sourcing: 'Vegetables from the Morel farm in Vourles',
182
- allergens: 'Celery',
183
- note: 'The broth is made every morning from the trimmings of the vegetables delivered that day, so it changes a little from one week to the next. At lunch it comes with a slice of toasted sourdough.',
184
- },
185
- {
186
- name: 'Grilled octopus, saffron and orange',
187
- slug: 'grilled-octopus',
188
- category: 'Starters',
189
- price: 17,
190
- vegetarian: false,
191
- description: 'Octopus braised for two hours, then grilled over vine cuttings, with an emulsion of saffron, orange and olive oil and a sprig of wild marjoram.',
192
- sourcing: 'Day boats at Saint-Gilles-Croix-de-Vie, delivered overnight',
193
- pairing: 'Saint-Joseph blanc, 2022',
194
- allergens: 'Molluscs, egg',
195
- note: 'We buy whole octopus of around two kilos and cook it the day it arrives. The vine cuttings come from a grower in Beaujolais who prunes in February and keeps a trailer of them for us.',
196
- },
197
- {
198
- name: 'Pâté en croûte, pickled girolles',
199
- slug: 'pate-en-croute',
200
- category: 'Starters',
201
- price: 15,
202
- vegetarian: false,
203
- description: 'Pork, duck and pistachio in a hot-water crust, made on Monday for the week, with girolles pickled in cider vinegar.',
204
- sourcing: 'Pork from the Monts du Lyonnais, duck from the Dombes',
205
- pairing: 'Morgon Côte du Py, 2022',
206
- allergens: 'Gluten, egg, pistachio, sulphites',
207
- note: 'One pâté makes eighteen slices and we make two each Monday. When they are gone, the dish comes off the menu until the following week.',
208
- },
209
- {
210
- name: 'Leeks vinaigrette, soft egg and hazelnuts',
211
- slug: 'leeks-vinaigrette',
212
- category: 'Starters',
213
- price: 11,
214
- vegetarian: true,
215
- description: 'Young leeks poached until tender, a sharp mustard vinaigrette, a soft-boiled egg from the farm and toasted hazelnuts.',
216
- sourcing: 'Leeks and eggs from the Morel farm in Vourles',
217
- allergens: 'Egg, mustard, hazelnuts',
218
- note: 'A bistro dish we have served since the first week. The leeks are pulled when they are no thicker than a thumb, and dressed while they are still warm so they take the vinaigrette.',
219
- },
220
- {
221
- name: 'Hake, brown butter and green peppercorns',
222
- slug: 'hake-green-peppercorns',
223
- category: 'Mains',
224
- price: 28,
225
- vegetarian: false,
226
- description: 'A thick fillet of line-caught hake roasted on its skin, brown butter with fresh green peppercorns and roasted Delicata squash.',
227
- sourcing: 'Day boats at Saint-Gilles-Croix-de-Vie',
228
- pairing: 'Saint-Joseph blanc, 2022',
229
- allergens: 'Fish, milk',
230
- note: 'Fresh green peppercorns arrive on the stem, packed in brine, and taste of pepper without the heat. They go into the butter at the last moment, with a squeeze of lemon.',
231
- },
232
- {
233
- name: 'Duck leg, red cabbage and juniper',
234
- slug: 'duck-leg-red-cabbage',
235
- category: 'Mains',
236
- price: 26,
237
- vegetarian: false,
238
- description: 'Duck leg cured overnight and cooked slowly in its own fat, crisped to order, with red cabbage braised with apple and a juniper jus.',
239
- sourcing: 'Ducks raised in the Dombes, forty minutes north of Lyon',
240
- pairing: 'Crozes-Hermitage, 2021',
241
- allergens: 'Sulphites',
242
- note: 'The legs are cured with salt, thyme and crushed juniper for a night before they are cooked. The cabbage spends three hours in the oven with apple, red wine vinegar and a little of the duck fat.',
243
- },
244
- {
245
- name: 'Risotto of ceps and girolles, aged Comté',
246
- slug: 'ceps-risotto',
247
- category: 'Mains',
248
- price: 24,
249
- vegetarian: true,
250
- description: 'Carnaroli rice cooked in a stock of roasted mushroom trimmings, ceps and girolles, Comté aged for twenty-four months and a leaf of lemon balm.',
251
- sourcing: 'Mushrooms picked in the Monts du Forez, Comté from an affineur on the Croix-Rousse market',
252
- pairing: 'Mâcon-Villages, 2023',
253
- allergens: 'Milk, celery',
254
- note: 'The mushrooms come from a picker who drives down from the Forez on Tuesdays and Fridays. When the weather has been dry there are none, and the risotto is made with squash instead.',
255
- },
256
- {
257
- name: 'Pork chop, cider and apples',
258
- slug: 'pork-chop-cider',
259
- category: 'Mains',
260
- price: 29,
261
- vegetarian: false,
262
- description: 'A thick chop from the Monts du Lyonnais roasted on the bone, apples cooked in cider and a spoonful of the pan juices.',
263
- sourcing: 'Pork from a farm in the Monts du Lyonnais',
264
- pairing: 'Morgon Côte du Py, 2022',
265
- allergens: 'Milk, sulphites',
266
- note: 'The pigs are a cross of Large White and Duroc, raised outdoors and slaughtered at ten months. The chop rests for as long as it cooked before it is carved.',
267
- },
268
- {
269
- name: 'Pike quenelle, crayfish sauce',
270
- slug: 'pike-quenelle',
271
- category: 'Mains',
272
- price: 25,
273
- vegetarian: false,
274
- description: 'The Lyon classic, made light: pike and choux pastry poached, then baked in a sauce of crayfish shells, cream and a little Cognac.',
275
- sourcing: 'Pike from the lakes of the Dombes, crayfish from Savoie',
276
- pairing: 'Mâcon-Villages, 2023',
277
- allergens: 'Fish, crustaceans, gluten, egg, milk',
278
- note: 'A quenelle rises like a soufflé in the oven and falls a little on the way to the table, which is how you know it was made that evening. Allow twenty minutes.',
279
- },
280
- {
281
- name: 'Saint-Marcellin, walnut bread',
282
- slug: 'saint-marcellin',
283
- category: 'Cheese and desserts',
284
- price: 9,
285
- vegetarian: false,
286
- description: 'A soft cow’s milk cheese from the Isère, ripened until it runs, with walnut bread from the bakery on rue d’Austerlitz.',
287
- sourcing: 'An affineur on the Croix-Rousse market',
288
- allergens: 'Milk, gluten, walnuts',
289
- note: 'Saint-Marcellin is served in its small earthenware dish, at room temperature. Ask for it runny or firm and the affineur’s shelf will usually have both.',
290
- },
291
- {
292
- name: 'Dark chocolate tart, sea salt',
293
- slug: 'chocolate-tart',
294
- category: 'Cheese and desserts',
295
- price: 10,
296
- vegetarian: true,
297
- description: 'A ganache of seventy per cent chocolate set in a sablé crust and finished with flakes of salt from Guérande.',
298
- sourcing: 'Chocolate from a maker in the Drôme',
299
- pairing: 'Muscat de Beaumes-de-Venise, 2020',
300
- allergens: 'Milk, gluten, egg, soya',
301
- note: 'The crust is baked blind in the afternoon and the ganache poured an hour before service, so the tart is set but never cold from the refrigerator.',
302
- },
303
- {
304
- name: 'Pear poached in Beaujolais, vanilla cream',
305
- slug: 'poached-pear',
306
- category: 'Cheese and desserts',
307
- price: 10,
308
- vegetarian: true,
309
- description: 'A Williams pear poached in Beaujolais with cinnamon and orange peel, a spoonful of vanilla cream and the reduced poaching wine.',
310
- sourcing: 'Pears from an orchard in the Monts du Lyonnais',
311
- pairing: 'Muscat de Beaumes-de-Venise, 2020',
312
- allergens: 'Milk, sulphites',
313
- note: 'The pears are poached two days ahead and left in the wine, which turns them the colour of the glass all the way to the core.',
314
- },
315
- {
316
- name: 'Pink praline tart',
317
- slug: 'praline-tart',
318
- category: 'Cheese and desserts',
319
- price: 9,
320
- vegetarian: true,
321
- description: 'The praline tart of Lyon: almonds and hazelnuts coated in red sugar, crushed and melted with cream into a sweet pastry case.',
322
- sourcing: 'Pralines from a confectioner in the Presqu’île',
323
- allergens: 'Gluten, egg, milk, almonds, hazelnuts',
324
- note: 'Made the way it has been made in Lyon since the 1950s, with a little less sugar. A slice is small and very sweet, and goes well with a coffee.',
325
- },
326
- {
327
- name: 'Mâcon-Villages, 2023',
328
- slug: 'macon-villages-2023',
329
- category: 'Wine by the glass',
330
- price: 8,
331
- vegetarian: false,
332
- description: 'Chardonnay from old vines above Lugny, fermented in steel tanks. Bright and a little salty.',
333
- sourcing: 'A family estate in the Mâconnais, farmed organically since 2011',
334
- note: 'We pour this with the risotto, the quenelle and the beetroot. It is the wine we open most often at lunch.',
335
- },
336
- {
337
- name: 'Saint-Joseph blanc, 2022',
338
- slug: 'saint-joseph-blanc-2022',
339
- category: 'Wine by the glass',
340
- price: 12,
341
- vegetarian: false,
342
- description: 'Marsanne and roussanne from granite terraces on the right bank of the Rhône. Round, with apricot and almond.',
343
- sourcing: 'A grower in Tournon-sur-Rhône with four hectares of terraces',
344
- note: 'Rich enough for the hake and the octopus, and served a little warmer than most white wines so the fruit comes through.',
345
- },
346
- {
347
- name: 'Morgon Côte du Py, 2022',
348
- slug: 'morgon-cote-du-py-2022',
349
- category: 'Wine by the glass',
350
- price: 10,
351
- vegetarian: false,
352
- description: 'Gamay from the hill of Py, fermented in whole bunches and bottled without filtering. Dark cherry and a stony finish.',
353
- sourcing: 'A grower in Villié-Morgon we have bought from since we opened',
354
- note: 'Served slightly cool, at about fourteen degrees. It is the wine we drink with the pâté en croûte and the pork.',
355
- },
356
- {
357
- name: 'Crozes-Hermitage, 2021',
358
- slug: 'crozes-hermitage-2021',
359
- category: 'Wine by the glass',
360
- price: 11,
361
- vegetarian: false,
362
- description: 'Syrah from the northern Rhône, aged fifteen months in old barrels. Black olive, pepper and violets.',
363
- sourcing: 'A cooperative of eleven growers in Tain-l’Hermitage',
364
- note: 'A cooler vintage than most, with fine tannins and a long, peppery finish. We decant a bottle at the start of each service, and it drinks best with the duck.',
365
- },
366
- {
367
- name: 'Muscat de Beaumes-de-Venise, 2020',
368
- slug: 'muscat-beaumes-de-venise-2020',
369
- category: 'Wine by the glass',
370
- price: 9,
371
- vegetarian: false,
372
- description: 'A sweet white from the southern Rhône, served cold in a glass of eight centilitres. Orange peel and honey.',
373
- sourcing: 'A domaine at the foot of the Dentelles de Montmirail',
374
- note: 'The one sweet wine we pour by the glass, kept for the poached pear and the chocolate tart. A half bottle is on the list for a table that wants more than a glass.',
375
- },
376
- ];
196
+ export const RESTAURANT_DEMO_DISHES = withCategory(RESTAURANT_COPY_EN);
197
+ /** The photographed dishes of a copy, by slug, with what each photograph shows. */
198
+ export function restaurantDishPhotos(copy) {
199
+ return Object.fromEntries(copy.dishes.flatMap((dish) => dish.photoAlt === undefined ? [] : [[dish.slug, dish.photoAlt]]));
200
+ }
377
201
  /** The dishes photographed for the site, by slug. Every other dish has no photograph, on purpose. */
378
- export const RESTAURANT_DISH_PHOTOS = {
379
- 'beetroot-goats-curd': 'Cubes of dark beetroot, white goat’s curd and toasted walnuts on a white plate',
380
- 'vegetable-broth': 'A bowl of clear tomato broth with squash, turnip, white beans and flat parsley',
381
- 'grilled-octopus': 'A grilled octopus tentacle on a pool of saffron and orange emulsion, with a sprig of marjoram',
382
- 'hake-green-peppercorns': 'A roasted fillet of hake with fresh green peppercorns on the stem and cubes of squash',
383
- 'duck-leg-red-cabbage': 'A crisp duck leg on braised red cabbage with juniper jus, on an oak table',
384
- 'ceps-risotto': 'A bowl of risotto with sliced ceps, grated Comté and a leaf of lemon balm',
385
- 'chocolate-tart': 'A slice of dark chocolate tart with flakes of salt, a fork resting on the plate',
386
- 'poached-pear': 'A pear poached dark red in Beaujolais, with vanilla cream and the reduced wine',
387
- };
202
+ export const RESTAURANT_DISH_PHOTOS = restaurantDishPhotos(RESTAURANT_COPY_EN);
388
203
  /** The four plates that make the band of photographs on the home page. */
389
- const HOME_PLATES = [
390
- 'beetroot-goats-curd',
391
- 'grilled-octopus',
392
- 'duck-leg-red-cabbage',
393
- 'poached-pear',
394
- ];
204
+ const HOME_PLATES = ['beetroot-carpaccio', 'grilled-octopus', 'duck-confit', 'poached-pear'];
395
205
  function menuList(key, options) {
396
206
  return {
397
207
  _key: key,
@@ -405,58 +215,46 @@ function menuList(key, options) {
405
215
  layout: options.layout ?? 'grid',
406
216
  };
407
217
  }
408
- const MORE_TITLES = {
409
- Starters: 'The other starters',
410
- Mains: 'The other mains',
411
- 'Cheese and desserts': 'Cheese and desserts',
412
- 'Wine by the glass': 'Wine by the glass',
413
- };
414
218
  /** The body of one dish's page: the note from the kitchen, then the rest of its section. */
415
- export function restaurantDishBlocks(dish) {
219
+ export function restaurantDishBlocks(dish, copy = RESTAURANT_COPY_EN) {
416
220
  return [
417
- prose(`dish-${dish.slug}-note`, [dish.note]),
418
- menuList(`dish-${dish.slug}-more`, {
419
- title: MORE_TITLES[dish.category],
420
- category: dish.category,
221
+ prose(`dish-${dish.id}-note`, [dish.note]),
222
+ menuList(`dish-${dish.id}-more`, {
223
+ title: copy.moreTitles[dish.section],
224
+ category: copy.sections[dish.section],
421
225
  layout: 'list',
422
226
  }),
423
227
  ];
424
228
  }
425
- function reservationActions(siteName) {
426
- return [
427
- { label: `Call ${RESTAURANT_PHONE}`, target: { href: PHONE_LINK }, emphasis: 'primary' },
428
- {
429
- label: 'Write to us',
430
- target: {
431
- href: `mailto:${restaurantEmail(siteName)}?subject=${encodeURIComponent('Table request')}`,
432
- },
433
- },
434
- ];
229
+ function pageContext(copy, siteName) {
230
+ return {
231
+ name: siteName,
232
+ email: (mailbox) => restaurantEmail(siteName, mailbox),
233
+ href: (key) => `/${copy.pageSlugs[key]}`,
234
+ phoneHref: PHONE_LINK,
235
+ mapHref: MAP_LINK,
236
+ };
435
237
  }
436
- const HOURS_ROWS = [
437
- { title: 'Lunch', text: 'Thursday and Friday, 12:00 to 13:45' },
438
- { title: 'Dinner', text: 'Tuesday to Saturday, 19:30 to 21:45' },
439
- { title: 'Closed', text: 'Sunday and Monday, three weeks in August and Christmas week' },
440
- ];
441
238
  export function buildRestaurantHomeBlocks(options = {}) {
239
+ const copy = options.copy ?? RESTAURANT_COPY_EN;
442
240
  const media = options.media ?? {};
443
241
  const siteName = nameOf(options.siteName);
444
- const plates = HOME_PLATES.map((slug) => media[`dish-${slug}`]).filter((id) => id !== undefined);
242
+ const context = pageContext(copy, siteName);
243
+ const words = copy.pages(context);
244
+ const plates = HOME_PLATES.map((id) => media[`dish-${id}`]).filter((id) => id !== undefined);
445
245
  return [
446
246
  {
447
247
  _key: 'home-hero',
448
248
  _type: 'hero',
449
249
  _version: BLOCK_VERSION,
450
- eyebrow: 'Restaurant and wine bar, Lyon',
250
+ eyebrow: words.home.heroEyebrow,
451
251
  title: siteName,
452
- subtitle: 'Seasonal cooking in a former silk workshop on the slopes of the Croix-Rousse. Lunch on Thursday and Friday, dinner from Tuesday to Saturday.',
252
+ subtitle: words.home.heroSubtitle,
453
253
  ...(media.hero === undefined ? {} : { media: media.hero }),
454
- actions: [{ label: 'Reserve a table', target: { href: '/reservations' } }],
254
+ actions: [{ label: words.home.heroAction, target: { href: context.href('reservations') } }],
455
255
  },
456
- prose('home-welcome', [
457
- 'We cook what a handful of farms, boats and growers send us each week, and write a new menu every Tuesday. Thirty-eight seats under the high ceilings of an old weaving workshop, and six at the counter for anyone who walks in.',
458
- ], { align: 'center' }),
459
- menuList('home-menu', { title: 'This week’s menu' }),
256
+ prose('home-welcome', [words.home.welcome], { align: 'center' }),
257
+ menuList('home-menu', { title: words.home.menuTitle }),
460
258
  ...(plates.length === 0
461
259
  ? []
462
260
  : [
@@ -468,419 +266,248 @@ export function buildRestaurantHomeBlocks(options = {}) {
468
266
  items: plates.map((id, index) => ({ _key: `home-plates-${index}`, media: id })),
469
267
  },
470
268
  ]),
471
- prose('home-kitchen', [
472
- '## The kitchen',
473
- `${siteName} opened in 2016 in a canut workshop on rue Burdeau, built in 1827 with ceilings four and a half metres high so that Jacquard looms could stand in it. Élise Marchand runs the kitchen with three cooks; Karim Benali looks after the room and the wine.`,
474
- 'Almost everything we cook comes from within a hundred kilometres of the door: vegetables from a farm in Vourles, ducks and pike from the Dombes, pork from the Monts du Lyonnais, cheese from the market at the top of the hill. The fish is the exception, and it comes overnight from the day boats of the Vendée coast.',
475
- '[Read about the kitchen and the people we buy from](/our-story)',
476
- ]),
269
+ prose('home-kitchen', words.home.kitchen),
477
270
  {
478
271
  _key: 'home-press',
479
272
  _type: 'quote',
480
273
  _version: BLOCK_VERSION,
481
- text: 'Élise Marchand cooks the way the room looks: there is nothing on the plate that is there for show, and nothing missing either.',
482
- author: 'Hélène Vasseur',
483
- role: 'Tablées, autumn guide 2026',
274
+ text: words.home.press.text,
275
+ author: words.home.press.author,
276
+ role: words.home.press.role,
484
277
  },
485
- table('home-hours', 'Hours and address', [
486
- ...HOURS_ROWS,
487
- { title: 'Address', text: RESTAURANT_ADDRESS, href: '/visit' },
488
- { title: 'Telephone', text: RESTAURANT_PHONE, href: PHONE_LINK },
278
+ table('home-hours', words.home.hoursTitle, [
279
+ ...words.hours,
280
+ { title: words.labels.address, text: copy.address, href: context.href('visit') },
281
+ { title: words.labels.telephone, text: copy.phoneDisplay, href: PHONE_LINK },
489
282
  ]),
490
283
  {
491
284
  _key: 'home-private',
492
285
  _type: 'cta',
493
286
  _version: BLOCK_VERSION,
494
- title: 'The room upstairs, for fourteen at one table',
495
- text: 'Lunch or dinner for a birthday, a wedding party or a team, with a menu written for the occasion and wines chosen with you.',
496
- actions: [{ label: 'Private dining', target: { href: '/private-dining' } }],
287
+ title: words.home.privateCta.title,
288
+ text: words.home.privateCta.text,
289
+ actions: [
290
+ {
291
+ label: words.home.privateCta.action,
292
+ target: { href: context.href('privateDining') },
293
+ },
294
+ ],
497
295
  },
498
296
  {
499
297
  _key: 'home-book',
500
298
  _type: 'cta',
501
299
  _version: BLOCK_VERSION,
502
300
  variant: { background: 'muted' },
503
- title: 'Book a table',
504
- text: 'Call us from Tuesday to Saturday between 10:00 and 18:00, or write to us and we will reply the same day.',
505
- actions: reservationActions(siteName),
301
+ title: words.home.bookCta.title,
302
+ text: words.home.bookCta.text,
303
+ actions: reservationActions(words, siteName),
304
+ },
305
+ ];
306
+ }
307
+ function reservationActions(words, siteName) {
308
+ return [
309
+ { label: words.actions.call, target: { href: PHONE_LINK }, emphasis: 'primary' },
310
+ {
311
+ label: words.actions.write,
312
+ target: {
313
+ href: `mailto:${restaurantEmail(siteName)}?subject=${encodeURIComponent(words.actions.tableSubject)}`,
314
+ },
506
315
  },
507
316
  ];
508
317
  }
318
+ /** A prose block that lists the photographs, each credited with its author, licence and source. */
319
+ function creditsBlock(key, intro, sourceWord, credits) {
320
+ const paragraphs = intro.map((line, index) => textBlock(`${key}-${index}`, 'normal', line));
321
+ const items = credits.map((credit, index) => {
322
+ const itemKey = `${key}-credit-${index}`;
323
+ const links = [];
324
+ const children = [];
325
+ const add = (text, href) => {
326
+ const spanKey = `${itemKey}-s${children.length}`;
327
+ if (href === undefined) {
328
+ children.push({ _key: spanKey, _type: 'span', text, marks: [] });
329
+ return;
330
+ }
331
+ const markKey = `${itemKey}-l${links.length}`;
332
+ links.push({ _key: markKey, _type: 'link', href });
333
+ children.push({ _key: spanKey, _type: 'span', text, marks: [markKey] });
334
+ };
335
+ add(`${credit.title} · ${credit.author} · `);
336
+ add(credit.licence.replaceAll(' ', '\u00A0'), credit.licenceUrl);
337
+ add(' ·\u00A0');
338
+ add(sourceWord, credit.source);
339
+ return {
340
+ _key: itemKey,
341
+ _type: 'block',
342
+ style: 'normal',
343
+ listItem: 'bullet',
344
+ level: 1,
345
+ children,
346
+ markDefs: links,
347
+ };
348
+ });
349
+ return {
350
+ _key: key,
351
+ _type: 'prose',
352
+ _version: BLOCK_VERSION,
353
+ body: [...paragraphs, ...items],
354
+ };
355
+ }
509
356
  /**
510
- * `home`, `menu`, `our-story`, `reservations`, `private-dining`, `visit` and
511
- * `legal`: every page the menus and the copy link to.
357
+ * `home`, the menu, the story, reservations, private dining, hours and
358
+ * address, the legal notice and the photo credits: every page the menus and
359
+ * the copy link to, in the copy's language.
512
360
  */
513
361
  export function buildRestaurantDemoPages(options = {}) {
362
+ const copy = options.copy ?? RESTAURANT_COPY_EN;
514
363
  const media = options.media ?? {};
515
364
  const siteName = nameOf(options.siteName);
516
- const tableEmail = restaurantEmail(siteName);
365
+ const context = pageContext(copy, siteName);
366
+ const words = copy.pages(context);
367
+ const slugs = copy.pageSlugs;
517
368
  const events = restaurantEmail(siteName, 'events');
518
- const hello = restaurantEmail(siteName, 'hello');
369
+ const figure = (key, name, caption, align) => media[name] === undefined
370
+ ? []
371
+ : [
372
+ {
373
+ _key: key,
374
+ _type: 'mediaFigure',
375
+ _version: BLOCK_VERSION,
376
+ media: media[name],
377
+ ratio: '3:2',
378
+ align,
379
+ caption,
380
+ },
381
+ ];
519
382
  return [
520
- { title: 'Home', slug: 'home', blocks: buildRestaurantHomeBlocks({ siteName, media }) },
521
383
  {
522
- title: 'Menu',
523
- slug: 'menu',
384
+ title: words.titles.home,
385
+ slug: slugs.home,
386
+ blocks: buildRestaurantHomeBlocks({ siteName, media, copy }),
387
+ },
388
+ {
389
+ title: words.titles.menu,
390
+ slug: slugs.menu,
524
391
  blocks: [
525
- prose('menu-intro', [
526
- 'The menu changes every Tuesday, following what the farms and boats send us, and a dish comes off it when we run out. Everything is cooked to order. Prices are in euros and include service; wine by the glass is poured at twelve centilitres unless the list says otherwise.',
527
- ]),
528
- {
529
- _key: 'menu-sets',
530
- _type: 'pricingTable',
531
- _version: BLOCK_VERSION,
532
- title: 'Set menus',
533
- tiers: [
534
- {
535
- _key: 'menu-sets-lunch',
536
- name: 'Lunch',
537
- price: '€29',
538
- interval: 'two courses, Thursday and Friday',
539
- features: [
540
- 'A starter and a main, or a main and a dessert',
541
- 'Three courses for €36',
542
- 'A glass of the white or red of the week for €7',
543
- ],
544
- },
545
- {
546
- _key: 'menu-sets-evening',
547
- name: 'The evening menu',
548
- price: '€62',
549
- interval: 'five courses, for the whole table',
550
- features: [
551
- 'Two starters, a main, cheese and a dessert',
552
- 'Five glasses chosen by Karim for €38',
553
- 'Served until 20:30',
554
- ],
555
- highlighted: true,
556
- },
557
- {
558
- _key: 'menu-sets-carte',
559
- name: 'À la carte',
560
- price: '€10 to €29',
561
- interval: 'a dish, at lunch and dinner',
562
- features: [
563
- 'Every dish on this page',
564
- 'Half portions for children',
565
- 'Bread and butter included',
566
- ],
567
- },
568
- ],
569
- },
570
- menuList('menu-all', { title: 'This week’s menu' }),
392
+ prose('menu-intro', [words.menu.intro]),
393
+ pricingTable('menu-sets', words.menu.setsTitle, words.menu.tiers),
394
+ menuList('menu-all', { title: words.menu.listTitle }),
571
395
  {
572
396
  _key: 'menu-notes',
573
397
  _type: 'accordion',
574
398
  _version: BLOCK_VERSION,
575
- title: 'Allergies and dietary requirements',
576
- items: questions('menu-notes', [
577
- [
578
- 'Allergies',
579
- 'Each dish lists the fourteen regulated allergens it contains. Tell us about an allergy when you book and again at the table, and the kitchen will say plainly what it can and cannot change. Our kitchen handles nuts, gluten and shellfish every day, so we cannot promise that a dish is free of traces.',
580
- ],
581
- [
582
- 'Vegetarian guests',
583
- 'Dishes marked vegetarian contain no meat or fish. Some of our cheeses are made with animal rennet, so ask and we will tell you which. There is always a vegetarian main, and we can cook the evening menu without meat or fish if you tell us when you book.',
584
- ],
585
- [
586
- 'Vegan guests',
587
- 'Write to us before you come and we will plan a meal for you. On a busy evening without notice, the kitchen can make two courses at most.',
588
- ],
589
- ]),
399
+ title: words.menu.notesTitle,
400
+ items: questions('menu-notes', words.menu.notes),
590
401
  },
591
402
  ],
592
403
  },
593
404
  {
594
- title: 'Our story',
595
- slug: 'our-story',
405
+ title: words.titles.story,
406
+ slug: slugs.story,
596
407
  blocks: [
597
- prose('story-intro', [
598
- `${siteName} opened in the autumn of 2016, in a workshop on rue Burdeau where silk was woven until the 1930s. The building went up in 1827 for the canuts, the silk weavers of Lyon, with ceilings high enough for a Jacquard loom and tall windows for the light they needed to see the thread. We kept both, sanded the oak floor and put the kitchen where the looms used to stand.`,
599
- '## The kitchen',
600
- 'Élise Marchand grew up in Villefranche-sur-Saône and cooked for eight years in Paris and Copenhagen before coming home to open the restaurant. She writes the menu on Monday afternoon, once the farms have told her what they will send, and cooks every service with three cooks and a pastry chef who works mornings.',
601
- 'The cooking is plain on purpose. A dish has three or four things on the plate, each of them chosen for the week it is in season, and the sauces are made from the bones and trimmings of what we buy whole.',
602
- '## The room and the wine',
603
- 'Karim Benali runs the room. He trained as a sommelier in Beaune and has built a list of about two hundred wines, almost all from growers in Beaujolais, the Mâconnais and the northern Rhône, most of whom he has visited. Six are poured by the glass and change with the menu.',
604
- ]),
605
- ...(media.hero === undefined
606
- ? []
607
- : [
608
- {
609
- _key: 'story-room',
610
- _type: 'mediaFigure',
611
- _version: BLOCK_VERSION,
612
- media: media.hero,
613
- ratio: '3:2',
614
- align: 'start',
615
- caption: 'The dining room on rue Burdeau, set for dinner under the skylight the weavers put in.',
616
- },
617
- ]),
408
+ prose('story-intro', words.story.intro),
409
+ ...figure('story-room', 'hero', words.story.roomCaption, 'start'),
618
410
  {
619
411
  _key: 'story-figures',
620
412
  _type: 'stats',
621
413
  _version: BLOCK_VERSION,
622
- title: 'The house in numbers',
623
- items: [
624
- {
625
- _key: 'story-figures-seats',
626
- value: '38',
627
- label: 'seats in the room, and six at the counter',
628
- },
629
- {
630
- _key: 'story-figures-room',
631
- value: '14',
632
- label: 'guests at one table in the room upstairs',
633
- },
634
- {
635
- _key: 'story-figures-growers',
636
- value: '11',
637
- label: 'farms, boats and growers we buy from each week',
638
- },
639
- {
640
- _key: 'story-figures-km',
641
- value: '100',
642
- unit: 'km',
643
- label: 'from the door to almost everything we cook',
644
- },
645
- ],
414
+ title: words.story.figuresTitle,
415
+ items: words.story.figures.map((item, index) => ({
416
+ _key: `story-figures-${index}`,
417
+ value: item.value,
418
+ ...(item.unit === undefined ? {} : { unit: item.unit }),
419
+ label: item.label,
420
+ })),
646
421
  },
647
- table('story-suppliers', 'Who we buy from', [
648
- {
649
- title: 'Vegetables and eggs',
650
- text: 'The Morel family farm in Vourles, twenty minutes south-west of Lyon, delivers on Tuesday and Friday mornings.',
651
- },
652
- {
653
- title: 'Duck and pike',
654
- text: 'Two farms and a fishery in the Dombes, the plateau of ponds north of the city.',
655
- },
656
- {
657
- title: 'Pork and pears',
658
- text: 'A farm and an orchard in the Monts du Lyonnais, where the pigs are raised outdoors.',
659
- },
660
- {
661
- title: 'Fish and shellfish',
662
- text: 'Day boats at Saint-Gilles-Croix-de-Vie, through a merchant who sends the catch overnight.',
663
- },
664
- {
665
- title: 'Cheese',
666
- text: 'An affineur on the Croix-Rousse market, two hundred metres up the hill.',
667
- },
668
- {
669
- title: 'Bread',
670
- text: 'A sourdough bakery on rue d’Austerlitz, collected by one of the cooks every morning at seven.',
671
- },
672
- ]),
422
+ table('story-suppliers', words.story.suppliersTitle, words.story.suppliers),
423
+ ...figure('story-market', 'market', words.story.marketCaption, 'start'),
673
424
  ],
674
425
  },
675
426
  {
676
- title: 'Reservations',
677
- slug: 'reservations',
427
+ title: words.titles.reservations,
428
+ slug: slugs.reservations,
678
429
  blocks: [
679
- prose('reservations-intro', [
680
- `We take bookings by telephone and by email, for up to eight weeks ahead. There is no online booking: every table is booked by one of us, so we can ask about allergies, a pram or a birthday while we do it.`,
681
- '## By telephone',
682
- `Call [${RESTAURANT_PHONE}](${PHONE_LINK}) from Tuesday to Saturday between 10:00 and 18:00. During service the phone is answered when someone is free, so the afternoon is the best time to reach us.`,
683
- '## By email',
684
- `Write to [${tableEmail}](mailto:${tableEmail}) with the date, the time, the number of guests and a telephone number. We reply the same day, and a table is only booked once you have our reply.`,
685
- '## Without a booking',
686
- 'The six seats at the counter are kept for guests who walk in at dinner, and the full menu is served there. On Friday and Saturday they are usually taken by 20:00.',
687
- ]),
688
- table('reservations-summary', 'Booking at a glance', [
689
- {
690
- title: 'Tables',
691
- text: 'For two to six guests. For seven to fourteen, see private dining.',
692
- href: '/private-dining',
693
- },
694
- { title: 'Booking ahead', text: 'Up to eight weeks, by telephone or by email' },
695
- { title: 'Deposits', text: 'None for up to six guests. €20 a guest for eight or more.' },
696
- {
697
- title: 'Cancelling',
698
- text: 'Please tell us 24 hours ahead, so we can give the table to someone else',
699
- },
700
- ]),
430
+ prose('reservations-intro', words.reservations.intro),
431
+ table('reservations-summary', words.reservations.summaryTitle, words.reservations.summary),
701
432
  {
702
433
  _key: 'reservations-faq',
703
434
  _type: 'faq',
704
435
  _version: BLOCK_VERSION,
705
- title: 'Before you come',
706
- items: questions('reservations-faq', [
707
- [
708
- 'What happens if we are running late?',
709
- 'We keep a table for fifteen minutes. Call us if you will be later than that and we will do what we can, although on a full evening we may have to shorten the meal.',
710
- ],
711
- [
712
- 'How do I cancel?',
713
- `Call us or reply to the email that confirmed your table, at least 24 hours before. For eight guests or more the deposit is returned in full if you cancel 72 hours ahead.`,
714
- ],
715
- [
716
- 'Do you welcome children?',
717
- 'Yes. We have two high chairs, and the kitchen makes a half portion of most dishes. There is no separate children’s menu.',
718
- ],
719
- [
720
- 'Is the restaurant accessible by wheelchair?',
721
- 'The room is on the ground floor, with a step of six centimetres at the door and a ramp we put down when you arrive. The toilets are downstairs and are not adapted, which we are sorry about.',
722
- ],
723
- [
724
- 'Can I bring a dog?',
725
- 'A small, quiet dog is welcome at the counter and at the tables by the window.',
726
- ],
727
- ]),
436
+ title: words.reservations.faqTitle,
437
+ items: questions('reservations-faq', words.reservations.faq),
728
438
  },
729
439
  {
730
440
  _key: 'reservations-book',
731
441
  _type: 'cta',
732
442
  _version: BLOCK_VERSION,
733
- title: 'Book a table',
734
- text: 'Tuesday to Saturday, 10:00 to 18:00 by telephone. Emails are answered the same day.',
735
- actions: reservationActions(siteName),
443
+ title: words.reservations.cta.title,
444
+ text: words.reservations.cta.text,
445
+ actions: reservationActions(words, siteName),
736
446
  },
737
447
  ],
738
448
  },
739
449
  {
740
- title: 'Private dining',
741
- slug: 'private-dining',
450
+ title: words.titles.privateDining,
451
+ slug: slugs.privateDining,
742
452
  blocks: [
743
- prose('private-intro', [
744
- 'Above the dining room is the old drawing room of the workshop, where the pattern designers worked. It seats fourteen at one oak table, has its own windows over rue Burdeau and a door that closes, and is served from the same kitchen as the restaurant.',
745
- 'We plan every meal upstairs with you. Tell us the date, the number of guests and what the occasion is, and Élise will suggest a menu from the week’s produce a fortnight before; Karim will do the same with the wine.',
746
- ]),
747
- {
748
- _key: 'private-menus',
749
- _type: 'pricingTable',
750
- _version: BLOCK_VERSION,
751
- title: 'Menus for the room upstairs',
752
- tiers: [
753
- {
754
- _key: 'private-menus-lunch',
755
- name: 'Lunch',
756
- price: '€48',
757
- interval: 'a guest, Tuesday to Saturday',
758
- features: [
759
- 'Three courses and coffee',
760
- 'For eight to fourteen guests',
761
- 'The room is yours from 12:00 to 15:30',
762
- ],
763
- },
764
- {
765
- _key: 'private-menus-dinner',
766
- name: 'Dinner',
767
- price: '€72',
768
- interval: 'a guest, Tuesday to Saturday',
769
- features: [
770
- 'Five courses, with cheese',
771
- 'For eight to fourteen guests',
772
- 'The room is yours from 19:30 to midnight',
773
- ],
774
- highlighted: true,
775
- },
776
- {
777
- _key: 'private-menus-wine',
778
- name: 'Wine',
779
- price: '€38',
780
- interval: 'a guest, with dinner',
781
- features: [
782
- 'A glass with each course',
783
- 'Or bottles from the list, chosen with you',
784
- 'Soft drinks and coffee included',
785
- ],
786
- },
787
- ],
788
- },
453
+ prose('private-intro', words.privateDining.intro),
454
+ pricingTable('private-menus', words.privateDining.menusTitle, words.privateDining.tiers),
789
455
  {
790
456
  _key: 'private-note',
791
457
  _type: 'testimonial',
792
458
  _version: BLOCK_VERSION,
793
- quote: answer('private-note-quote', 'We had my father’s seventieth birthday upstairs, twelve of us and a cake from his favourite bakery. Karim found a bottle from the year he was born and nobody told him until the cheese.'),
794
- attribution: { name: 'Camille Roux', role: 'Dinner for twelve, March 2026' },
459
+ quote: answer('private-note-quote', words.privateDining.testimonial.quote),
460
+ attribution: {
461
+ name: words.privateDining.testimonial.name,
462
+ role: words.privateDining.testimonial.role,
463
+ },
795
464
  },
796
465
  {
797
466
  _key: 'private-faq',
798
467
  _type: 'faq',
799
468
  _version: BLOCK_VERSION,
800
- title: 'Planning a meal upstairs',
801
- items: questions('private-faq', [
802
- [
803
- 'Is there a minimum number of guests?',
804
- 'Eight guests for lunch and ten for dinner on Friday and Saturday. Smaller parties are welcome in the dining room.',
805
- ],
806
- [
807
- 'How do we confirm the booking?',
808
- 'With a deposit of €20 a guest, by bank transfer or card, within a week of our proposal. It is taken off the bill on the evening.',
809
- ],
810
- [
811
- 'Can we bring our own cake or decorations?',
812
- 'A cake, yes, with no charge for serving it. Flowers and place cards are welcome; we ask that nothing is fixed to the walls.',
813
- ],
814
- [
815
- 'Is the room upstairs accessible?',
816
- 'It is up a flight of eighteen stairs, and there is no lift. For a guest who cannot manage the stairs, we can set a long table in the dining room at lunch.',
817
- ],
818
- ]),
469
+ title: words.privateDining.faqTitle,
470
+ items: questions('private-faq', words.privateDining.faq),
819
471
  },
820
472
  {
821
473
  _key: 'private-plan',
822
474
  _type: 'cta',
823
475
  _version: BLOCK_VERSION,
824
- title: 'Plan a meal upstairs',
825
- text: 'Write with the date, the number of guests and the occasion. We reply within two working days with a proposal.',
476
+ title: words.privateDining.cta.title,
477
+ text: words.privateDining.cta.text,
826
478
  actions: [
827
479
  {
828
- label: 'Write to us',
480
+ label: words.actions.write,
829
481
  target: {
830
- href: `mailto:${events}?subject=${encodeURIComponent('Private dining enquiry')}`,
482
+ href: `mailto:${events}?subject=${encodeURIComponent(words.actions.eventsSubject)}`,
831
483
  },
832
484
  emphasis: 'primary',
833
485
  },
834
- { label: `Call ${RESTAURANT_PHONE}`, target: { href: PHONE_LINK } },
486
+ { label: words.actions.call, target: { href: PHONE_LINK } },
835
487
  ],
836
488
  },
837
489
  ],
838
490
  },
839
491
  {
840
- title: 'Hours and address',
841
- slug: 'visit',
492
+ title: words.titles.visit,
493
+ slug: slugs.visit,
842
494
  blocks: [
843
- table('visit-hours', 'Opening hours', [
844
- ...HOURS_ROWS,
845
- { title: 'The counter', text: 'At dinner, for guests without a booking' },
846
- { title: 'Telephone', text: RESTAURANT_PHONE, href: PHONE_LINK },
847
- { title: 'Bookings by telephone', text: 'Tuesday to Saturday, 10:00 to 18:00' },
848
- ]),
849
- table('visit-access', 'Finding us', [
850
- { title: 'Address', text: RESTAURANT_ADDRESS, href: MAP_LINK },
851
- { title: 'The door', text: 'Under the stone arch, next to the bookbinder' },
852
- {
853
- title: 'Metro',
854
- text: 'Line C to Croix-Paquet, then four minutes on foot up rue Burdeau',
855
- },
856
- { title: 'Bus', text: 'Lines S6 and 2 to Burdeau, a minute away' },
857
- {
858
- title: 'Car',
859
- text: 'There is no parking on the slopes. The Terreaux car park is a ten-minute walk and closes at 01:00.',
860
- },
861
- {
862
- title: 'Bicycle',
863
- text: 'A Vélo’v station on place Sathonay, and two racks across the street',
864
- },
865
- ]),
866
- prose('visit-note', [
867
- 'The Pentes are steep, and rue Burdeau is a street of stairs in places. From the Terreaux, the gentlest way up is along rue Sainte-Catherine and rue Burdeau, rather than the stairs of the passage Mermet.',
868
- ]),
495
+ table('visit-hours', words.visit.hoursTitle, [...words.hours, ...words.visit.extraHours]),
496
+ table('visit-access', words.visit.accessTitle, words.visit.access),
497
+ ...figure('visit-street', 'street', words.visit.streetCaption, 'wide'),
498
+ prose('visit-note', [words.visit.note]),
869
499
  ],
870
500
  },
871
501
  {
872
- title: 'Legal notice and privacy',
873
- slug: 'legal',
502
+ title: words.titles.legal,
503
+ slug: slugs.legal,
504
+ blocks: [prose('legal-body', words.legal)],
505
+ },
506
+ {
507
+ title: words.titles.credits,
508
+ slug: slugs.credits,
874
509
  blocks: [
875
- prose('legal-body', [
876
- `This site is published by ${siteName} SAS, ${RESTAURANT_ADDRESS}, France, registered with the Lyon trade and companies register under number 821 405 617, VAT FR 34 821405617. Publication director: Karim Benali.`,
877
- '## Your booking details',
878
- `When you book, we keep your name, telephone number, the size of your party and anything you tell us about allergies, for the sole purpose of seating you. We delete booking details three months after your visit, and we never send marketing emails. Write to [${hello}](mailto:${hello}) to see or remove what we hold.`,
879
- '## Cookies',
880
- 'This site sets no advertising or tracking cookies. If you choose a light or dark appearance, that choice is kept in your own browser and nowhere else.',
881
- '## Photographs',
882
- 'The photographs on this site are of our dishes and our dining room, and may not be reused without permission.',
883
- ]),
510
+ creditsBlock('credits-body', words.credits.intro, words.credits.sourceWord, RESTAURANT_PHOTO_CREDITS),
884
511
  ],
885
512
  },
886
513
  ];
@@ -907,42 +534,56 @@ function restaurantPalette() {
907
534
  }
908
535
  /**
909
536
  * Every visual this blueprint seeds is a bundled photograph: the dining room,
910
- * and one photograph for each of the eight dishes that has one, every one
911
- * cropped to the same 4:5. The home page's band of plates and the story
912
- * page's figure reuse these, never a second copy. `spec` is `seedDemoMedia`'s
913
- * fallback if a file is ever missing, never the intended picture.
537
+ * the street, a market stall, and one photograph for each of the ten dishes
538
+ * that has one, every dish cropped to the same 4:5. The home page's band of
539
+ * plates and the story page's figure reuse these, never a second copy. `spec`
540
+ * is `seedDemoMedia`'s fallback if a file is ever missing, never the intended
541
+ * picture.
914
542
  */
915
- export const RESTAURANT_MEDIA_SPECS = [
916
- {
917
- name: 'hero',
918
- spec: heroArt(restaurantPalette(), 'radial', 7),
919
- alt: 'The dining room at night, tables laid with white cloths and glasses under a skylight',
920
- photo: 'restaurant/hero.jpg',
921
- },
922
- ...Object.entries(RESTAURANT_DISH_PHOTOS).map(([slug, alt], index) => ({
923
- name: `dish-${slug}`,
924
- spec: coverArt(restaurantPalette(), 100 + index),
925
- alt,
926
- photo: `restaurant/${slug}.jpg`,
927
- })),
928
- ];
929
- export const RESTAURANT_MENUS = {
930
- header: [
931
- { label: 'Menu', url: '/menu' },
932
- { label: 'Our story', url: '/our-story' },
933
- { label: 'Private dining', url: '/private-dining' },
934
- { label: 'Hours and address', url: '/visit' },
935
- ],
936
- footer: [
937
- { label: 'Menu', url: '/menu' },
938
- { label: 'Reservations', url: '/reservations' },
939
- { label: 'Private dining', url: '/private-dining' },
940
- { label: 'Our story', url: '/our-story' },
941
- { label: 'Hours and address', url: '/visit' },
942
- { label: 'Legal notice', url: '/legal' },
943
- ],
944
- headerAction: { label: 'Reserve', url: '/reservations' },
945
- };
543
+ export function restaurantMediaSpecs(copy) {
544
+ const palette = restaurantPalette();
545
+ const dishes = copy.dishes.flatMap((dish, index) => dish.photoAlt === undefined
546
+ ? []
547
+ : [
548
+ {
549
+ name: `dish-${dish.id}`,
550
+ spec: coverArt(palette, 100 + index),
551
+ alt: dish.photoAlt,
552
+ photo: `restaurant/${dish.id}.jpg`,
553
+ },
554
+ ]);
555
+ return [
556
+ {
557
+ name: 'hero',
558
+ spec: heroArt(palette, 'radial', 7),
559
+ alt: copy.media.hero,
560
+ photo: 'restaurant/hero.jpg',
561
+ },
562
+ ...dishes,
563
+ {
564
+ name: 'street',
565
+ spec: coverArt(palette, 140),
566
+ alt: copy.media.street,
567
+ photo: 'restaurant/street-burdeau.jpg',
568
+ },
569
+ {
570
+ name: 'market',
571
+ spec: coverArt(palette, 141),
572
+ alt: copy.media.market,
573
+ photo: 'restaurant/market-stall.jpg',
574
+ },
575
+ ];
576
+ }
577
+ export const RESTAURANT_MEDIA_SPECS = restaurantMediaSpecs(RESTAURANT_COPY_EN);
578
+ export function restaurantMenus(copy) {
579
+ const url = (key) => `/${copy.pageSlugs[key]}`;
580
+ return {
581
+ header: copy.menus.header.map((item) => ({ label: item.label, url: url(item.page) })),
582
+ footer: copy.menus.footer.map((item) => ({ label: item.label, url: url(item.page) })),
583
+ headerAction: { label: copy.menus.action.label, url: url(copy.menus.action.page) },
584
+ };
585
+ }
586
+ export const RESTAURANT_MENUS = restaurantMenus(RESTAURANT_COPY_EN);
946
587
  const DISH_PAGE = { kind: 'collection', collection: 'menu_item' };
947
588
  const SEARCH_RESULTS = { kind: 'search' };
948
589
  const HOME = { kind: 'home' };
@@ -950,66 +591,71 @@ const pagePath = (path) => ({ kind: 'path', path });
950
591
  function shownOn(mode, ...targets) {
951
592
  return { pages: { mode, targets } };
952
593
  }
953
- export const RESTAURANT_WIDGETS = [
954
- {
955
- area: 'sidebar',
956
- type: 'cta',
957
- title: 'Reservations',
958
- settings: {
959
- heading: 'Book a table',
960
- body: 'Every table is booked by one of us, by telephone or by email, up to eight weeks ahead, so we can ask about allergies or a birthday while we do it.',
961
- label: 'How to book',
962
- href: '/reservations',
594
+ export function restaurantWidgets(copy) {
595
+ const w = copy.widgets;
596
+ const path = (key) => `/${copy.pageSlugs[key]}`;
597
+ const press = copy.pages({
598
+ name: DEFAULT_RESTAURANT_NAME,
599
+ email: () => '',
600
+ href: path,
601
+ phoneHref: PHONE_LINK,
602
+ mapHref: MAP_LINK,
603
+ }).home.press;
604
+ return [
605
+ {
606
+ area: 'sidebar',
607
+ type: 'cta',
608
+ title: w.reservations.title,
609
+ settings: {
610
+ heading: w.reservations.heading,
611
+ body: w.reservations.body,
612
+ label: w.reservations.label,
613
+ href: path('reservations'),
614
+ },
615
+ visibility: shownOn('only', DISH_PAGE, SEARCH_RESULTS),
963
616
  },
964
- visibility: shownOn('only', DISH_PAGE, SEARCH_RESULTS),
965
- },
966
- {
967
- area: 'sidebar',
968
- type: 'quote',
969
- settings: {
970
- text: 'Élise Marchand cooks the way the room looks: there is nothing on the plate that is there for show, and nothing missing either.',
971
- attribution: 'Hélène Vasseur',
972
- role: 'Tablées, autumn guide 2026',
617
+ {
618
+ area: 'sidebar',
619
+ type: 'quote',
620
+ settings: { text: press.text, attribution: press.author, role: press.role },
621
+ visibility: shownOn('only', DISH_PAGE),
973
622
  },
974
- visibility: shownOn('only', DISH_PAGE),
975
- },
976
- {
977
- area: 'footer-1',
978
- type: 'contact',
979
- title: 'Good to know',
980
- settings: {
981
- hours: [
982
- { label: 'Closed', value: 'Sunday and Monday, three weeks in August and Christmas week' },
983
- { label: 'The counter', value: 'Six seats at dinner, kept for guests without a booking' },
984
- { label: 'Bookings', value: 'By telephone, Tuesday to Saturday, 10:00 to 18:00' },
985
- ],
623
+ {
624
+ area: 'footer-1',
625
+ type: 'contact',
626
+ title: w.goodToKnow.title,
627
+ settings: { hours: w.goodToKnow.hours.map((row) => ({ ...row })) },
628
+ visibility: shownOn('except', HOME, pagePath(path('visit')), pagePath(path('reservations'))),
986
629
  },
987
- visibility: shownOn('except', HOME, pagePath('/visit'), pagePath('/reservations')),
988
- },
989
- {
990
- area: 'footer-2',
991
- type: 'cta',
992
- title: 'Private dining',
993
- settings: {
994
- heading: 'The room upstairs, for fourteen',
995
- body: 'Lunch or dinner at one oak table, with a menu written for the occasion and wines chosen with you.',
996
- label: 'Plan a meal upstairs',
997
- href: '/private-dining',
630
+ {
631
+ area: 'footer-2',
632
+ type: 'cta',
633
+ title: w.privateDining.title,
634
+ settings: {
635
+ heading: w.privateDining.heading,
636
+ body: w.privateDining.body,
637
+ label: w.privateDining.label,
638
+ href: path('privateDining'),
639
+ },
640
+ visibility: shownOn('except', HOME, pagePath(path('privateDining'))),
998
641
  },
999
- visibility: shownOn('except', HOME, pagePath('/private-dining')),
1000
- },
1001
- ];
1002
- export const RESTAURANT_SITE_SETTINGS = {
1003
- 'general.tagline': 'Seasonal cooking on the slopes of the Croix-Rousse.',
1004
- 'general.socialLinks': [
1005
- { label: 'Instagram', url: 'https://instagram.com/example' },
1006
- { label: 'Facebook', url: 'https://facebook.com/example' },
1007
- ],
1008
- 'general.footerNote': `8 rue Burdeau\n69001 Lyon\n${RESTAURANT_PHONE}\n\nLunch Thursday and Friday, 12:00 to 13:45.\nDinner Tuesday to Saturday, 19:30 to 21:45.`,
1009
- // A menu is not a discussion: no comment form under a dish. An editor can
1010
- // open comments again per collection or per entry.
1011
- 'discussion.enabled': false,
1012
- };
642
+ ];
643
+ }
644
+ export const RESTAURANT_WIDGETS = restaurantWidgets(RESTAURANT_COPY_EN);
645
+ export function restaurantSiteSettings(copy) {
646
+ return {
647
+ 'general.tagline': copy.settings.tagline,
648
+ 'general.socialLinks': [
649
+ { label: 'Instagram', url: 'https://instagram.com/example' },
650
+ { label: 'Facebook', url: 'https://facebook.com/example' },
651
+ ],
652
+ 'general.footerNote': copy.settings.footerNote,
653
+ // A menu is not a discussion: no comment form under a dish. An editor can
654
+ // open comments again per collection or per entry.
655
+ 'discussion.enabled': false,
656
+ };
657
+ }
658
+ export const RESTAURANT_SITE_SETTINGS = restaurantSiteSettings(RESTAURANT_COPY_EN);
1013
659
  export const RESTAURANT_RECOMMENDED_AGENTS = [
1014
660
  {
1015
661
  name: 'seoAgent',
@@ -1028,49 +674,58 @@ export const RESTAURANT_RECOMMENDED_AGENTS = [
1028
674
  * then the pages. Everything is published, so every list and every link has
1029
675
  * something real behind it.
1030
676
  */
1031
- async function seedRestaurantDemoContent(ctx) {
1032
- const { db, defaultLocale, adminId, media } = ctx;
1033
- const siteName = nameOf(ctx.siteName);
1034
- const dishStore = createContentStore({ db, collection: menuItem, defaultLocale });
1035
- const pageStore = createContentStore({ db, collection: page, defaultLocale });
1036
- for (const dish of RESTAURANT_DEMO_DISHES) {
1037
- const photo = media[`dish-${dish.slug}`];
1038
- await dishStore.create({
1039
- status: 'published',
1040
- createdBy: adminId,
1041
- values: {
1042
- name: dish.name,
1043
- slug: dish.slug,
1044
- description: dish.description,
1045
- price: dish.price,
1046
- currency: 'EUR',
1047
- category: dish.category,
1048
- vegetarian: dish.vegetarian,
1049
- sourcing: dish.sourcing,
1050
- ...(dish.pairing === undefined ? {} : { pairing: dish.pairing }),
1051
- ...(dish.allergens === undefined ? {} : { allergens: dish.allergens }),
1052
- ...(photo === undefined ? {} : { photo }),
1053
- },
1054
- blocks: { blocks: restaurantDishBlocks(dish).map(toBlockZoneEntry) },
1055
- });
1056
- }
1057
- for (const demo of buildRestaurantDemoPages({ siteName, media })) {
1058
- await pageStore.create({
1059
- status: 'published',
1060
- createdBy: adminId,
1061
- values: { title: demo.title, slug: demo.slug },
1062
- blocks: { blocks: demo.blocks.map(toBlockZoneEntry) },
1063
- });
1064
- }
677
+ function seedRestaurantDemoContent(copy) {
678
+ return async (ctx) => {
679
+ const { db, defaultLocale, adminId, media } = ctx;
680
+ const siteName = nameOf(ctx.siteName);
681
+ const model = restaurantSchema(copy);
682
+ const dishStore = createContentStore({ db, collection: model.menuItem, defaultLocale });
683
+ const pageStore = createContentStore({ db, collection: model.page, defaultLocale });
684
+ for (const dish of copy.dishes) {
685
+ const photo = media[`dish-${dish.id}`];
686
+ await dishStore.create({
687
+ status: 'published',
688
+ createdBy: adminId,
689
+ values: {
690
+ name: dish.name,
691
+ slug: dish.slug,
692
+ description: dish.description,
693
+ price: dish.price,
694
+ currency: 'EUR',
695
+ category: copy.sections[dish.section],
696
+ vegetarian: dish.vegetarian,
697
+ sourcing: dish.sourcing,
698
+ ...(dish.pairing === undefined ? {} : { pairing: dish.pairing }),
699
+ ...(dish.allergens === undefined ? {} : { allergens: dish.allergens }),
700
+ ...(photo === undefined ? {} : { photo }),
701
+ },
702
+ blocks: { blocks: restaurantDishBlocks(dish, copy).map(toBlockZoneEntry) },
703
+ });
704
+ }
705
+ for (const demo of buildRestaurantDemoPages({ siteName, media, copy })) {
706
+ await pageStore.create({
707
+ status: 'published',
708
+ createdBy: adminId,
709
+ values: { title: demo.title, slug: demo.slug },
710
+ blocks: { blocks: demo.blocks.map(toBlockZoneEntry) },
711
+ });
712
+ }
713
+ };
1065
714
  }
1066
- export const restaurantContentPack = {
1067
- collections: RESTAURANT_COLLECTIONS,
1068
- recommendedAgents: RESTAURANT_RECOMMENDED_AGENTS,
1069
- seedDemoContent: seedRestaurantDemoContent,
1070
- defaultTheme: '@cogenta/theme-restaurant',
1071
- menus: RESTAURANT_MENUS,
1072
- widgets: RESTAURANT_WIDGETS,
1073
- siteSettings: RESTAURANT_SITE_SETTINGS,
1074
- mediaSpecs: RESTAURANT_MEDIA_SPECS,
1075
- };
715
+ /** The whole blueprint, in one language. */
716
+ export function createRestaurantContentPack(locale) {
717
+ const copy = restaurantCopyFor(locale);
718
+ return {
719
+ collections: restaurantSchema(copy).collections,
720
+ recommendedAgents: RESTAURANT_RECOMMENDED_AGENTS,
721
+ seedDemoContent: seedRestaurantDemoContent(copy),
722
+ defaultTheme: '@cogenta/theme-restaurant',
723
+ menus: restaurantMenus(copy),
724
+ widgets: restaurantWidgets(copy),
725
+ siteSettings: restaurantSiteSettings(copy),
726
+ mediaSpecs: restaurantMediaSpecs(copy),
727
+ };
728
+ }
729
+ /** The English pack, which `BLUEPRINT_CONTENT_PACKS` lists; `contentPackFor` picks by locale. */
730
+ export const restaurantContentPack = createRestaurantContentPack('en');
1076
731
  //# sourceMappingURL=restaurant.js.map