create-cogenta 0.2.0 → 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 (179) hide show
  1. package/dist/blueprint-defaults.d.ts.map +1 -1
  2. package/dist/blueprint-defaults.js +6 -0
  3. package/dist/blueprint-defaults.js.map +1 -1
  4. package/dist/blueprints/assets/photos/association/community-cleanup.jpg +0 -0
  5. package/dist/blueprints/assets/photos/association/fundraising-dinner.jpg +0 -0
  6. package/dist/blueprints/assets/photos/association/garden-planting.jpg +0 -0
  7. package/dist/blueprints/assets/photos/association/harvest-food-drive.jpg +0 -0
  8. package/dist/blueprints/assets/photos/association/hero.jpg +0 -0
  9. package/dist/blueprints/assets/photos/association/volunteer-orientation.jpg +0 -0
  10. package/dist/blueprints/assets/photos/association/winter-coat-collection.jpg +0 -0
  11. package/dist/blueprints/assets/photos/blog/blog-runs-itself.jpg +0 -0
  12. package/dist/blueprints/assets/photos/blog/desk-setup.jpg +0 -0
  13. package/dist/blueprints/assets/photos/blog/editing.jpg +0 -0
  14. package/dist/blueprints/assets/photos/blog/hero.jpg +0 -0
  15. package/dist/blueprints/assets/photos/blog/notebooks.jpg +0 -0
  16. package/dist/blueprints/assets/photos/blog/plain-text-editor.jpg +0 -0
  17. package/dist/blueprints/assets/photos/blog/reading-nonfiction.jpg +0 -0
  18. package/dist/blueprints/assets/photos/blog/small-tool.jpg +0 -0
  19. package/dist/blueprints/assets/photos/blog/writing-schedule.jpg +0 -0
  20. package/dist/blueprints/assets/photos/magazine/business-2.jpg +0 -0
  21. package/dist/blueprints/assets/photos/magazine/business-3.jpg +0 -0
  22. package/dist/blueprints/assets/photos/magazine/business.jpg +0 -0
  23. package/dist/blueprints/assets/photos/magazine/culture-2.jpg +0 -0
  24. package/dist/blueprints/assets/photos/magazine/culture-3.jpg +0 -0
  25. package/dist/blueprints/assets/photos/magazine/culture.jpg +0 -0
  26. package/dist/blueprints/assets/photos/magazine/news-2.jpg +0 -0
  27. package/dist/blueprints/assets/photos/magazine/news-3.jpg +0 -0
  28. package/dist/blueprints/assets/photos/magazine/news.jpg +0 -0
  29. package/dist/blueprints/assets/photos/magazine/opinion-2.jpg +0 -0
  30. package/dist/blueprints/assets/photos/magazine/opinion-3.jpg +0 -0
  31. package/dist/blueprints/assets/photos/magazine/opinion.jpg +0 -0
  32. package/dist/blueprints/assets/photos/portfolio/adatum-publishing.jpg +0 -0
  33. package/dist/blueprints/assets/photos/portfolio/contoso-mobile-app.jpg +0 -0
  34. package/dist/blueprints/assets/photos/portfolio/fabrikam-annual-report.jpg +0 -0
  35. package/dist/blueprints/assets/photos/portfolio/hero.jpg +0 -0
  36. package/dist/blueprints/assets/photos/portfolio/litware-signage.jpg +0 -0
  37. package/dist/blueprints/assets/photos/portfolio/northwind-rebrand.jpg +0 -0
  38. package/dist/blueprints/assets/photos/portfolio/proseware-launch.jpg +0 -0
  39. package/dist/blueprints/assets/photos/portfolio/tailspin-streaming.jpg +0 -0
  40. package/dist/blueprints/assets/photos/portfolio/wingtip-wayfinding.jpg +0 -0
  41. package/dist/blueprints/assets/photos/restaurant/charred-octopus.jpg +0 -0
  42. package/dist/blueprints/assets/photos/restaurant/chocolate-tart.jpg +0 -0
  43. package/dist/blueprints/assets/photos/restaurant/creme-brulee.jpg +0 -0
  44. package/dist/blueprints/assets/photos/restaurant/hero.jpg +0 -0
  45. package/dist/blueprints/assets/photos/restaurant/house-red.jpg +0 -0
  46. package/dist/blueprints/assets/photos/restaurant/house-white.jpg +0 -0
  47. package/dist/blueprints/assets/photos/restaurant/pan-seared-trout.jpg +0 -0
  48. package/dist/blueprints/assets/photos/restaurant/poached-pear.jpg +0 -0
  49. package/dist/blueprints/assets/photos/restaurant/roasted-beet-salad.jpg +0 -0
  50. package/dist/blueprints/assets/photos/restaurant/slow-roast-duck-leg.jpg +0 -0
  51. package/dist/blueprints/assets/photos/restaurant/soup-of-the-day.jpg +0 -0
  52. package/dist/blueprints/assets/photos/restaurant/sparkling-water.jpg +0 -0
  53. package/dist/blueprints/assets/photos/restaurant/wild-mushroom-risotto.jpg +0 -0
  54. package/dist/blueprints/assets/photos/saas/avatar-1.jpg +0 -0
  55. package/dist/blueprints/assets/photos/saas/hero.jpg +0 -0
  56. package/dist/blueprints/assets/photos/store/camp-blanket.jpg +0 -0
  57. package/dist/blueprints/assets/photos/store/canvas-tote.jpg +0 -0
  58. package/dist/blueprints/assets/photos/store/cast-iron-skillet.jpg +0 -0
  59. package/dist/blueprints/assets/photos/store/ceramic-pour-over-set.jpg +0 -0
  60. package/dist/blueprints/assets/photos/store/enamel-mug.jpg +0 -0
  61. package/dist/blueprints/assets/photos/store/everyday-tee.jpg +0 -0
  62. package/dist/blueprints/assets/photos/store/field-jacket.jpg +0 -0
  63. package/dist/blueprints/assets/photos/store/hero.jpg +0 -0
  64. package/dist/blueprints/assets/photos/store/leather-card-holder.jpg +0 -0
  65. package/dist/blueprints/assets/photos/store/linen-table-runner.jpg +0 -0
  66. package/dist/blueprints/assets/photos/store/trail-tote.jpg +0 -0
  67. package/dist/blueprints/assets/photos/store/wool-beanie.jpg +0 -0
  68. package/dist/blueprints/assets/photos/store/wool-overshirt.jpg +0 -0
  69. package/dist/blueprints/assets/photos/vitrine/avatar-1.jpg +0 -0
  70. package/dist/blueprints/assets/photos/vitrine/avatar-2.jpg +0 -0
  71. package/dist/blueprints/assets/photos/vitrine/avatar-3.jpg +0 -0
  72. package/dist/blueprints/assets/photos/vitrine/dashboard.jpg +0 -0
  73. package/dist/blueprints/assets/photos/vitrine/hero.jpg +0 -0
  74. package/dist/blueprints/association.d.ts +93 -15
  75. package/dist/blueprints/association.d.ts.map +1 -1
  76. package/dist/blueprints/association.js +637 -130
  77. package/dist/blueprints/association.js.map +1 -1
  78. package/dist/blueprints/blog.d.ts +81 -94
  79. package/dist/blueprints/blog.d.ts.map +1 -1
  80. package/dist/blueprints/blog.js +513 -177
  81. package/dist/blueprints/blog.js.map +1 -1
  82. package/dist/blueprints/content-pack.d.ts +83 -2
  83. package/dist/blueprints/content-pack.d.ts.map +1 -1
  84. package/dist/blueprints/content-pack.js +54 -1
  85. package/dist/blueprints/content-pack.js.map +1 -1
  86. package/dist/blueprints/content-packs.d.ts +4 -3
  87. package/dist/blueprints/content-packs.d.ts.map +1 -1
  88. package/dist/blueprints/content-packs.js +6 -3
  89. package/dist/blueprints/content-packs.js.map +1 -1
  90. package/dist/blueprints/demo-media.d.ts +39 -0
  91. package/dist/blueprints/demo-media.d.ts.map +1 -0
  92. package/dist/blueprints/demo-media.js +30 -0
  93. package/dist/blueprints/demo-media.js.map +1 -0
  94. package/dist/blueprints/documentation.d.ts +78 -15
  95. package/dist/blueprints/documentation.d.ts.map +1 -1
  96. package/dist/blueprints/documentation.js +585 -104
  97. package/dist/blueprints/documentation.js.map +1 -1
  98. package/dist/blueprints/magazine.d.ts +81 -15
  99. package/dist/blueprints/magazine.d.ts.map +1 -1
  100. package/dist/blueprints/magazine.js +431 -85
  101. package/dist/blueprints/magazine.js.map +1 -1
  102. package/dist/blueprints/menus.d.ts +25 -0
  103. package/dist/blueprints/menus.d.ts.map +1 -0
  104. package/dist/blueprints/menus.js +33 -0
  105. package/dist/blueprints/menus.js.map +1 -0
  106. package/dist/blueprints/photo-assets.d.ts +3 -0
  107. package/dist/blueprints/photo-assets.d.ts.map +1 -0
  108. package/dist/blueprints/photo-assets.js +33 -0
  109. package/dist/blueprints/photo-assets.js.map +1 -0
  110. package/dist/blueprints/portfolio.d.ts +81 -13
  111. package/dist/blueprints/portfolio.d.ts.map +1 -1
  112. package/dist/blueprints/portfolio.js +605 -102
  113. package/dist/blueprints/portfolio.js.map +1 -1
  114. package/dist/blueprints/registry.d.ts +3 -1
  115. package/dist/blueprints/registry.d.ts.map +1 -1
  116. package/dist/blueprints/registry.js +4 -1
  117. package/dist/blueprints/registry.js.map +1 -1
  118. package/dist/blueprints/restaurant.d.ts +65 -20
  119. package/dist/blueprints/restaurant.d.ts.map +1 -1
  120. package/dist/blueprints/restaurant.js +350 -117
  121. package/dist/blueprints/restaurant.js.map +1 -1
  122. package/dist/blueprints/saas.d.ts +81 -14
  123. package/dist/blueprints/saas.d.ts.map +1 -1
  124. package/dist/blueprints/saas.js +469 -132
  125. package/dist/blueprints/saas.js.map +1 -1
  126. package/dist/blueprints/site-settings-seed.d.ts +17 -0
  127. package/dist/blueprints/site-settings-seed.d.ts.map +1 -0
  128. package/dist/blueprints/site-settings-seed.js +45 -0
  129. package/dist/blueprints/site-settings-seed.js.map +1 -0
  130. package/dist/blueprints/starting-skins.d.ts +19 -0
  131. package/dist/blueprints/starting-skins.d.ts.map +1 -0
  132. package/dist/blueprints/starting-skins.js +301 -0
  133. package/dist/blueprints/starting-skins.js.map +1 -0
  134. package/dist/blueprints/store.d.ts +173 -0
  135. package/dist/blueprints/store.d.ts.map +1 -0
  136. package/dist/blueprints/store.js +643 -0
  137. package/dist/blueprints/store.js.map +1 -0
  138. package/dist/blueprints/vitrine.d.ts +94 -16
  139. package/dist/blueprints/vitrine.d.ts.map +1 -1
  140. package/dist/blueprints/vitrine.js +421 -142
  141. package/dist/blueprints/vitrine.js.map +1 -1
  142. package/dist/demo-art/compositions.d.ts +48 -0
  143. package/dist/demo-art/compositions.d.ts.map +1 -0
  144. package/dist/demo-art/compositions.js +690 -0
  145. package/dist/demo-art/compositions.js.map +1 -0
  146. package/dist/demo-art/index.d.ts +8 -0
  147. package/dist/demo-art/index.d.ts.map +1 -0
  148. package/dist/demo-art/index.js +5 -0
  149. package/dist/demo-art/index.js.map +1 -0
  150. package/dist/demo-art/oklch.d.ts +39 -0
  151. package/dist/demo-art/oklch.d.ts.map +1 -0
  152. package/dist/demo-art/oklch.js +79 -0
  153. package/dist/demo-art/oklch.js.map +1 -0
  154. package/dist/demo-art/png.d.ts +24 -0
  155. package/dist/demo-art/png.d.ts.map +1 -0
  156. package/dist/demo-art/png.js +110 -0
  157. package/dist/demo-art/png.js.map +1 -0
  158. package/dist/demo-art/render.d.ts +228 -0
  159. package/dist/demo-art/render.d.ts.map +1 -0
  160. package/dist/demo-art/render.js +478 -0
  161. package/dist/demo-art/render.js.map +1 -0
  162. package/dist/playground-reset.d.ts.map +1 -1
  163. package/dist/playground-reset.js +22 -4
  164. package/dist/playground-reset.js.map +1 -1
  165. package/dist/prompts.d.ts +12 -0
  166. package/dist/prompts.d.ts.map +1 -1
  167. package/dist/prompts.js +32 -0
  168. package/dist/prompts.js.map +1 -1
  169. package/dist/recap.d.ts.map +1 -1
  170. package/dist/recap.js +6 -0
  171. package/dist/recap.js.map +1 -1
  172. package/dist/scaffold.d.ts +22 -2
  173. package/dist/scaffold.d.ts.map +1 -1
  174. package/dist/scaffold.js +185 -23
  175. package/dist/scaffold.js.map +1 -1
  176. package/dist/wizard.d.ts.map +1 -1
  177. package/dist/wizard.js +4 -1
  178. package/dist/wizard.js.map +1 -1
  179. package/package.json +14 -11
@@ -1,10 +1,31 @@
1
+ import { CogentaError } from '@cogenta/core';
1
2
  import { createContentStore, defineCollection, f, validateCollectionSet, } from '@cogenta/schema';
2
- import { definePageCollection, richTextParagraph, toBlockZoneEntry, } from './content-pack.js';
3
+ import { avatarArt, coverArt, heroArt, logoArt } from '../demo-art/compositions.js';
4
+ import { definePageCollection, richTextParagraph, SEO_FIELDS, toBlockZoneEntry, } from './content-pack.js';
5
+ import { STARTING_SKINS } from './starting-skins.js';
3
6
  /**
4
- * The `association` blueprint's content model (L9 task 8, batch B): a
5
- * nonprofit's public site an `event` collection (what a visitor comes
6
- * back to check), and two pages built around the mission and a call to
7
- * act (donate, volunteer, attend).
7
+ * The `association` blueprint's content model (L25 pass, batch warm,
8
+ * human template for a nonprofit or community group): an `event` collection
9
+ * (what a returning visitor comes back to check, dated and located) and a
10
+ * small set of pages built around the mission, the programmes, and a call
11
+ * to act (donate, volunteer, attend).
12
+ *
13
+ * `blocks` on `event` (a page-builder zone, `f.blocks()`) carries the
14
+ * per-event "When / Where" panel — a real `stats` block the theme renders
15
+ * exactly like any other one. This is a deliberate workaround for a real
16
+ * gap, not an oversight: a theme's `renderPage` receives `PageContent`
17
+ * (title, blocks, `PageEntryMeta`), never the entry's raw schema fields, so
18
+ * there is no contract-D-legal way for `@cogenta/theme-association` to read
19
+ * `date`/`location` on a *single* event's own page (unlike the home page's
20
+ * `collectionList`, whose `ContentEntry[]` *does* carry every raw field —
21
+ * see that block's own `date`/`location` reading). Baking a `stats` block
22
+ * with the same date/location the blueprint already computed keeps the
23
+ * panel real, dated correctly, and rendered by ordinary block code — no
24
+ * theme-kit/`cogenta serve` change, which is out of this task's scope.
25
+ * The same `blocks` zone also carries a second, longer `prose` paragraph
26
+ * (`AssociationDemoEvent.whatToExpect`, L26) — without it an event's own
27
+ * page was only its one-line `description`, a photo and the When/Where
28
+ * panel, which read as bare next to the rest of the site.
8
29
  */
9
30
  export const event = defineCollection({
10
31
  name: 'event',
@@ -16,6 +37,9 @@ export const event = defineCollection({
16
37
  date: f.datetime({ required: true }),
17
38
  location: f.text({ max: 200 }),
18
39
  description: f.text({ max: 500, multiline: true }),
40
+ coverImage: f.media({ accept: ['image'] }),
41
+ blocks: f.blocks(),
42
+ ...SEO_FIELDS,
19
43
  },
20
44
  indexes: [['slug'], ['date']],
21
45
  permissions: {
@@ -28,153 +52,617 @@ export const event = defineCollection({
28
52
  export const page = definePageCollection('/:slug');
29
53
  export const ASSOCIATION_COLLECTIONS = [event, page];
30
54
  validateCollectionSet(ASSOCIATION_COLLECTIONS);
55
+ /**
56
+ * Six real-sounding events, deliberately spread from one to six weeks out —
57
+ * `npm create cogenta` always produces a site whose "Upcoming events" list
58
+ * is never empty and never stale, whatever day it is scaffolded on.
59
+ */
31
60
  export const ASSOCIATION_DEMO_EVENTS = [
61
+ {
62
+ title: 'Volunteer orientation evening',
63
+ slug: 'volunteer-orientation-evening',
64
+ daysFromNow: 7,
65
+ hour: 17,
66
+ minute: 0,
67
+ location: 'Community Hall, Room 2',
68
+ description: 'A short, friendly session for anyone new to volunteering with us — what a Thursday shift actually involves, where the van keys live, and who to ask when something breaks. No commitment, just questions, and tea while you ask them.',
69
+ whatToExpect: 'Expect about forty-five minutes: a walk through the four programmes, who runs each one, and which evenings and afternoons have space right now. Bring nothing and commit to nothing — most people leave with one shift pencilled in for the following week, and a couple of names to ask for when they turn up.',
70
+ },
32
71
  {
33
72
  title: 'Community clean-up day',
34
73
  slug: 'community-clean-up-day',
35
- date: '2026-09-12T09:00:00.000Z',
36
- location: 'Riverside Park',
37
- description: 'A morning of volunteering, open to everyone, no experience needed.',
74
+ daysFromNow: 14,
75
+ hour: 10,
76
+ minute: 0,
77
+ location: 'Riverside Park, main entrance',
78
+ description: 'A morning of raking, planting spring bulbs and clearing last winter’s debris from the riverbank trail, working in pairs along the towpath as far as the old mill. Gloves, bags and tools are all provided — just bring boots you don’t mind getting muddy.',
79
+ whatToExpect: 'We split into pairs at the main entrance and work outward along the towpath, meeting back for sandwiches around midday. Council collects what we bag by the Monday after, so nothing sits by the river over a weekend. Wellingtons help more than the weather forecast usually suggests.',
80
+ },
81
+ {
82
+ title: 'Harvest food drive',
83
+ slug: 'harvest-food-drive',
84
+ daysFromNow: 21,
85
+ hour: 9,
86
+ minute: 0,
87
+ location: 'Community Hall car park',
88
+ description: 'Drop off tinned and dried goods at the collection point, or come for an hour to sort what arrives into family-sized crates ready for Thursday’s distribution. Last year this single morning restocked the pantry for six weeks — every tin really does make a difference.',
89
+ whatToExpect: 'A folding table goes up in the car park from nine, with a second team sorting inside the hall — tins by type, dried goods by weight, anything close to its date used first. No sign-up needed for either job; if the table looks busy, the sorting team inside can always use another pair of hands.',
90
+ },
91
+ {
92
+ title: 'Winter coat collection',
93
+ slug: 'winter-coat-collection',
94
+ daysFromNow: 28,
95
+ hour: 11,
96
+ minute: 0,
97
+ location: 'Main Street Fire Station',
98
+ description: 'Clean, wearable coats of any size — children’s especially short this year — go straight to families at the winter shelter before the cold sets in properly. The fire crew lets us use their car park and their kettle, so it doubles as a good chat with the neighbours.',
99
+ whatToExpect: 'Drop coats at the station forecourt any time between eleven and two — someone is always there to take them, no need to stay. Everything is checked for zips and buttons on the spot and sorted by size, so what leaves for the shelter that same afternoon is already ready to wear.',
38
100
  },
39
101
  {
40
102
  title: 'Annual fundraising dinner',
41
103
  slug: 'annual-fundraising-dinner',
42
- date: '2026-10-03T18:30:00.000Z',
43
- location: 'Town Hall',
44
- description: "This year's proceeds go directly to the winter shelter programme.",
104
+ daysFromNow: 35,
105
+ hour: 18,
106
+ minute: 30,
107
+ location: 'Grand Ballroom, Town Hall',
108
+ description: "This year's proceeds go directly to the winter shelter programme, matched pound for pound by two local businesses up to £5,000. Tickets include a three-course dinner, a raffle with prizes donated by shops on Main Street, and a short word from the families the shelter has housed this year.",
109
+ whatToExpect: 'Doors at six thirty for drinks, dinner at seven, and the raffle drawn between courses so nobody has to wait until the end. Smart casual, and the Town Hall car park is free after six for anyone driving in. Tickets are limited to two hundred, so this is the one date worth booking ahead for.',
45
110
  },
46
111
  {
47
- title: 'Volunteer orientation',
48
- slug: 'volunteer-orientation',
49
- date: '2026-08-22T17:00:00.000Z',
50
- location: 'Community Centre, Room 2',
51
- description: 'A short session for anyone new to volunteering with us.',
112
+ title: 'Neighbourhood garden planting day',
113
+ slug: 'neighbourhood-garden-planting-day',
114
+ daysFromNow: 42,
115
+ hour: 9,
116
+ minute: 30,
117
+ location: 'Elm Street Community Garden',
118
+ description: 'Plant the spring beds with us — seedlings, soil and a flask of coffee all provided, no gardening experience needed. Whatever grows in the shared beds is free for anyone to pick once it comes in, which by June is usually more courgettes than the street knows what to do with.',
119
+ whatToExpect: 'Kneeling pads and tools are laid out by nine thirty, with one of the garden’s regular volunteers showing anyone new what goes where. Children are welcome alongside a parent — there is always a bed of easy, fast-growing things kept aside for smaller hands.',
52
120
  },
53
121
  ];
122
+ /** ISO 8601, computed from "now" at scaffold time — never a fixed calendar date that ages into the past. */
123
+ function futureIso(daysFromNow, hour, minute) {
124
+ const date = new Date();
125
+ date.setUTCDate(date.getUTCDate() + daysFromNow);
126
+ date.setUTCHours(hour, minute, 0, 0);
127
+ return date.toISOString();
128
+ }
129
+ /** `Intl.DateTimeFormat`'s own long form, for the per-event "When" stats item baked into `event.blocks`. */
130
+ function formatWhen(iso) {
131
+ return new Intl.DateTimeFormat('en', {
132
+ weekday: 'short',
133
+ day: 'numeric',
134
+ month: 'short',
135
+ hour: 'numeric',
136
+ minute: '2-digit',
137
+ }).format(new Date(iso));
138
+ }
54
139
  const BLOCK_VERSION = '1.0.0';
55
140
  function proseParagraph(key, text) {
56
141
  return {
57
142
  _key: key,
58
143
  _type: 'prose',
59
144
  _version: BLOCK_VERSION,
60
- body: [
61
- {
62
- _key: `${key}-block`,
63
- _type: 'block',
64
- style: 'normal',
65
- children: [{ _key: `${key}-span`, _type: 'span', text, marks: [] }],
66
- markDefs: [],
67
- },
145
+ body: richTextParagraph(`${key}-body`, text),
146
+ };
147
+ }
148
+ /**
149
+ * The "When / Where" panel every event's own page carries — see this
150
+ * module's own top comment for why a `stats` block, baked at seed time from
151
+ * the same date/location this function's caller already computed, is the
152
+ * honest way to give a single event page a dated, located panel without a
153
+ * theme-kit contract change.
154
+ */
155
+ function whenWhereBlock(iso, location) {
156
+ return {
157
+ _key: 'when-where',
158
+ _type: 'stats',
159
+ _version: BLOCK_VERSION,
160
+ items: [
161
+ { _key: 'when', value: formatWhen(iso), label: 'When' },
162
+ { _key: 'where', value: location, label: 'Where' },
68
163
  ],
69
164
  };
70
165
  }
71
166
  /**
72
- * `home` (hero + mission prose + a live list of upcoming events + a cta to
73
- * donate) and `mission` (a longer prose statement plus a static `stats`
74
- * summary of impact the kind of number a nonprofit reports periodically,
75
- * not something to model as its own queryable collection).
167
+ * `home` (hero + impact numbers + programmes + upcoming events + gallery +
168
+ * a volunteer's own words + partner logos + a donate band + FAQ — the
169
+ * eight-to-twelve-block, real-photo home page a template needs), `about`
170
+ * (the mission, told at length), `programmes` (what "What we do" on the
171
+ * home page actually funds, in full), `events` (every upcoming date, not
172
+ * only the home page's own six), `get-involved` (donate/volunteer/become a
173
+ * member, with a real pricing table) and `privacy` (a short, honest note —
174
+ * every footer needs one).
175
+ *
176
+ * A function of `media` (`SeedContext.media`), not a static const: several
177
+ * blocks need ids `seedDemoMedia` only knows at scaffold time.
76
178
  */
77
- export const ASSOCIATION_DEMO_PAGES = [
78
- {
79
- title: 'Home',
80
- slug: 'home',
81
- blocks: [
82
- {
83
- _key: 'demo-home-hero',
84
- _type: 'hero',
85
- _version: BLOCK_VERSION,
86
- eyebrow: 'Association',
87
- title: 'Working together, close to home',
88
- subtitle: 'Scaffolded by create-cogenta from the "association" blueprint, with real demo events already in place.',
89
- },
90
- proseParagraph('demo-home-mission', 'This is a demo nonprofit site, scaffolded by create-cogenta from the "association" blueprint. Its events and this page were seeded by the installer so there is real content to look at from the first run.'),
91
- {
92
- _key: 'demo-home-events',
93
- _type: 'collectionList',
94
- _version: BLOCK_VERSION,
95
- title: 'Upcoming events',
96
- collection: 'event',
97
- sort: { field: 'createdAt', direction: 'asc' },
98
- limit: 10,
99
- layout: 'list',
100
- },
101
- {
102
- _key: 'demo-home-work',
103
- _type: 'featureGrid',
104
- _version: BLOCK_VERSION,
105
- title: 'What we do',
106
- items: [
107
- {
108
- _key: 'demo-work-1',
109
- icon: 'basket',
110
- title: 'Weekly food distribution',
111
- text: 'Thursday evenings, from the hall on rue des Tilleuls. No paperwork, no means test.',
112
- },
113
- {
114
- _key: 'demo-work-2',
115
- icon: 'book',
116
- title: 'Homework club',
117
- text: 'Two afternoons a week for children in years 6 to 9, run entirely by volunteers.',
118
- },
119
- {
120
- _key: 'demo-work-3',
121
- icon: 'hands',
122
- title: 'Neighbour to neighbour',
123
- text: 'Shopping, paperwork and a bit of company for people who cannot easily get out.',
179
+ export function buildAssociationDemoPages(media) {
180
+ const mediaField = (name) => media[name] === undefined ? {} : { media: media[name] };
181
+ // `gallery.items` and `logoStrip.logos` are contract-B `min: 1` lists —
182
+ // emitting either block with an empty array (no media seeded, e.g. this
183
+ // function's own `{}` call in `blueprint-demo-blocks.test.ts`) would be
184
+ // contract-invalid content, not a degraded block. Omit the whole block
185
+ // instead, the same rule `gallery`/`logoStrip`/`mediaFigure` already
186
+ // follow on every other blueprint that seeds media conditionally.
187
+ const galleryItems = [1, 2, 3, 4, 5, 6]
188
+ .map((n) => ({ _key: `gallery-${n}`, media: media[`gallery-${n}`] }))
189
+ .filter((item) => item.media !== undefined);
190
+ const partnerLogos = [1, 2, 3, 4, 5]
191
+ .map((n) => ({ _key: `partner-${n}`, media: media[`partner-${n}`] }))
192
+ .filter((item) => item.media !== undefined);
193
+ return [
194
+ {
195
+ title: 'Home',
196
+ slug: 'home',
197
+ blocks: [
198
+ {
199
+ _key: 'demo-home-hero',
200
+ _type: 'hero',
201
+ _version: BLOCK_VERSION,
202
+ eyebrow: 'Riverside Community Fund',
203
+ title: 'Working together, close to home',
204
+ subtitle: 'Every gift and every hour goes straight back into this neighbourhood.',
205
+ ...mediaField('hero'),
206
+ actions: [
207
+ { label: 'Donate', target: { href: '/get-involved' }, emphasis: 'primary' },
208
+ { label: 'Volunteer', target: { href: '/get-involved' } },
209
+ ],
210
+ },
211
+ {
212
+ _key: 'demo-home-impact',
213
+ _type: 'stats',
214
+ _version: BLOCK_VERSION,
215
+ title: 'Our impact this year',
216
+ items: [
217
+ { _key: 'i1', value: '12,400', label: 'meals served' },
218
+ { _key: 'i2', value: '380', label: 'volunteers' },
219
+ { _key: 'i3', value: '27', label: 'partner schools' },
220
+ { _key: 'i4', value: '€1.2M', label: 'raised' },
221
+ ],
222
+ },
223
+ {
224
+ _key: 'demo-home-work',
225
+ _type: 'featureGrid',
226
+ _version: BLOCK_VERSION,
227
+ title: 'What we do',
228
+ items: [
229
+ {
230
+ _key: 'demo-work-1',
231
+ icon: 'heart',
232
+ title: 'Weekly food distribution',
233
+ text: 'Thursday evenings, from the hall — no paperwork, no means test.',
234
+ link: { href: '/programmes' },
235
+ },
236
+ {
237
+ _key: 'demo-work-2',
238
+ icon: 'book',
239
+ title: 'Homework club',
240
+ text: 'Two afternoons a week for children in years 6 to 9, run entirely by volunteers.',
241
+ link: { href: '/programmes' },
242
+ },
243
+ {
244
+ _key: 'demo-work-3',
245
+ icon: 'leaf',
246
+ title: 'Community garden',
247
+ text: 'Raised beds anyone in the neighbourhood can plant, tend and harvest from.',
248
+ link: { href: '/programmes' },
249
+ },
250
+ ],
251
+ },
252
+ {
253
+ _key: 'demo-home-events',
254
+ _type: 'collectionList',
255
+ _version: BLOCK_VERSION,
256
+ title: 'Upcoming events',
257
+ collection: 'event',
258
+ sort: { field: 'createdAt', direction: 'asc' },
259
+ limit: 6,
260
+ layout: 'grid',
261
+ },
262
+ ...(galleryItems.length > 0
263
+ ? [
264
+ {
265
+ _key: 'demo-home-gallery',
266
+ _type: 'gallery',
267
+ _version: BLOCK_VERSION,
268
+ layout: 'grid',
269
+ items: galleryItems,
270
+ },
271
+ ]
272
+ : []),
273
+ {
274
+ _key: 'demo-home-testimonial',
275
+ _type: 'testimonial',
276
+ _version: BLOCK_VERSION,
277
+ quote: richTextParagraph('demo-testimonial-body', 'I came for one Saturday and stayed for three years. Nobody here ever made it feel like a chore.'),
278
+ attribution: {
279
+ name: 'M. Alaoui',
280
+ role: 'Volunteer since 2023',
281
+ ...(media.avatar === undefined ? {} : { avatar: media.avatar }),
124
282
  },
125
- ],
126
- },
127
- {
128
- _key: 'demo-home-cta',
129
- _type: 'cta',
130
- _version: BLOCK_VERSION,
131
- title: 'Help us do more',
132
- text: 'Every donation and every volunteer hour goes straight back into the community.',
133
- actions: [{ label: 'Donate', target: { href: '/mission' }, emphasis: 'primary' }],
134
- },
135
- ],
283
+ },
284
+ ...(partnerLogos.length > 0
285
+ ? [
286
+ {
287
+ _key: 'demo-home-partners',
288
+ _type: 'logoStrip',
289
+ _version: BLOCK_VERSION,
290
+ caption: 'Our partners',
291
+ logos: partnerLogos,
292
+ },
293
+ ]
294
+ : []),
295
+ {
296
+ _key: 'demo-home-cta',
297
+ _type: 'cta',
298
+ _version: BLOCK_VERSION,
299
+ title: 'Every gift counts',
300
+ text: 'A one-off gift or a monthly one — both keep the hall open.',
301
+ actions: [
302
+ { label: 'Donate now', target: { href: '/get-involved' }, emphasis: 'primary' },
303
+ { label: 'See how to help', target: { href: '/get-involved' } },
304
+ ],
305
+ },
306
+ {
307
+ _key: 'demo-home-faq',
308
+ _type: 'faq',
309
+ _version: BLOCK_VERSION,
310
+ title: 'How to help',
311
+ items: [
312
+ {
313
+ _key: 'faq-1',
314
+ question: 'Do I have to commit to a regular slot to volunteer?',
315
+ answer: richTextParagraph('faq-1-a', 'No. Some people come every week for years, some come twice a year. Both are useful.'),
316
+ },
317
+ {
318
+ _key: 'faq-2',
319
+ question: 'Where does a donation actually go?',
320
+ answer: richTextParagraph('faq-2-a', 'Rent on the hall, the van, and the food we buy to fill the gaps in what is donated — see our accounts on the Get Involved page.'),
321
+ },
322
+ {
323
+ _key: 'faq-3',
324
+ question: 'Can my company help without writing a cheque?',
325
+ answer: richTextParagraph('faq-3-a', 'Often more usefully, yes — storage space, a van on Thursdays, or an hour of an accountant every quarter are all things we have had to buy in the past.'),
326
+ },
327
+ {
328
+ _key: 'faq-4',
329
+ question: 'Is there a minimum age to volunteer?',
330
+ answer: richTextParagraph('faq-4-a', 'Sixteen for most shifts, with a parent or guardian for the garden and food-drive days at any age.'),
331
+ },
332
+ ],
333
+ },
334
+ ],
335
+ },
336
+ {
337
+ title: 'About',
338
+ slug: 'about',
339
+ blocks: [
340
+ proseParagraph('demo-about-1', 'Riverside Community Fund started in 1994 as three neighbours — a retired teacher, a bus driver and a nurse finishing night shifts — sharing a rented van and a spare room over the hardware shop on Elm Street. That winter they drove hot meals to eleven households who had nowhere else to turn. Thirty years later we still answer to the same neighbourhood, one street at a time, from the same stretch of road.'),
341
+ proseParagraph('demo-about-2', 'The hall came in 2001, bought outright with money raised over six years of jumble sales and sponsored walks, so that no landlord could ever end the food distribution with a rent notice. The homework club followed in 2009, started by a parent who noticed the hall sat empty on weekday afternoons while her own children had nowhere quiet to do their reading. The community garden — three raised beds when it began — now runs the length of the car park behind the hall.'),
342
+ // `mediaFigure.media` is contract-B `required: true` — omit the whole
343
+ // block rather than emit one with no image, the same rule
344
+ // `gallery`/`logoStrip` already follow above when media wasn't seeded
345
+ // (e.g. this function's own `{}` call in `blueprint-demo-blocks
346
+ // .test.ts`).
347
+ ...(media['event-volunteer-orientation-evening'] === undefined
348
+ ? []
349
+ : [
350
+ {
351
+ _key: 'demo-about-orientation',
352
+ _type: 'mediaFigure',
353
+ _version: BLOCK_VERSION,
354
+ ...mediaField('event-volunteer-orientation-evening'),
355
+ caption: 'New volunteers meet around this table on their first evening — no forms, just introductions.',
356
+ },
357
+ ]),
358
+ proseParagraph('demo-about-3', 'Today four programmes run entirely on volunteer hours and neighbour donations: weekly food distribution, the homework club, the community garden, and a neighbour-to-neighbour scheme for shopping and paperwork. None of them means-test anyone who turns up — the founders’ own rule, kept without exception since 1994, is that asking someone to prove their need is worse than the cost of occasionally helping someone who didn’t strictly need it.'),
359
+ {
360
+ _key: 'demo-about-since',
361
+ _type: 'statCounter',
362
+ _version: BLOCK_VERSION,
363
+ title: 'Since 1994',
364
+ stats: [
365
+ { _key: 'c1', value: '32', label: 'years serving the community' },
366
+ { _key: 'c2', value: '4', label: 'programmes running today' },
367
+ ],
368
+ },
369
+ proseParagraph('demo-about-4', 'Everything on this page — the schema, the content, the skin — is a normal part of this site and is meant to be edited, renamed or deleted the moment the defaults stop fitting your own organisation’s history.'),
370
+ {
371
+ _key: 'demo-about-testimonial',
372
+ _type: 'quote',
373
+ _version: BLOCK_VERSION,
374
+ text: 'This hall has fed my family through two hard winters. Nobody ever asked us to prove anything.',
375
+ author: 'A neighbour',
376
+ role: 'Weekly visitor',
377
+ },
378
+ ],
379
+ },
380
+ {
381
+ title: 'Programmes',
382
+ slug: 'programmes',
383
+ blocks: [
384
+ proseParagraph('demo-programmes-intro', 'Four programmes, all volunteer-run, all funded by the gifts and hours neighbours give directly — no council grant and no corporate sponsor pays for any of what follows. Each one started because someone here noticed a specific gap, not because a template said a community fund should run four things.'),
385
+ {
386
+ _key: 'demo-programmes-grid',
387
+ _type: 'featureGrid',
388
+ _version: BLOCK_VERSION,
389
+ items: [
390
+ {
391
+ _key: 'p1',
392
+ icon: 'heart',
393
+ title: 'Weekly food distribution',
394
+ text: 'Every Thursday from 6pm, from the hall — no referral, no paperwork, no means test, just turn up with a bag. We buy fresh bread and vegetables to fill whatever tinned donations don’t cover, so a family leaves with a proper week’s worth of meals, not just what happened to be given.',
395
+ },
396
+ {
397
+ _key: 'p2',
398
+ icon: 'book',
399
+ title: 'Homework club',
400
+ text: 'Tuesday and Thursday afternoons, 3:30 to 5:30, for children in years 6 to 9. Run entirely by volunteers — two retired teachers and a rotating group of university students home for the holidays — with quiet space, real supervision, and biscuits, for anyone whose house isn’t quiet enough to concentrate in.',
401
+ },
402
+ {
403
+ _key: 'p3',
404
+ icon: 'leaf',
405
+ title: 'Community garden',
406
+ text: 'Raised beds anyone in the neighbourhood can plant, tend and harvest from, no allotment waiting list required. What grows there in summer — usually more courgettes and runner beans than anyone expects — goes straight into Thursday’s food distribution alongside the tinned goods.',
407
+ },
408
+ {
409
+ _key: 'p4',
410
+ icon: 'users',
411
+ title: 'Neighbour to neighbour',
412
+ text: 'Shopping runs, help with council paperwork, and a bit of company for people who cannot easily get out — mostly older neighbours living alone. Volunteers are matched to the same person each fortnight wherever possible, so it becomes a friendship rather than a rota.',
413
+ },
414
+ ],
415
+ },
416
+ // `mediaFigure.media` is contract-B `required: true` — omit the whole
417
+ // block rather than emit one with no image, the same rule
418
+ // `gallery`/`logoStrip` already follow on the home page above when
419
+ // media wasn't seeded (e.g. this function's own `{}` call in
420
+ // `blueprint-demo-blocks.test.ts`).
421
+ ...(media['event-neighbourhood-garden-planting-day'] === undefined
422
+ ? []
423
+ : [
424
+ {
425
+ _key: 'demo-programmes-garden',
426
+ _type: 'mediaFigure',
427
+ _version: BLOCK_VERSION,
428
+ ...mediaField('event-neighbourhood-garden-planting-day'),
429
+ caption: 'The community garden on a planting weekend — three beds in 2009, twelve today.',
430
+ },
431
+ ]),
432
+ proseParagraph('demo-programmes-outro', 'None of the four is means-tested and none asks a question it doesn’t need the answer to. If you’d like to fund an hour of any of them, or spend one yourself, the Get involved page has both a donation and a sign-up sheet.'),
433
+ ],
434
+ },
435
+ {
436
+ title: 'Events',
437
+ slug: 'events',
438
+ blocks: [
439
+ proseParagraph('demo-events-intro', 'Every upcoming date, in one place — come to one, or come to all of them. Most take an hour or two, need no booking beyond turning up, and end with tea back at the hall for anyone who wants to stay and talk.'),
440
+ {
441
+ _key: 'demo-events-list',
442
+ _type: 'collectionList',
443
+ _version: BLOCK_VERSION,
444
+ collection: 'event',
445
+ sort: { field: 'createdAt', direction: 'asc' },
446
+ limit: 100,
447
+ layout: 'grid',
448
+ },
449
+ ],
450
+ },
451
+ {
452
+ title: 'Get involved',
453
+ slug: 'get-involved',
454
+ blocks: [
455
+ {
456
+ _key: 'demo-involved-cta',
457
+ _type: 'cta',
458
+ _version: BLOCK_VERSION,
459
+ title: 'Every gift counts',
460
+ text: 'A one-off gift keeps the lights on for a week. A monthly one lets us plan — and every euro goes to a named programme, never to overheads we don’t have.',
461
+ actions: [
462
+ {
463
+ label: 'Donate now',
464
+ target: { href: 'https://example.org/donate' },
465
+ emphasis: 'primary',
466
+ },
467
+ // A real, working page rather than an in-page `#membership`
468
+ // fragment: no block in this theme's vocabulary ever renders an
469
+ // `id`, so a same-page anchor here was a dead link on every
470
+ // theme — found on this page's own audit. The volunteer
471
+ // orientation evening is the concrete next step for "I'd rather
472
+ // give time than money", and it is a real routed entry.
473
+ {
474
+ label: 'Volunteer instead',
475
+ target: { href: '/events/volunteer-orientation-evening' },
476
+ },
477
+ ],
478
+ },
479
+ proseParagraph('demo-involved-volunteer', 'No experience needed for any shift — a returning volunteer is always paired with someone new, and every task is named before you arrive, not discovered on the day. Most people start at a monthly orientation evening (see the Events page for the next date), then pick one recurring slot — Thursday food distribution, homework club, or the garden — that fits around their own week.'),
480
+ {
481
+ _key: 'demo-involved-membership',
482
+ _type: 'pricingTable',
483
+ _version: BLOCK_VERSION,
484
+ title: 'Become a member',
485
+ tiers: [
486
+ {
487
+ _key: 'friend',
488
+ name: 'Friend',
489
+ price: '€5',
490
+ interval: '/month',
491
+ features: ['Our quarterly newsletter', 'An invitation to the annual dinner'],
492
+ action: { label: 'Join as a Friend', target: { href: 'https://example.org/join' } },
493
+ },
494
+ {
495
+ _key: 'sustainer',
496
+ name: 'Sustainer',
497
+ price: '€20',
498
+ interval: '/month',
499
+ features: [
500
+ 'Everything in Friend',
501
+ 'A named seat at the AGM',
502
+ 'Priority booking for the summer camp',
503
+ ],
504
+ action: {
505
+ label: 'Become a Sustainer',
506
+ target: { href: 'https://example.org/join' },
507
+ emphasis: 'primary',
508
+ },
509
+ highlighted: true,
510
+ },
511
+ ],
512
+ },
513
+ // `mediaFigure.media` is contract-B `required: true` — omit the
514
+ // whole block rather than emit one with no image, the same rule
515
+ // `gallery`/`logoStrip` already follow on the home page above when
516
+ // media wasn't seeded (e.g. this function's own `{}` call in
517
+ // `blueprint-demo-blocks.test.ts`).
518
+ ...(media['event-harvest-food-drive'] === undefined
519
+ ? []
520
+ : [
521
+ {
522
+ _key: 'demo-involved-sorting',
523
+ _type: 'mediaFigure',
524
+ _version: BLOCK_VERSION,
525
+ ...mediaField('event-harvest-food-drive'),
526
+ caption: 'Sorting a Saturday’s donations into family-sized crates before Thursday’s distribution.',
527
+ },
528
+ ]),
529
+ {
530
+ _key: 'demo-involved-hours',
531
+ _type: 'accordion',
532
+ _version: BLOCK_VERSION,
533
+ title: 'Before you sign up',
534
+ items: [
535
+ {
536
+ _key: 'hours-1',
537
+ question: 'Do I have to commit to a regular slot?',
538
+ answer: richTextParagraph('hours-1-a', 'No — some people come every week for years, some come twice a year. Both are genuinely useful, and nobody keeps score.'),
539
+ },
540
+ {
541
+ _key: 'hours-2',
542
+ question: 'What should I bring?',
543
+ answer: richTextParagraph('hours-2-a', 'Closed shoes and clothes you do not mind getting dirty. Gloves and tools are provided for every outdoor shift, and the hall has a kettle for everyone else.'),
544
+ },
545
+ {
546
+ _key: 'hours-3',
547
+ question: 'Can under-16s help?',
548
+ answer: richTextParagraph('hours-3-a', 'Yes, alongside a parent or guardian, on the garden and food-drive days — those two are the ones we can supervise safely for younger volunteers. The homework club and food distribution are adult shifts.'),
549
+ },
550
+ {
551
+ _key: 'hours-4',
552
+ question: 'Is there parking?',
553
+ answer: richTextParagraph('hours-4-a', 'The hall’s own car park is small — cycling or walking is easier on a Thursday evening. For the clean-up and garden days, Riverside Park and Elm Street both have free parking within a few minutes’ walk.'),
554
+ },
555
+ ],
556
+ },
557
+ ],
558
+ },
559
+ {
560
+ title: 'Privacy',
561
+ slug: 'privacy',
562
+ blocks: [
563
+ proseParagraph('demo-privacy-1', 'We keep only what a donation or a volunteer sign-up needs: a name, a contact detail, and a record for our own accounts. We never sell or share it, and we delete it on request.'),
564
+ proseParagraph('demo-privacy-2', 'Donation records are kept for six years, the minimum our accountant asks for at tax time. Volunteer contact details are kept only while you are an active volunteer, and removed within a month of your last shift unless you ask us to keep them for a specific reason — an upcoming DBS check, for instance.'),
565
+ proseParagraph('demo-privacy-3', 'This page is a real, honest starting point — replace it with your own policy once you know exactly what your site collects.'),
566
+ ],
567
+ },
568
+ ];
569
+ }
570
+ /** Header/footer navigation and the header call-to-action button. */
571
+ export const ASSOCIATION_MENUS = {
572
+ header: [
573
+ { label: 'About', url: '/about' },
574
+ { label: 'Programmes', url: '/programmes' },
575
+ { label: 'Events', url: '/events' },
576
+ { label: 'Get involved', url: '/get-involved' },
577
+ ],
578
+ footer: [
579
+ { label: 'About', url: '/about' },
580
+ { label: 'Events', url: '/events' },
581
+ { label: 'Donate', url: '/get-involved' },
582
+ { label: 'Privacy', url: '/privacy' },
583
+ ],
584
+ headerAction: { label: 'Donate', url: '/get-involved' },
585
+ };
586
+ export const ASSOCIATION_SITE_SETTINGS = {
587
+ 'general.tagline': 'Working together, close to home.',
588
+ 'general.socialLinks': [
589
+ { label: 'Facebook', url: 'https://facebook.com/example' },
590
+ { label: 'Instagram', url: 'https://instagram.com/example' },
591
+ { label: 'LinkedIn', url: 'https://linkedin.com/company/example' },
592
+ ],
593
+ 'general.footerNote': 'Riverside Community Fund is a registered charity, no. 1029384. 220 Elm Street.',
594
+ };
595
+ /**
596
+ * `association`'s own starting skin (`starting-skins.js`) — asserted
597
+ * present with a real check, not a `!`, since `STARTING_SKINS` is keyed by
598
+ * blueprint id and TypeScript cannot see that this particular key is always
599
+ * populated.
600
+ */
601
+ function associationPalette() {
602
+ const skin = STARTING_SKINS.association;
603
+ if (skin === undefined) {
604
+ throw new CogentaError({
605
+ code: 'BLUEPRINT_REGISTRY_CORRUPT',
606
+ message: 'STARTING_SKINS.association is missing.',
607
+ hint: 'The "association" entry must stay declared in starting-skins.ts for this blueprint to render its demo art.',
608
+ });
609
+ }
610
+ return skin.color;
611
+ }
612
+ /**
613
+ * Procedural demo visuals (L25 D1): a warm hero backdrop, six event covers,
614
+ * six gallery photos, one volunteer avatar and five neutral partner marks —
615
+ * all from the same starting-skin palette this blueprint ships.
616
+ *
617
+ * `heroArt` has no literal "warm" variant (`demo-art/compositions.ts` names
618
+ * `mesh`/`geometric`/`diagonal`/`radial` only, and this task's scope forbids
619
+ * editing that module) — `mesh` is the closest of the four to a soft, warm
620
+ * wash, and is used here exactly as the brief's own "if available else
621
+ * mesh" fallback names.
622
+ */
623
+ /** Bundled photography (`assets/photos/association/`), keyed by the same slug `ASSOCIATION_DEMO_EVENTS` uses. */
624
+ const ASSOCIATION_EVENT_PHOTOS = {
625
+ 'volunteer-orientation-evening': 'association/volunteer-orientation.jpg',
626
+ 'community-clean-up-day': 'association/community-cleanup.jpg',
627
+ 'harvest-food-drive': 'association/harvest-food-drive.jpg',
628
+ 'winter-coat-collection': 'association/winter-coat-collection.jpg',
629
+ 'annual-fundraising-dinner': 'association/fundraising-dinner.jpg',
630
+ 'neighbourhood-garden-planting-day': 'association/garden-planting.jpg',
631
+ };
632
+ export const ASSOCIATION_MEDIA_SPECS = [
633
+ {
634
+ name: 'hero',
635
+ spec: heroArt(associationPalette(), 'mesh', 21),
636
+ alt: 'Volunteers working together',
637
+ photo: 'association/hero.jpg',
136
638
  },
639
+ ...ASSOCIATION_DEMO_EVENTS.map((demo, index) => {
640
+ const photo = ASSOCIATION_EVENT_PHOTOS[demo.slug];
641
+ return {
642
+ name: `event-${demo.slug}`,
643
+ spec: coverArt(associationPalette(), index + 1),
644
+ alt: `Cover image for ${demo.title}`,
645
+ ...(photo === undefined ? {} : { photo }),
646
+ };
647
+ }),
648
+ ...[1, 2, 3, 4, 5, 6].map((n) => ({
649
+ name: `gallery-${n}`,
650
+ spec: coverArt(associationPalette(), n + 10),
651
+ alt: `Photo ${n} from a Riverside Community Fund event`,
652
+ })),
137
653
  {
138
- title: 'Our mission',
139
- slug: 'mission',
140
- blocks: [
141
- proseParagraph('demo-mission-prose', 'Everything here the schema, the content, the skin — is a normal part of the site and is meant to be edited, renamed or deleted the moment the defaults stop fitting.'),
142
- {
143
- _key: 'demo-mission-stats',
144
- _type: 'stats',
145
- _version: BLOCK_VERSION,
146
- title: 'Our impact',
147
- items: [
148
- { _key: 'demo-stat-1', value: '12', unit: 'years', label: 'serving the community' },
149
- { _key: 'demo-stat-2', value: '340+', label: 'volunteers this year' },
150
- { _key: 'demo-stat-3', value: '60', label: 'families supported' },
151
- ],
152
- },
153
- {
154
- _key: 'demo-mission-faq',
155
- _type: 'faq',
156
- _version: BLOCK_VERSION,
157
- title: 'Before you get in touch',
158
- items: [
159
- {
160
- _key: 'demo-mission-faq-1',
161
- question: 'Do I have to commit to a regular slot to volunteer?',
162
- answer: richTextParagraph('demo-mission-faq-1-a', 'No. Some people come every week for years, some come twice. Both are useful, and the second kind is the one we are usually short of in August.'),
163
- },
164
- {
165
- _key: 'demo-mission-faq-2',
166
- question: 'Where does a donation actually go?',
167
- answer: richTextParagraph('demo-mission-faq-2-a', 'Rent on the hall, the van, and the food we buy to fill the gaps in what is donated. Nobody here is paid, which is why the accounts are short enough to publish in full.'),
168
- },
169
- {
170
- _key: 'demo-mission-faq-3',
171
- question: 'Can my company help without writing a cheque?',
172
- answer: richTextParagraph('demo-mission-faq-3-a', 'Often more usefully, yes — storage space, a van on Thursdays, or an hour of an accountant every quarter are all things we have had to buy in the past.'),
173
- },
174
- ],
175
- },
176
- ],
654
+ name: 'avatar',
655
+ spec: avatarArt(associationPalette(), 1),
656
+ // Not decorative (`ingestMediaUpload` requires either real alt text or
657
+ // an explicit decorative justification, and `DemoMediaSpec` carries no
658
+ // such flag) — a short, honest description of the abstract mark itself.
659
+ alt: 'Abstract portrait mark standing in for a volunteer’s photo',
177
660
  },
661
+ ...[1, 2, 3, 4, 5].map((n) => ({
662
+ name: `partner-${n}`,
663
+ spec: logoArt(n),
664
+ alt: `Partner organisation ${n}`,
665
+ })),
178
666
  ];
179
667
  export const ASSOCIATION_RECOMMENDED_AGENTS = [
180
668
  {
@@ -185,30 +673,45 @@ export const ASSOCIATION_RECOMMENDED_AGENTS = [
185
673
  {
186
674
  name: 'seoAgent',
187
675
  package: '@cogenta/agents-builtin',
188
- reason: 'Audits the mission and event pages so newcomers can find them from a search engine.',
676
+ reason: 'Audits the events and programmes pages so newcomers can find them from a search engine.',
189
677
  },
190
678
  ];
191
679
  /**
192
680
  * Inserts the `association` blueprint's demo content through the real
193
- * `ContentStore` — never mocked (house rule).
681
+ * `ContentStore` — never mocked (house rule). Each event's `coverImage` and
682
+ * its own "When / Where" `blocks` panel, the home page's hero/gallery/
683
+ * testimonial/partner media, all come from `ctx.media`
684
+ * (`seedDemoMedia`/`ASSOCIATION_MEDIA_SPECS`) — absent (e.g. a blueprint
685
+ * seeded with `seedDemoContent: false`) simply leaves those fields unset,
686
+ * since none is `required`.
194
687
  */
195
- async function seedAssociationDemoContent(db, defaultLocale, adminId) {
688
+ async function seedAssociationDemoContent(ctx) {
689
+ const { db, defaultLocale, adminId, media } = ctx;
196
690
  const eventStore = createContentStore({ db, collection: event, defaultLocale });
197
691
  const pageStore = createContentStore({ db, collection: page, defaultLocale });
198
692
  for (const demo of ASSOCIATION_DEMO_EVENTS) {
693
+ const iso = futureIso(demo.daysFromNow, demo.hour, demo.minute);
694
+ const cover = media[`event-${demo.slug}`];
199
695
  await eventStore.create({
200
696
  status: 'published',
201
697
  createdBy: adminId,
202
698
  values: {
203
699
  title: demo.title,
204
700
  slug: demo.slug,
205
- date: demo.date,
701
+ date: iso,
206
702
  location: demo.location,
207
703
  description: demo.description,
704
+ ...(cover === undefined ? {} : { coverImage: cover }),
705
+ },
706
+ blocks: {
707
+ blocks: [
708
+ whenWhereBlock(iso, demo.location),
709
+ proseParagraph(`${demo.slug}-expect`, demo.whatToExpect),
710
+ ].map(toBlockZoneEntry),
208
711
  },
209
712
  });
210
713
  }
211
- for (const demo of ASSOCIATION_DEMO_PAGES) {
714
+ for (const demo of buildAssociationDemoPages(media)) {
212
715
  await pageStore.create({
213
716
  status: 'published',
214
717
  createdBy: adminId,
@@ -221,5 +724,9 @@ export const associationContentPack = {
221
724
  collections: ASSOCIATION_COLLECTIONS,
222
725
  recommendedAgents: ASSOCIATION_RECOMMENDED_AGENTS,
223
726
  seedDemoContent: seedAssociationDemoContent,
727
+ defaultTheme: '@cogenta/theme-association',
728
+ menus: ASSOCIATION_MENUS,
729
+ siteSettings: ASSOCIATION_SITE_SETTINGS,
730
+ mediaSpecs: ASSOCIATION_MEDIA_SPECS,
224
731
  };
225
732
  //# sourceMappingURL=association.js.map