@cogenta/starters 0.1.5

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 (200) hide show
  1. package/LICENSE +373 -0
  2. package/dist/blueprints/assets/photos/association/food-bank.jpg +0 -0
  3. package/dist/blueprints/assets/photos/association/fundraising-dinner.jpg +0 -0
  4. package/dist/blueprints/assets/photos/association/garden-planting.jpg +0 -0
  5. package/dist/blueprints/assets/photos/association/hero.jpg +0 -0
  6. package/dist/blueprints/assets/photos/association/homework-club.jpg +0 -0
  7. package/dist/blueprints/assets/photos/association/partner-ashworth-college.png +0 -0
  8. package/dist/blueprints/assets/photos/association/partner-ashworth-council.png +0 -0
  9. package/dist/blueprints/assets/photos/association/partner-brindle-and-sons.png +0 -0
  10. package/dist/blueprints/assets/photos/association/partner-kestrel-homes.png +0 -0
  11. package/dist/blueprints/assets/photos/association/partner-linden-trust.png +0 -0
  12. package/dist/blueprints/assets/photos/association/partner-marlow-street-pharmacy.png +0 -0
  13. package/dist/blueprints/assets/photos/association/volunteer-orientation.jpg +0 -0
  14. package/dist/blueprints/assets/photos/association/volunteer-portrait.jpg +0 -0
  15. package/dist/blueprints/assets/photos/association/winter-coat-collection.jpg +0 -0
  16. package/dist/blueprints/assets/photos/blog/desk-setup.jpg +0 -0
  17. package/dist/blueprints/assets/photos/blog/notebook-and-coffee.jpg +0 -0
  18. package/dist/blueprints/assets/photos/blog/notebooks.jpg +0 -0
  19. package/dist/blueprints/assets/photos/blog/platform.jpg +0 -0
  20. package/dist/blueprints/assets/photos/blog/pour-over.jpg +0 -0
  21. package/dist/blueprints/assets/photos/documentation/delivery-flow.png +0 -0
  22. package/dist/blueprints/assets/photos/magazine/archive-room.jpg +0 -0
  23. package/dist/blueprints/assets/photos/magazine/banquet-hall.jpg +0 -0
  24. package/dist/blueprints/assets/photos/magazine/city-hall.jpg +0 -0
  25. package/dist/blueprints/assets/photos/magazine/dining-room.jpg +0 -0
  26. package/dist/blueprints/assets/photos/magazine/downtown-towers.jpg +0 -0
  27. package/dist/blueprints/assets/photos/magazine/gallery-opening.jpg +0 -0
  28. package/dist/blueprints/assets/photos/magazine/garden-volunteers.jpg +0 -0
  29. package/dist/blueprints/assets/photos/magazine/station-platform.jpg +0 -0
  30. package/dist/blueprints/assets/photos/magazine/theater-stage.jpg +0 -0
  31. package/dist/blueprints/assets/photos/portfolio/fenmore-branch.jpg +0 -0
  32. package/dist/blueprints/assets/photos/portfolio/fenmore-cover.jpg +0 -0
  33. package/dist/blueprints/assets/photos/portfolio/fenmore-stationery.jpg +0 -0
  34. package/dist/blueprints/assets/photos/portfolio/gannet-cover.jpg +0 -0
  35. package/dist/blueprints/assets/photos/portfolio/gannet-sardines.jpg +0 -0
  36. package/dist/blueprints/assets/photos/portfolio/gannet-shelf.jpg +0 -0
  37. package/dist/blueprints/assets/photos/portfolio/hollis-crane-cover.jpg +0 -0
  38. package/dist/blueprints/assets/photos/portfolio/hollis-crane-grid.jpg +0 -0
  39. package/dist/blueprints/assets/photos/portfolio/hollis-crane-salt-year.jpg +0 -0
  40. package/dist/blueprints/assets/photos/portfolio/hollis-crane-spines.jpg +0 -0
  41. package/dist/blueprints/assets/photos/portfolio/rookery-hall-brochure.jpg +0 -0
  42. package/dist/blueprints/assets/photos/portfolio/rookery-hall-cover.jpg +0 -0
  43. package/dist/blueprints/assets/photos/portfolio/rookery-hall-poster-carnival.jpg +0 -0
  44. package/dist/blueprints/assets/photos/portfolio/rookery-hall-poster-sibelius.jpg +0 -0
  45. package/dist/blueprints/assets/photos/portfolio/rookery-hall-series.jpg +0 -0
  46. package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-cover.jpg +0 -0
  47. package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-gate.jpg +0 -0
  48. package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-pictograms.jpg +0 -0
  49. package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-totem.jpg +0 -0
  50. package/dist/blueprints/assets/photos/portfolio/tidewater-cover.jpg +0 -0
  51. package/dist/blueprints/assets/photos/portfolio/tidewater-data.jpg +0 -0
  52. package/dist/blueprints/assets/photos/portfolio/tidewater-report-cover.jpg +0 -0
  53. package/dist/blueprints/assets/photos/portfolio/warp-and-weft-cover.jpg +0 -0
  54. package/dist/blueprints/assets/photos/portfolio/warp-and-weft-room.jpg +0 -0
  55. package/dist/blueprints/assets/photos/portfolio/westmoor-rail-components.jpg +0 -0
  56. package/dist/blueprints/assets/photos/portfolio/westmoor-rail-cover.jpg +0 -0
  57. package/dist/blueprints/assets/photos/portfolio/westmoor-rail-departures.jpg +0 -0
  58. package/dist/blueprints/assets/photos/restaurant/beetroot-goats-curd.jpg +0 -0
  59. package/dist/blueprints/assets/photos/restaurant/ceps-risotto.jpg +0 -0
  60. package/dist/blueprints/assets/photos/restaurant/chocolate-tart.jpg +0 -0
  61. package/dist/blueprints/assets/photos/restaurant/duck-leg-red-cabbage.jpg +0 -0
  62. package/dist/blueprints/assets/photos/restaurant/grilled-octopus.jpg +0 -0
  63. package/dist/blueprints/assets/photos/restaurant/hake-green-peppercorns.jpg +0 -0
  64. package/dist/blueprints/assets/photos/restaurant/hero.jpg +0 -0
  65. package/dist/blueprints/assets/photos/restaurant/poached-pear.jpg +0 -0
  66. package/dist/blueprints/assets/photos/restaurant/vegetable-broth.jpg +0 -0
  67. package/dist/blueprints/assets/photos/saas/adrian-tan.jpg +0 -0
  68. package/dist/blueprints/assets/photos/saas/api-webhooks.png +0 -0
  69. package/dist/blueprints/assets/photos/saas/approvals-queue.png +0 -0
  70. package/dist/blueprints/assets/photos/saas/audit-log.png +0 -0
  71. package/dist/blueprints/assets/photos/saas/erp-sync.png +0 -0
  72. package/dist/blueprints/assets/photos/saas/logo-brightwell-clinics.png +0 -0
  73. package/dist/blueprints/assets/photos/saas/logo-castlemere-foods.png +0 -0
  74. package/dist/blueprints/assets/photos/saas/logo-halvorsen-freight.png +0 -0
  75. package/dist/blueprints/assets/photos/saas/logo-northgate-homes.png +0 -0
  76. package/dist/blueprints/assets/photos/saas/logo-orrin-and-vale.png +0 -0
  77. package/dist/blueprints/assets/photos/saas/logo-tessaly-energy.png +0 -0
  78. package/dist/blueprints/assets/photos/saas/policy-editor.png +0 -0
  79. package/dist/blueprints/assets/photos/saas/spend-reporting.png +0 -0
  80. package/dist/blueprints/assets/photos/saas/sso-scim.png +0 -0
  81. package/dist/blueprints/assets/photos/store/brushed-cotton-overshirt.jpg +0 -0
  82. package/dist/blueprints/assets/photos/store/cable-knit-beanie.jpg +0 -0
  83. package/dist/blueprints/assets/photos/store/canvas-shoulder-bag.jpg +0 -0
  84. package/dist/blueprints/assets/photos/store/canvas-tote.jpg +0 -0
  85. package/dist/blueprints/assets/photos/store/cast-iron-skillet.jpg +0 -0
  86. package/dist/blueprints/assets/photos/store/enamel-mug.jpg +0 -0
  87. package/dist/blueprints/assets/photos/store/field-jacket.jpg +0 -0
  88. package/dist/blueprints/assets/photos/store/heavyweight-t-shirt.jpg +0 -0
  89. package/dist/blueprints/assets/photos/store/hero.jpg +0 -0
  90. package/dist/blueprints/assets/photos/store/leather-card-holder.jpg +0 -0
  91. package/dist/blueprints/assets/photos/store/linen-table-runner.jpg +0 -0
  92. package/dist/blueprints/assets/photos/store/porcelain-pour-over-set.jpg +0 -0
  93. package/dist/blueprints/assets/photos/store/striped-wool-blanket.jpg +0 -0
  94. package/dist/blueprints/assets/photos/vitrine/avatar-1.jpg +0 -0
  95. package/dist/blueprints/assets/photos/vitrine/avatar-2.jpg +0 -0
  96. package/dist/blueprints/assets/photos/vitrine/avatar-3.jpg +0 -0
  97. package/dist/blueprints/assets/photos/vitrine/case-council.jpg +0 -0
  98. package/dist/blueprints/assets/photos/vitrine/case-hotels.jpg +0 -0
  99. package/dist/blueprints/assets/photos/vitrine/case-rail.jpg +0 -0
  100. package/dist/blueprints/assets/photos/vitrine/exhibit-working-capital.png +0 -0
  101. package/dist/blueprints/assets/photos/vitrine/hero-review.jpg +0 -0
  102. package/dist/blueprints/assets/photos/vitrine/logo-aldermoor.png +0 -0
  103. package/dist/blueprints/assets/photos/vitrine/logo-brightwater.png +0 -0
  104. package/dist/blueprints/assets/photos/vitrine/logo-castell-vane.png +0 -0
  105. package/dist/blueprints/assets/photos/vitrine/logo-meridian-rail.png +0 -0
  106. package/dist/blueprints/assets/photos/vitrine/logo-nordvik.png +0 -0
  107. package/dist/blueprints/assets/photos/vitrine/logo-oakfield-health.png +0 -0
  108. package/dist/blueprints/association.d.ts +322 -0
  109. package/dist/blueprints/association.d.ts.map +1 -0
  110. package/dist/blueprints/association.js +1440 -0
  111. package/dist/blueprints/association.js.map +1 -0
  112. package/dist/blueprints/blog.d.ts +221 -0
  113. package/dist/blueprints/blog.d.ts.map +1 -0
  114. package/dist/blueprints/blog.js +1132 -0
  115. package/dist/blueprints/blog.js.map +1 -0
  116. package/dist/blueprints/content-pack.d.ts +148 -0
  117. package/dist/blueprints/content-pack.d.ts.map +1 -0
  118. package/dist/blueprints/content-pack.js +111 -0
  119. package/dist/blueprints/content-pack.js.map +1 -0
  120. package/dist/blueprints/content-packs.d.ts +12 -0
  121. package/dist/blueprints/content-packs.d.ts.map +1 -0
  122. package/dist/blueprints/content-packs.js +30 -0
  123. package/dist/blueprints/content-packs.js.map +1 -0
  124. package/dist/blueprints/demo-media.d.ts +39 -0
  125. package/dist/blueprints/demo-media.d.ts.map +1 -0
  126. package/dist/blueprints/demo-media.js +31 -0
  127. package/dist/blueprints/demo-media.js.map +1 -0
  128. package/dist/blueprints/documentation.d.ts +165 -0
  129. package/dist/blueprints/documentation.d.ts.map +1 -0
  130. package/dist/blueprints/documentation.js +1236 -0
  131. package/dist/blueprints/documentation.js.map +1 -0
  132. package/dist/blueprints/magazine.d.ts +207 -0
  133. package/dist/blueprints/magazine.d.ts.map +1 -0
  134. package/dist/blueprints/magazine.js +1076 -0
  135. package/dist/blueprints/magazine.js.map +1 -0
  136. package/dist/blueprints/menus.d.ts +25 -0
  137. package/dist/blueprints/menus.d.ts.map +1 -0
  138. package/dist/blueprints/menus.js +33 -0
  139. package/dist/blueprints/menus.js.map +1 -0
  140. package/dist/blueprints/photo-assets.d.ts +8 -0
  141. package/dist/blueprints/photo-assets.d.ts.map +1 -0
  142. package/dist/blueprints/photo-assets.js +41 -0
  143. package/dist/blueprints/photo-assets.js.map +1 -0
  144. package/dist/blueprints/portfolio.d.ts +207 -0
  145. package/dist/blueprints/portfolio.d.ts.map +1 -0
  146. package/dist/blueprints/portfolio.js +1032 -0
  147. package/dist/blueprints/portfolio.js.map +1 -0
  148. package/dist/blueprints/restaurant.d.ts +191 -0
  149. package/dist/blueprints/restaurant.d.ts.map +1 -0
  150. package/dist/blueprints/restaurant.js +1076 -0
  151. package/dist/blueprints/restaurant.js.map +1 -0
  152. package/dist/blueprints/saas.d.ts +268 -0
  153. package/dist/blueprints/saas.d.ts.map +1 -0
  154. package/dist/blueprints/saas.js +1472 -0
  155. package/dist/blueprints/saas.js.map +1 -0
  156. package/dist/blueprints/site-settings-seed.d.ts +17 -0
  157. package/dist/blueprints/site-settings-seed.d.ts.map +1 -0
  158. package/dist/blueprints/site-settings-seed.js +45 -0
  159. package/dist/blueprints/site-settings-seed.js.map +1 -0
  160. package/dist/blueprints/starting-skins.d.ts +19 -0
  161. package/dist/blueprints/starting-skins.d.ts.map +1 -0
  162. package/dist/blueprints/starting-skins.js +310 -0
  163. package/dist/blueprints/starting-skins.js.map +1 -0
  164. package/dist/blueprints/store.d.ts +290 -0
  165. package/dist/blueprints/store.d.ts.map +1 -0
  166. package/dist/blueprints/store.js +1031 -0
  167. package/dist/blueprints/store.js.map +1 -0
  168. package/dist/blueprints/vitrine.d.ts +266 -0
  169. package/dist/blueprints/vitrine.d.ts.map +1 -0
  170. package/dist/blueprints/vitrine.js +1056 -0
  171. package/dist/blueprints/vitrine.js.map +1 -0
  172. package/dist/blueprints/widgets.d.ts +26 -0
  173. package/dist/blueprints/widgets.d.ts.map +1 -0
  174. package/dist/blueprints/widgets.js +27 -0
  175. package/dist/blueprints/widgets.js.map +1 -0
  176. package/dist/demo-art/compositions.d.ts +48 -0
  177. package/dist/demo-art/compositions.d.ts.map +1 -0
  178. package/dist/demo-art/compositions.js +690 -0
  179. package/dist/demo-art/compositions.js.map +1 -0
  180. package/dist/demo-art/index.d.ts +8 -0
  181. package/dist/demo-art/index.d.ts.map +1 -0
  182. package/dist/demo-art/index.js +5 -0
  183. package/dist/demo-art/index.js.map +1 -0
  184. package/dist/demo-art/oklch.d.ts +39 -0
  185. package/dist/demo-art/oklch.d.ts.map +1 -0
  186. package/dist/demo-art/oklch.js +79 -0
  187. package/dist/demo-art/oklch.js.map +1 -0
  188. package/dist/demo-art/png.d.ts +24 -0
  189. package/dist/demo-art/png.d.ts.map +1 -0
  190. package/dist/demo-art/png.js +110 -0
  191. package/dist/demo-art/png.js.map +1 -0
  192. package/dist/demo-art/render.d.ts +228 -0
  193. package/dist/demo-art/render.d.ts.map +1 -0
  194. package/dist/demo-art/render.js +478 -0
  195. package/dist/demo-art/render.js.map +1 -0
  196. package/dist/index.d.ts +11 -0
  197. package/dist/index.d.ts.map +1 -0
  198. package/dist/index.js +7 -0
  199. package/dist/index.js.map +1 -0
  200. package/package.json +52 -0
@@ -0,0 +1,1056 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ import { createContentStore, createTaxonomyStore, defineCollection, defineTaxonomy, f, validateCollectionSet, validateTaxonomySet, } from '@cogenta/schema';
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';
6
+ /**
7
+ * The `vitrine` blueprint: the website of a management consultancy (L9 task
8
+ * 8; raised to a pro template by L25; rewritten to studio level by L27).
9
+ *
10
+ * Three real, editable collections behind the pages: the firm's practices
11
+ * (`service`), its published case studies (`case_study`, filed by `sector`)
12
+ * and the client testimonials the pages quote. Demo copy names the firm the
13
+ * person scaffolding the site actually named (`SeedContext.siteName`), and
14
+ * falls back to a fictional firm only outside a real scaffold.
15
+ */
16
+ export const DEFAULT_FIRM_NAME = 'Northfield Partners';
17
+ export const sector = defineTaxonomy({
18
+ name: 'sector',
19
+ labels: {
20
+ singular: { en: 'Sector', fr: 'Secteur' },
21
+ plural: { en: 'Sectors', fr: 'Secteurs' },
22
+ },
23
+ hierarchical: false,
24
+ permissions: {
25
+ read: ['public'],
26
+ create: ['editor', 'admin'],
27
+ update: ['editor', 'admin'],
28
+ delete: ['admin'],
29
+ },
30
+ });
31
+ export const service = defineCollection({
32
+ name: 'service',
33
+ labels: { singular: 'Practice', plural: 'Practices' },
34
+ // Routed: `featureGrid` links each practice to its own page, and a
35
+ // `collectionList` of this collection builds a link for every entry.
36
+ routing: { pattern: '/services/:slug' },
37
+ fields: {
38
+ name: f.text({ required: true, max: 120 }),
39
+ slug: f.slug({ from: 'name', unique: true }),
40
+ description: f.text({ max: 400, multiline: true }),
41
+ body: f.richText(),
42
+ icon: f.text({
43
+ max: 64,
44
+ admin: {
45
+ label: 'Icon',
46
+ help: 'One of the symbol names @cogenta/theme-kit recognises (e.g. "chart", "shield", "briefcase"). Themes that set practices as a numbered list may not draw it.',
47
+ },
48
+ }),
49
+ coverImage: f.media({ accept: ['image'] }),
50
+ ...SEO_FIELDS,
51
+ },
52
+ indexes: [['slug']],
53
+ permissions: {
54
+ read: ['public'],
55
+ create: ['editor', 'admin'],
56
+ update: ['editor', 'admin'],
57
+ delete: ['admin'],
58
+ },
59
+ });
60
+ export const caseStudy = defineCollection({
61
+ name: 'case_study',
62
+ labels: { singular: 'Case study', plural: 'Case studies' },
63
+ routing: { pattern: '/case-studies/:slug' },
64
+ fields: {
65
+ title: f.text({ required: true, max: 200 }),
66
+ slug: f.slug({ from: 'title', unique: true }),
67
+ client: f.text({ max: 120 }),
68
+ summary: f.text({ max: 400, multiline: true }),
69
+ body: f.richText(),
70
+ coverImage: f.media({ accept: ['image'] }),
71
+ sector: f.taxonomy({ of: 'sector', many: false }),
72
+ ...SEO_FIELDS,
73
+ },
74
+ indexes: [['slug']],
75
+ permissions: {
76
+ read: ['public'],
77
+ create: ['editor', 'admin'],
78
+ update: ['editor', 'admin'],
79
+ delete: ['admin'],
80
+ },
81
+ });
82
+ export const testimonial = defineCollection({
83
+ name: 'testimonial',
84
+ labels: { singular: 'Testimonial', plural: 'Testimonials' },
85
+ fields: {
86
+ authorName: f.text({ required: true, max: 120 }),
87
+ authorRole: f.text({ max: 120 }),
88
+ quote: f.text({ required: true, max: 500, multiline: true }),
89
+ avatar: f.media({ accept: ['image'] }),
90
+ },
91
+ permissions: {
92
+ read: ['public'],
93
+ create: ['editor', 'admin'],
94
+ update: ['editor', 'admin'],
95
+ delete: ['admin'],
96
+ },
97
+ });
98
+ export const page = definePageCollection('/:slug');
99
+ export const VITRINE_COLLECTIONS = [
100
+ service,
101
+ caseStudy,
102
+ testimonial,
103
+ page,
104
+ ];
105
+ export const VITRINE_TAXONOMIES = [sector];
106
+ validateCollectionSet(VITRINE_COLLECTIONS);
107
+ validateTaxonomySet(VITRINE_TAXONOMIES, VITRINE_COLLECTIONS);
108
+ function span(key, text) {
109
+ return { _key: `${key}-s`, _type: 'span', text, marks: [] };
110
+ }
111
+ /** Builds a contract-A rich-text document from plain parts (never HTML, R3). */
112
+ function richText(key, parts) {
113
+ return parts.flatMap((part, index) => {
114
+ const partKey = `${key}-${index}`;
115
+ if ('p' in part) {
116
+ return [
117
+ {
118
+ _key: partKey,
119
+ _type: 'block',
120
+ style: 'normal',
121
+ children: [span(partKey, part.p)],
122
+ markDefs: [],
123
+ },
124
+ ];
125
+ }
126
+ if ('h2' in part) {
127
+ return [
128
+ {
129
+ _key: partKey,
130
+ _type: 'block',
131
+ style: 'h2',
132
+ children: [span(partKey, part.h2)],
133
+ markDefs: [],
134
+ },
135
+ ];
136
+ }
137
+ return part.bullets.map((text, bulletIndex) => ({
138
+ _key: `${partKey}-${bulletIndex}`,
139
+ _type: 'block',
140
+ style: 'normal',
141
+ listItem: 'bullet',
142
+ level: 1,
143
+ children: [span(`${partKey}-${bulletIndex}`, text)],
144
+ markDefs: [],
145
+ }));
146
+ });
147
+ }
148
+ export const VITRINE_DEMO_SERVICES = [
149
+ {
150
+ name: 'Strategy',
151
+ slug: 'strategy',
152
+ icon: 'globe',
153
+ description: 'Where to compete, what to stop doing and what the plan is worth. We build the three-year plan with the executive team and test every assumption against the figures the board will see.',
154
+ body: (firm) => [
155
+ {
156
+ p: `Strategy work at ${firm} starts from the operating results. Before anyone discusses ambition, we rebuild the last three years of profit by customer, product and channel, so the executive team and the board argue from one set of facts.`,
157
+ },
158
+ { h2: 'Questions clients bring' },
159
+ {
160
+ bullets: [
161
+ 'Which of our markets can we lead within five years, and which should we leave?',
162
+ 'Should we grow by acquisition, and what can we afford to pay?',
163
+ 'What does the business need to look like before a sale in three years?',
164
+ ],
165
+ },
166
+ { h2: 'What the work produces' },
167
+ {
168
+ p: 'A three-year plan with targets by business unit, the investment case behind each major decision, and a quarterly scorecard the board can run without us. Strategy engagements usually take ten to fourteen weeks.',
169
+ },
170
+ ],
171
+ },
172
+ {
173
+ name: 'Operations',
174
+ slug: 'operations',
175
+ icon: 'settings',
176
+ description: 'Cost, service and working capital across plants, supply chains and shared services. Our programmes typically release 2 to 4% of revenue within eighteen months.',
177
+ body: (firm) => [
178
+ {
179
+ p: `${firm} consultants start on the floor: in the depot, the warehouse, the contact centre. The diagnostic takes six weeks and ends with a costed list of changes ranked by cash released and by risk to service.`,
180
+ },
181
+ { h2: 'Where the value usually is' },
182
+ {
183
+ bullets: [
184
+ 'Inventory held against forecasts nobody uses',
185
+ 'Maintenance and rostering plans built for last year’s demand',
186
+ 'Procurement spread across too many suppliers on short contracts',
187
+ ],
188
+ },
189
+ { h2: 'How results are measured' },
190
+ {
191
+ p: 'Every target is agreed with your finance director before implementation starts and tracked in your own management accounts. On most operations programmes part of our fee depends on those numbers.',
192
+ },
193
+ ],
194
+ },
195
+ {
196
+ name: 'Finance and transactions',
197
+ slug: 'finance-transactions',
198
+ icon: 'chart',
199
+ description: 'Commercial due diligence, value-creation plans and post-merger integration for owners, lenders and management teams, from the first model to the hundredth day.',
200
+ body: () => [
201
+ {
202
+ p: 'We work for buyers, sellers and the management teams in between. A diligence report tells an investment committee what it needs to know in forty pages; the value-creation plan that follows is written to be executed by the company, with owners for every line.',
203
+ },
204
+ { h2: 'Typical mandates' },
205
+ {
206
+ bullets: [
207
+ 'Buy-side commercial diligence in four to six weeks',
208
+ 'Vendor preparation twelve months before a sale process',
209
+ 'Integration planning and the first hundred days after completion',
210
+ ],
211
+ },
212
+ { h2: 'Independence' },
213
+ {
214
+ p: 'We do not take success fees on transactions, and we will not advise both sides of the same deal.',
215
+ },
216
+ ],
217
+ },
218
+ {
219
+ name: 'Organisation',
220
+ slug: 'organisation',
221
+ icon: 'users',
222
+ description: 'Operating models, spans and layers, and succession for the top hundred roles. We design structures a company can run without its advisers in the room.',
223
+ body: () => [
224
+ {
225
+ p: 'Most reorganisations fail in the second year, once the new chart meets the old habits. We design the structure together with the decision rights, the committee calendar and the performance measures, and we test it on real decisions before it is announced.',
226
+ },
227
+ { h2: 'What we look at' },
228
+ {
229
+ bullets: [
230
+ 'How many layers sit between the chief executive and the customer',
231
+ 'Which decisions need three signatures, and why',
232
+ 'Who is ready to take the twenty most critical roles in the next two years',
233
+ ],
234
+ },
235
+ ],
236
+ },
237
+ {
238
+ name: 'Technology and data',
239
+ slug: 'technology-data',
240
+ icon: 'layers',
241
+ description: 'Architecture reviews, vendor selection and delivery assurance for ERP, pricing and planning systems, judged against the business case they were bought for.',
242
+ body: () => [
243
+ {
244
+ p: 'We are independent of software vendors and integrators, and we are paid by the client alone. That lets us tell a board whether a stalled programme should be rescued, reduced or stopped.',
245
+ },
246
+ { h2: 'Typical work' },
247
+ {
248
+ bullets: [
249
+ 'Independent reviews of programmes that are late or over budget',
250
+ 'Selection of ERP, pricing and planning software',
251
+ 'Quarterly assurance reports for audit committees',
252
+ ],
253
+ },
254
+ ],
255
+ },
256
+ {
257
+ name: 'Risk and resilience',
258
+ slug: 'risk-resilience',
259
+ icon: 'shield',
260
+ description: 'Operational resilience, regulatory remediation and continuity planning, written to the standard a supervisor or an insurer will actually test.',
261
+ body: () => [
262
+ {
263
+ p: 'Resilience plans are usually written for the audit and discovered to be unworkable on the day they are needed. We run the scenarios with the people who would have to act on them, at night and without the usual systems.',
264
+ },
265
+ { h2: 'Typical work' },
266
+ {
267
+ bullets: [
268
+ 'Important business services and impact tolerances for regulated firms',
269
+ 'Remediation programmes after a supervisory review',
270
+ 'Crisis exercises for executive teams and boards',
271
+ ],
272
+ },
273
+ ],
274
+ },
275
+ ];
276
+ export const VITRINE_DEMO_SECTORS = [
277
+ { slug: 'transport', name: 'Transport' },
278
+ { slug: 'hospitality', name: 'Hospitality' },
279
+ { slug: 'public-sector', name: 'Public sector' },
280
+ ];
281
+ export const VITRINE_DEMO_CASE_STUDIES = [
282
+ {
283
+ title: 'Meridian Rail raises punctuality from 88% to 94% in fourteen months',
284
+ slug: 'meridian-rail-punctuality',
285
+ client: 'Meridian Rail',
286
+ sectorSlug: 'transport',
287
+ summary: 'A commuter operator carrying 190,000 passengers a day rebuilt its crew rosters and depot maintenance plan. Cancellations caused by missing crew fell by two thirds.',
288
+ body: (firm) => [
289
+ { h2: 'The situation' },
290
+ {
291
+ p: 'Meridian Rail runs 1,140 services a day on four lines into the city. Punctuality had fallen for three consecutive years, and the regulator had opened a formal review. Internal analysis blamed ageing trains; the operator’s own data showed that most delays began before a train left the depot.',
292
+ },
293
+ { h2: 'What we did' },
294
+ {
295
+ p: `A team of five from ${firm} spent the first three weeks on night shifts in the two main depots and in the control room. Rosters were being built for a timetable that had changed twice since they were drawn up, and heavy maintenance was scheduled in the hours when spare trains were most needed.`,
296
+ },
297
+ {
298
+ bullets: [
299
+ 'Rebuilt crew rosters around the current timetable, with a reserve pool at each depot',
300
+ 'Moved heavy maintenance to a rolling overnight plan agreed with the unions',
301
+ 'Set up a daily performance meeting using one shared delay log',
302
+ ],
303
+ },
304
+ { h2: 'Results' },
305
+ {
306
+ p: 'Trains arriving within five minutes of schedule rose from 88% to 94% over fourteen months. Cancellations caused by missing crew fell by 67%, and the regulator closed its review without penalty. The operator’s planning team now maintains the rosters itself.',
307
+ },
308
+ ],
309
+ },
310
+ {
311
+ title: 'Castell & Vane Hotels adds £11.2 million of revenue with one pricing desk',
312
+ slug: 'castell-vane-pricing',
313
+ client: 'Castell & Vane Hotels',
314
+ sectorSlug: 'hospitality',
315
+ summary: 'Room rates at fourteen properties had been set by each general manager from a spreadsheet. A central pricing desk and a weekly forecast changed that within a season.',
316
+ body: (firm) => [
317
+ { h2: 'The situation' },
318
+ {
319
+ p: 'Castell & Vane owns fourteen hotels in six cities, most of them historic buildings with restaurants that account for a third of revenue. Each general manager set room rates independently, and weekend prices were often lower than weekday prices in the same city.',
320
+ },
321
+ { h2: 'What we did' },
322
+ {
323
+ p: `${firm} built a demand forecast from four years of bookings, local events and competitor rates, then designed a central pricing desk of three analysts who publish rates every Monday. General managers kept the right to override a price, with a reason recorded against it.`,
324
+ },
325
+ {
326
+ bullets: [
327
+ 'Weekly forecast by property, room type and channel',
328
+ 'Rate rules for group bookings, long stays and restaurant packages',
329
+ 'Training for fourteen general managers and their revenue leads',
330
+ ],
331
+ },
332
+ { h2: 'Results' },
333
+ {
334
+ p: 'Revenue per available room rose by 9% in the first full year, worth £11.2 million, with occupancy unchanged. The pricing desk paid for the engagement within eleven weeks.',
335
+ },
336
+ ],
337
+ },
338
+ {
339
+ title: 'Kellmoor City Council closes a £38 million budget gap and keeps every library open',
340
+ slug: 'kellmoor-council-budget',
341
+ client: 'Kellmoor City Council',
342
+ sectorSlug: 'public-sector',
343
+ summary: 'Facing a statutory deficit, the council merged six back-office functions into one shared service and renegotiated 41 contracts. Front-line spending was protected in full.',
344
+ body: (firm) => [
345
+ { h2: 'The situation' },
346
+ {
347
+ p: 'Kellmoor City Council serves 310,000 residents. Rising care costs had opened a £38 million gap in a £420 million budget, and the council had eighteen months to close it before the government could intervene.',
348
+ },
349
+ { h2: 'What we did' },
350
+ {
351
+ p: `${firm} began with the council’s statutory duties, then its cost base. Each proposed saving was checked against those duties by the council’s own legal team before it went to elected members.`,
352
+ },
353
+ {
354
+ bullets: [
355
+ 'Merged finance, payroll, procurement, IT, legal and property support into one shared service',
356
+ 'Renegotiated 41 contracts worth £96 million a year',
357
+ 'Sold or let eleven under-used buildings',
358
+ ],
359
+ },
360
+ { h2: 'Results' },
361
+ {
362
+ p: 'The gap was closed four months ahead of the deadline. Libraries, children’s centres and road maintenance kept their budgets in full, and none of the savings has had to be reversed.',
363
+ },
364
+ ],
365
+ },
366
+ ];
367
+ export const VITRINE_DEMO_TESTIMONIALS = [
368
+ {
369
+ authorName: 'Helen Achterberg',
370
+ authorRole: 'Chief Operating Officer, Meridian Rail',
371
+ quote: () => 'They spent three weeks in our depots and on night shifts before they showed us a single slide. The rosters we run today are theirs, and so are the eleven people on my team who now maintain them.',
372
+ },
373
+ {
374
+ authorName: 'Tomás Aguirre',
375
+ authorRole: 'Group Finance Director, Castell & Vane Hotels',
376
+ quote: () => 'The pricing desk paid for the whole engagement in its first eleven weeks. What I value more is that my general managers now argue about the forecast using the same numbers.',
377
+ },
378
+ {
379
+ authorName: 'Daniel Whitcombe',
380
+ authorRole: 'Chief Executive, Kellmoor City Council',
381
+ quote: (firm) => `${firm} was the only adviser that asked to see our statutory duties before our cost base. Every saving in the plan was checked against them, and none has had to be reversed.`,
382
+ },
383
+ ];
384
+ // ---------------------------------------------------------------------------
385
+ // Pages
386
+ // ---------------------------------------------------------------------------
387
+ const BLOCK_VERSION = '1.0.0';
388
+ function prose(key, parts) {
389
+ return {
390
+ _key: key,
391
+ _type: 'prose',
392
+ _version: BLOCK_VERSION,
393
+ body: richText(`${key}-body`, parts),
394
+ };
395
+ }
396
+ const LOGO_KEYS = [
397
+ 'logo-meridian-rail',
398
+ 'logo-castell-vane',
399
+ 'logo-aldermoor',
400
+ 'logo-brightwater',
401
+ 'logo-nordvik',
402
+ 'logo-oakfield-health',
403
+ ];
404
+ const LOGO_NAMES = {
405
+ 'logo-meridian-rail': 'Meridian Rail',
406
+ 'logo-castell-vane': 'Castell & Vane Hotels',
407
+ 'logo-aldermoor': 'Aldermoor Bank',
408
+ 'logo-brightwater': 'Brightwater Foods',
409
+ 'logo-nordvik': 'Nordvik Industries',
410
+ 'logo-oakfield-health': 'Oakfield Health',
411
+ };
412
+ /**
413
+ * `home` (ten blocks: hero, selected clients, practices, the firm in figures,
414
+ * selected work, a client's words, research and its exhibit, questions, and
415
+ * the call to action), `practices`, `case-studies`, `about` and `contact`.
416
+ *
417
+ * A function of `media` (`SeedContext.media`), of the real practice ids
418
+ * `seedVitrineDemoContent` assigns (`serviceIdBySlug`) and of the firm's name.
419
+ * All three default, so `buildVitrineDemoPages({})` still returns complete,
420
+ * contract-valid pages: a block whose required media is absent is omitted
421
+ * rather than emitted invalid.
422
+ */
423
+ export function buildVitrineDemoPages(media = {}, serviceIdBySlug = new Map(), firm = DEFAULT_FIRM_NAME) {
424
+ const serviceLink = (slug) => {
425
+ const id = serviceIdBySlug.get(slug);
426
+ return id === undefined ? undefined : { collection: 'service', id };
427
+ };
428
+ const practices = (key, title) => ({
429
+ _key: key,
430
+ _type: 'featureGrid',
431
+ _version: BLOCK_VERSION,
432
+ title,
433
+ items: VITRINE_DEMO_SERVICES.map((demo, index) => {
434
+ const link = serviceLink(demo.slug);
435
+ return {
436
+ _key: `${key}-${index}`,
437
+ icon: demo.icon,
438
+ title: demo.name,
439
+ text: demo.description,
440
+ ...(link === undefined ? {} : { link }),
441
+ };
442
+ }),
443
+ });
444
+ const logoIds = LOGO_KEYS.map((key) => ({ key, id: media[key] })).filter((entry) => entry.id !== undefined);
445
+ const callToAction = (key) => ({
446
+ _key: key,
447
+ _type: 'cta',
448
+ _version: BLOCK_VERSION,
449
+ title: 'Tell us about the decision in front of you',
450
+ text: 'A partner will reply within two working days to arrange a first conversation. There is no charge for it and no obligation.',
451
+ actions: [{ label: 'Contact an office', target: { href: '/contact' }, emphasis: 'primary' }],
452
+ });
453
+ const [rail, hotels, council] = VITRINE_DEMO_TESTIMONIALS;
454
+ const avatar = (index) => {
455
+ const id = media[`avatar-${index}`];
456
+ return id === undefined ? {} : { avatar: id };
457
+ };
458
+ const home = {
459
+ title: 'Home',
460
+ slug: 'home',
461
+ blocks: [
462
+ {
463
+ _key: 'demo-home-hero',
464
+ _type: 'hero',
465
+ _version: BLOCK_VERSION,
466
+ eyebrow: 'Management consultancy · London, New York, Singapore',
467
+ title: 'Decisions that still hold a year later',
468
+ subtitle: 'We advise the boards and executive teams of companies with £50 million to £2 billion in revenue on strategy, operations and transactions, and we stay until the results show in the accounts.',
469
+ ...(media.hero === undefined ? {} : { media: media.hero }),
470
+ actions: [
471
+ { label: 'Discuss a mandate', target: { href: '/contact' }, emphasis: 'primary' },
472
+ { label: 'Selected work', target: { href: '/case-studies' } },
473
+ ],
474
+ },
475
+ ...(logoIds.length === 0
476
+ ? []
477
+ : [
478
+ {
479
+ _key: 'demo-home-clients',
480
+ _type: 'logoStrip',
481
+ _version: BLOCK_VERSION,
482
+ logos: logoIds.map((logo, index) => ({
483
+ _key: `demo-home-client-${index}`,
484
+ media: logo.id,
485
+ })),
486
+ caption: 'Selected clients',
487
+ },
488
+ ]),
489
+ practices('demo-home-practices', 'Practices'),
490
+ {
491
+ _key: 'demo-home-figures',
492
+ _type: 'stats',
493
+ _version: BLOCK_VERSION,
494
+ title: 'The firm in figures',
495
+ items: [
496
+ { _key: 'demo-home-figure-1', value: '340', label: 'engagements completed since 2011' },
497
+ {
498
+ _key: 'demo-home-figure-2',
499
+ value: '72',
500
+ unit: '%',
501
+ label: 'of fees from clients who have worked with us before',
502
+ },
503
+ {
504
+ _key: 'demo-home-figure-3',
505
+ value: '58',
506
+ label: 'consultants, a third of them former operating executives',
507
+ },
508
+ {
509
+ _key: 'demo-home-figure-4',
510
+ value: '3',
511
+ label: 'offices, in London, New York and Singapore',
512
+ },
513
+ ],
514
+ },
515
+ {
516
+ _key: 'demo-home-work',
517
+ _type: 'collectionList',
518
+ _version: BLOCK_VERSION,
519
+ title: 'Selected work',
520
+ collection: 'case_study',
521
+ sort: { field: 'createdAt', direction: 'asc' },
522
+ limit: 3,
523
+ layout: 'list',
524
+ },
525
+ {
526
+ _key: 'demo-home-testimonial',
527
+ _type: 'testimonial',
528
+ _version: BLOCK_VERSION,
529
+ quote: richTextParagraph('demo-home-testimonial-quote', rail.quote(firm)),
530
+ attribution: { name: rail.authorName, role: rail.authorRole, ...avatar(0) },
531
+ },
532
+ prose('demo-home-research', [
533
+ { h2: 'Research' },
534
+ {
535
+ p: 'Each year we survey the finance and operations leaders of mid-sized companies. The 2026 edition covers 412 companies in four countries, and its central finding is simple: the number of separate planning cycles a company runs predicts how much cash is tied up in the business better than its sector or its size.',
536
+ },
537
+ {
538
+ p: 'Companies that had merged three or more of those cycles released a median 3.8% of revenue in working capital within a year. The full report, with the method and the questionnaire, is available from any of our offices.',
539
+ },
540
+ ]),
541
+ ...(media.exhibit === undefined
542
+ ? []
543
+ : [
544
+ {
545
+ _key: 'demo-home-exhibit',
546
+ _type: 'mediaFigure',
547
+ _version: BLOCK_VERSION,
548
+ media: media.exhibit,
549
+ caption: 'Exhibit 3, 2026 Mid-Market Operations Survey. Companies grouped by how many of their budget, sales and operations, workforce and capital planning cycles they had merged.',
550
+ credit: `Source: ${firm} analysis`,
551
+ ratio: 'original',
552
+ align: 'wide',
553
+ },
554
+ ]),
555
+ homeFaq(),
556
+ callToAction('demo-home-cta'),
557
+ ],
558
+ };
559
+ const practicesPage = {
560
+ title: 'Practices',
561
+ slug: 'practices',
562
+ blocks: [
563
+ prose('demo-practices-intro', [
564
+ {
565
+ p: `${firm} is organised in six practices. Each is led by partners who have held operating roles in the field they advise on, and most engagements draw on two or three of them.`,
566
+ },
567
+ ]),
568
+ practices('demo-practices-list', 'Six practices'),
569
+ {
570
+ _key: 'demo-practices-method',
571
+ _type: 'accordion',
572
+ _version: BLOCK_VERSION,
573
+ title: 'How an engagement runs',
574
+ items: [
575
+ [
576
+ 'Diagnostic',
577
+ 'Six to ten weeks. We rebuild the facts from your own systems, interview the people who run the work and agree the size of the opportunity with your finance team.',
578
+ ],
579
+ [
580
+ 'Design',
581
+ 'Four to eight weeks. Each change is costed, given an owner and ranked by value and risk. The executive team decides what goes ahead.',
582
+ ],
583
+ [
584
+ 'Implementation',
585
+ 'Six to eighteen months. Our consultants work inside your teams, and the steering group meets every fortnight with a partner present.',
586
+ ],
587
+ [
588
+ 'Review',
589
+ 'Twelve months after we leave, we return for a day at our own cost to check the results against the plan with you.',
590
+ ],
591
+ ].map(([question, answer], index) => ({
592
+ _key: `demo-practices-method-${index}`,
593
+ question: question,
594
+ answer: richTextParagraph(`demo-practices-method-${index}-a`, answer),
595
+ })),
596
+ },
597
+ callToAction('demo-practices-cta'),
598
+ ],
599
+ };
600
+ const caseStudiesPage = {
601
+ title: 'Case studies',
602
+ slug: 'case-studies',
603
+ blocks: [
604
+ prose('demo-work-intro', [
605
+ {
606
+ p: 'A selection of engagements our clients have agreed to describe in public. Figures are taken from their published accounts or regulatory filings, and each account was reviewed by the client before publication.',
607
+ },
608
+ ]),
609
+ {
610
+ _key: 'demo-work-all',
611
+ _type: 'collectionList',
612
+ _version: BLOCK_VERSION,
613
+ collection: 'case_study',
614
+ sort: { field: 'createdAt', direction: 'asc' },
615
+ limit: 12,
616
+ layout: 'grid',
617
+ },
618
+ ...(logoIds.length === 0
619
+ ? []
620
+ : [
621
+ {
622
+ _key: 'demo-work-clients',
623
+ _type: 'logos',
624
+ _version: BLOCK_VERSION,
625
+ title: 'Clients we have advised',
626
+ items: logoIds.map((logo, index) => ({
627
+ _key: `demo-work-client-${index}`,
628
+ media: logo.id,
629
+ name: LOGO_NAMES[logo.key],
630
+ })),
631
+ },
632
+ ]),
633
+ callToAction('demo-work-cta'),
634
+ ],
635
+ };
636
+ const about = {
637
+ title: 'About',
638
+ slug: 'about',
639
+ blocks: [
640
+ prose('demo-about-story', [
641
+ {
642
+ p: `${firm} was founded in London in 2011 by four partners who had spent their careers running operations inside mid-sized companies before advising them.`,
643
+ },
644
+ { h2: 'Why we started' },
645
+ {
646
+ p: 'Large consultancies were built for the largest companies, and their methods assume a head office with hundreds of analysts. Companies with a few thousand employees need the same quality of thinking from a team that fits in their building and understands a business where the chief executive signs the larger purchase orders.',
647
+ },
648
+ { h2: 'How we work' },
649
+ {
650
+ p: 'A partner leads every engagement from the first meeting to the final review. Teams are small and stay with a client from diagnosis to implementation, and we publish our research so clients can judge our thinking before they hire us.',
651
+ },
652
+ {
653
+ p: 'We opened in New York in 2016 and in Singapore in 2021, each time to follow clients who were expanding there.',
654
+ },
655
+ { h2: 'Ownership' },
656
+ {
657
+ p: 'The firm is a limited liability partnership owned by its 14 partners. We have no outside shareholders and no commercial relationships with software vendors.',
658
+ },
659
+ ]),
660
+ {
661
+ _key: 'demo-about-figures',
662
+ _type: 'statCounter',
663
+ _version: BLOCK_VERSION,
664
+ title: 'Since 2011',
665
+ stats: [
666
+ { _key: 'demo-about-figure-1', value: '340', label: 'engagements completed' },
667
+ {
668
+ _key: 'demo-about-figure-2',
669
+ value: '14',
670
+ label: 'partners, each with operating experience',
671
+ },
672
+ {
673
+ _key: 'demo-about-figure-3',
674
+ value: '21',
675
+ label: 'countries where clients have operations',
676
+ },
677
+ {
678
+ _key: 'demo-about-figure-4',
679
+ value: '£1.9bn',
680
+ label: 'of cash and profit our clients have reported',
681
+ },
682
+ ],
683
+ },
684
+ {
685
+ _key: 'demo-about-quote-hotels',
686
+ _type: 'quote',
687
+ _version: BLOCK_VERSION,
688
+ text: hotels.quote(firm),
689
+ author: hotels.authorName,
690
+ role: hotels.authorRole,
691
+ ...avatar(1),
692
+ },
693
+ {
694
+ _key: 'demo-about-quote-council',
695
+ _type: 'quote',
696
+ _version: BLOCK_VERSION,
697
+ text: council.quote(firm),
698
+ author: council.authorName,
699
+ role: council.authorRole,
700
+ ...avatar(2),
701
+ },
702
+ callToAction('demo-about-cta'),
703
+ ],
704
+ };
705
+ const contact = {
706
+ title: 'Contact',
707
+ slug: 'contact',
708
+ blocks: [
709
+ prose('demo-contact-offices', [
710
+ {
711
+ p: 'Write to the office nearest to you, or to any partner you already know. We reply within two working days.',
712
+ },
713
+ { h2: 'London' },
714
+ {
715
+ p: '12 Hanover Square, London W1S 1JB, United Kingdom. Telephone +44 20 7946 0321. london@example.com',
716
+ },
717
+ { h2: 'New York' },
718
+ {
719
+ p: '230 Park Avenue, Floor 10, New York, NY 10169, United States. Telephone +1 212 555 0148. newyork@example.com',
720
+ },
721
+ { h2: 'Singapore' },
722
+ {
723
+ p: '8 Marina View, #32-01, Singapore 018960. Telephone +65 6555 0172. singapore@example.com',
724
+ },
725
+ { h2: 'Careers' },
726
+ {
727
+ p: 'We recruit experienced hires throughout the year and graduates each autumn. Send a CV and a short note on the kind of work you want to do to careers@example.com.',
728
+ },
729
+ ]),
730
+ {
731
+ _key: 'demo-contact-cta',
732
+ _type: 'cta',
733
+ _version: BLOCK_VERSION,
734
+ title: 'Discuss a mandate',
735
+ text: 'Describe the decision, its timing and who is involved. A partner will reply within two working days.',
736
+ actions: [
737
+ {
738
+ label: 'Write to us',
739
+ target: { href: 'mailto:hello@example.com' },
740
+ emphasis: 'primary',
741
+ },
742
+ ],
743
+ },
744
+ ],
745
+ };
746
+ return [home, practicesPage, caseStudiesPage, about, contact];
747
+ }
748
+ function homeFaq() {
749
+ const items = [
750
+ [
751
+ 'What size of company do you work with?',
752
+ 'Most of our clients have annual revenue between £50 million and £2 billion and are owned by founders, families or private equity. We also advise public bodies and a small number of listed companies.',
753
+ ],
754
+ [
755
+ 'How is an engagement priced?',
756
+ 'We agree a fixed fee for a defined scope before work starts. On operations programmes, up to a third of the fee can depend on results measured by your own finance team.',
757
+ ],
758
+ [
759
+ 'Who will we work with day to day?',
760
+ 'A partner leads every engagement and attends each steering meeting. Teams are small, usually three to six consultants, and the people who scope the work are the people who do it.',
761
+ ],
762
+ [
763
+ 'How long does a typical engagement last?',
764
+ 'Diagnostic work takes six to ten weeks. Implementation runs from six to eighteen months, with a formal review at each stage, so you can stop or change course.',
765
+ ],
766
+ [
767
+ 'Do you work alongside other advisers?',
768
+ 'Often. We work with auditors, lawyers and systems integrators under a shared plan, and we will say early if another firm is better placed for part of the work.',
769
+ ],
770
+ ];
771
+ return {
772
+ _key: 'demo-home-faq',
773
+ _type: 'faq',
774
+ _version: BLOCK_VERSION,
775
+ title: 'Working with us',
776
+ items: items.map(([question, answer], index) => ({
777
+ _key: `demo-home-faq-${index}`,
778
+ question,
779
+ answer: richTextParagraph(`demo-home-faq-${index}-a`, answer),
780
+ })),
781
+ };
782
+ }
783
+ /** `VITRINE_DEMO_PAGES`: the fixed-shape alias older callers use. Equivalent to `buildVitrineDemoPages({})`. */
784
+ export const VITRINE_DEMO_PAGES = buildVitrineDemoPages({});
785
+ export const VITRINE_RECOMMENDED_AGENTS = [
786
+ {
787
+ name: 'seoAgent',
788
+ package: '@cogenta/agents-builtin',
789
+ reason: 'Audits practice and case-study pages for on-page SEO issues before they go live.',
790
+ },
791
+ {
792
+ name: 'performanceAgent',
793
+ package: '@cogenta/agents-builtin',
794
+ reason: 'Catches oversized images and third-party scripts that would slow the site down.',
795
+ },
796
+ ];
797
+ /**
798
+ * `vitrine`'s own starting skin, asserted present with a real check rather
799
+ * than a `!`: `STARTING_SKINS` is keyed by blueprint id and TypeScript cannot
800
+ * see that this key is always populated.
801
+ */
802
+ function vitrinePalette() {
803
+ const skin = STARTING_SKINS.vitrine;
804
+ if (skin === undefined) {
805
+ throw new CogentaError({
806
+ code: 'BLUEPRINT_REGISTRY_CORRUPT',
807
+ message: 'STARTING_SKINS.vitrine is missing.',
808
+ hint: 'The "vitrine" entry must stay declared in starting-skins.ts for this blueprint to render its demo media.',
809
+ });
810
+ }
811
+ return skin.color;
812
+ }
813
+ /**
814
+ * The media this blueprint seeds. Every slot names a bundled file under
815
+ * `assets/photos/vitrine/`: a cropped photograph for the hero, the three case
816
+ * studies and the three portraits, a rendered research exhibit, and six client
817
+ * wordmarks drawn once with OFL typefaces. The procedural `spec` is only the
818
+ * fallback `seedDemoMedia` uses if a file is ever missing.
819
+ */
820
+ export const VITRINE_MEDIA_SPECS = [
821
+ {
822
+ name: 'hero',
823
+ spec: heroArt(vitrinePalette(), 'sun', 61),
824
+ alt: 'Two consultants reviewing a report at a meeting table',
825
+ photo: 'vitrine/hero-review.jpg',
826
+ },
827
+ {
828
+ name: 'exhibit',
829
+ spec: coverArt(vitrinePalette(), 62),
830
+ alt: 'Bar chart: companies that merged three or more planning cycles released a median 3.8% of revenue in working capital, against 0.4% for companies that merged none.',
831
+ photo: 'vitrine/exhibit-working-capital.png',
832
+ },
833
+ ...LOGO_KEYS.map((key, index) => ({
834
+ name: key,
835
+ spec: logoArt(70 + index),
836
+ alt: LOGO_NAMES[key],
837
+ photo: `vitrine/${key}.png`,
838
+ })),
839
+ ...VITRINE_DEMO_TESTIMONIALS.map((demo, index) => ({
840
+ name: `avatar-${index}`,
841
+ spec: avatarArt(vitrinePalette(), 80 + index),
842
+ alt: `Portrait of ${demo.authorName}`,
843
+ photo: `vitrine/avatar-${index + 1}.jpg`,
844
+ })),
845
+ {
846
+ name: 'case-meridian-rail-punctuality',
847
+ spec: coverArt(vitrinePalette(), 90),
848
+ alt: 'Passengers walking along a platform beside a commuter train',
849
+ photo: 'vitrine/case-rail.jpg',
850
+ },
851
+ {
852
+ name: 'case-castell-vane-pricing',
853
+ spec: coverArt(vitrinePalette(), 91),
854
+ alt: 'The dining room of a hotel at dusk, tables laid under a skylight',
855
+ photo: 'vitrine/case-hotels.jpg',
856
+ },
857
+ {
858
+ name: 'case-kellmoor-council-budget',
859
+ spec: coverArt(vitrinePalette(), 92),
860
+ alt: 'The colonnaded front of a city hall in afternoon sun',
861
+ photo: 'vitrine/case-council.jpg',
862
+ },
863
+ ];
864
+ /** Header/footer navigation and the header call to action (L25, D4). */
865
+ export const VITRINE_MENUS = {
866
+ header: [
867
+ { label: 'Practices', url: '/practices' },
868
+ { label: 'Case studies', url: '/case-studies' },
869
+ { label: 'About', url: '/about' },
870
+ { label: 'Contact', url: '/contact' },
871
+ ],
872
+ footer: [
873
+ { label: 'Practices', url: '/practices' },
874
+ { label: 'Case studies', url: '/case-studies' },
875
+ { label: 'About', url: '/about' },
876
+ { label: 'Contact', url: '/contact' },
877
+ ],
878
+ headerAction: { label: 'Discuss a mandate', url: '/contact' },
879
+ };
880
+ export const VITRINE_SITE_SETTINGS = {
881
+ 'general.tagline': 'Management consultancy for mid-sized companies, their owners and their boards.',
882
+ 'general.socialLinks': [
883
+ { label: 'LinkedIn', url: 'https://linkedin.com/company/example' },
884
+ { label: 'X', url: 'https://x.com/example' },
885
+ { label: 'YouTube', url: 'https://youtube.com/@example' },
886
+ ],
887
+ 'general.footerNote': [
888
+ 'London\n12 Hanover Square\nLondon W1S 1JB',
889
+ 'New York\n230 Park Avenue, Floor 10\nNew York, NY 10169',
890
+ 'Singapore\n8 Marina View, #32-01\nSingapore 018960',
891
+ ].join('\n\n'),
892
+ };
893
+ /**
894
+ * The side column a consultancy's reader expects beside the work: on a case
895
+ * study, the sectors with their counts, the other published engagements and
896
+ * the one call to discuss a mandate; on a practice, the other practices and
897
+ * the partners' switchboard, with selected work under the text; on a sector
898
+ * archive and on search results, the sectors and the same call, plus a
899
+ * search box on the archive (a results page already opens on its own). Never
900
+ * on the home page or on the Practices, Case studies, About and Contact
901
+ * pages, which already carry their own lists and calls to action.
902
+ */
903
+ const VITRINE_CASE_STUDY = { kind: 'collection', collection: 'case_study' };
904
+ const VITRINE_SERVICE = { kind: 'collection', collection: 'service' };
905
+ const VITRINE_SECTOR = { kind: 'taxonomy', taxonomy: 'sector' };
906
+ const VITRINE_SEARCH = { kind: 'search' };
907
+ function onlyOn(...targets) {
908
+ return { pages: { mode: 'only', targets } };
909
+ }
910
+ export const VITRINE_WIDGETS = [
911
+ {
912
+ area: 'sidebar',
913
+ type: 'search',
914
+ title: 'Search the site',
915
+ settings: { placeholder: 'A client or a sector' },
916
+ visibility: onlyOn(VITRINE_SECTOR),
917
+ },
918
+ {
919
+ area: 'sidebar',
920
+ type: 'terms',
921
+ title: 'Sectors',
922
+ settings: { taxonomy: 'sector', showCounts: true, hierarchical: false, hideEmpty: true },
923
+ visibility: onlyOn(VITRINE_CASE_STUDY, VITRINE_SECTOR, VITRINE_SEARCH),
924
+ },
925
+ {
926
+ area: 'sidebar',
927
+ type: 'recentEntries',
928
+ title: 'More case studies',
929
+ settings: { collection: 'case_study', count: 3, showDate: false },
930
+ visibility: onlyOn(VITRINE_CASE_STUDY),
931
+ },
932
+ {
933
+ area: 'sidebar',
934
+ type: 'recentEntries',
935
+ title: 'Other practices',
936
+ settings: { collection: 'service', count: 6, showDate: false },
937
+ visibility: onlyOn(VITRINE_SERVICE),
938
+ },
939
+ {
940
+ area: 'sidebar',
941
+ type: 'contact',
942
+ title: 'Speak to a partner',
943
+ settings: {
944
+ address: '12 Hanover Square, London W1S 1JB',
945
+ phone: '+44 20 7946 0321',
946
+ email: 'hello@example.com',
947
+ hours: [{ label: 'Monday to Friday', value: '8:30 to 18:30' }],
948
+ },
949
+ visibility: onlyOn(VITRINE_SERVICE),
950
+ },
951
+ {
952
+ area: 'sidebar',
953
+ type: 'cta',
954
+ settings: {
955
+ heading: 'Facing a similar decision?',
956
+ body: 'Describe it in a few lines. A partner will reply within two working days, at no charge.',
957
+ label: 'Discuss a mandate',
958
+ href: '/contact',
959
+ },
960
+ visibility: onlyOn(VITRINE_CASE_STUDY, VITRINE_SECTOR, VITRINE_SEARCH),
961
+ },
962
+ {
963
+ area: 'content-after',
964
+ type: 'recentEntries',
965
+ title: 'Selected work',
966
+ settings: { collection: 'case_study', count: 3, showDate: false, showImage: true },
967
+ visibility: onlyOn(VITRINE_SERVICE),
968
+ },
969
+ ];
970
+ /**
971
+ * Inserts the demo content through the real `ContentStore` and taxonomy store
972
+ * (never mocked, house rule). Sectors first, then practices and case studies,
973
+ * whose ids the home page's links and lists need; everything is published,
974
+ * since a theme lists only published entries.
975
+ */
976
+ async function seedVitrineDemoContent(ctx) {
977
+ const { db, defaultLocale, adminId, media } = ctx;
978
+ const firm = ctx.siteName?.trim() || DEFAULT_FIRM_NAME;
979
+ const sectorStore = createTaxonomyStore({ db, taxonomy: sector });
980
+ const serviceStore = createContentStore({ db, collection: service, defaultLocale });
981
+ const caseStudyStore = createContentStore({ db, collection: caseStudy, defaultLocale });
982
+ const testimonialStore = createContentStore({ db, collection: testimonial, defaultLocale });
983
+ const pageStore = createContentStore({ db, collection: page, defaultLocale });
984
+ const sectorIdBySlug = new Map();
985
+ for (const demo of VITRINE_DEMO_SECTORS) {
986
+ const term = await sectorStore.create({
987
+ slug: demo.slug,
988
+ labels: { [defaultLocale]: demo.name },
989
+ });
990
+ sectorIdBySlug.set(demo.slug, term.id);
991
+ }
992
+ const serviceIdBySlug = new Map();
993
+ for (const demo of VITRINE_DEMO_SERVICES) {
994
+ const created = await serviceStore.create({
995
+ status: 'published',
996
+ createdBy: adminId,
997
+ values: {
998
+ name: demo.name,
999
+ slug: demo.slug,
1000
+ description: demo.description,
1001
+ body: richText(`service-${demo.slug}`, demo.body(firm)),
1002
+ icon: demo.icon,
1003
+ },
1004
+ });
1005
+ serviceIdBySlug.set(demo.slug, created.id);
1006
+ }
1007
+ for (const demo of VITRINE_DEMO_CASE_STUDIES) {
1008
+ const cover = media[`case-${demo.slug}`];
1009
+ await caseStudyStore.create({
1010
+ status: 'published',
1011
+ createdBy: adminId,
1012
+ values: {
1013
+ title: demo.title,
1014
+ slug: demo.slug,
1015
+ client: demo.client,
1016
+ summary: demo.summary,
1017
+ body: richText(`case-${demo.slug}`, demo.body(firm)),
1018
+ sector: sectorIdBySlug.get(demo.sectorSlug) ?? null,
1019
+ ...(cover === undefined ? {} : { coverImage: cover }),
1020
+ },
1021
+ });
1022
+ }
1023
+ for (const [index, demo] of VITRINE_DEMO_TESTIMONIALS.entries()) {
1024
+ const portrait = media[`avatar-${index}`];
1025
+ await testimonialStore.create({
1026
+ status: 'published',
1027
+ createdBy: adminId,
1028
+ values: {
1029
+ authorName: demo.authorName,
1030
+ authorRole: demo.authorRole,
1031
+ quote: demo.quote(firm),
1032
+ ...(portrait === undefined ? {} : { avatar: portrait }),
1033
+ },
1034
+ });
1035
+ }
1036
+ for (const demo of buildVitrineDemoPages(media, serviceIdBySlug, firm)) {
1037
+ await pageStore.create({
1038
+ status: 'published',
1039
+ createdBy: adminId,
1040
+ values: { title: demo.title, slug: demo.slug },
1041
+ blocks: { blocks: demo.blocks.map(toBlockZoneEntry) },
1042
+ });
1043
+ }
1044
+ }
1045
+ export const vitrineContentPack = {
1046
+ collections: VITRINE_COLLECTIONS,
1047
+ taxonomies: VITRINE_TAXONOMIES,
1048
+ recommendedAgents: VITRINE_RECOMMENDED_AGENTS,
1049
+ seedDemoContent: seedVitrineDemoContent,
1050
+ defaultTheme: '@cogenta/theme-entreprise',
1051
+ menus: VITRINE_MENUS,
1052
+ widgets: VITRINE_WIDGETS,
1053
+ siteSettings: VITRINE_SITE_SETTINGS,
1054
+ mediaSpecs: VITRINE_MEDIA_SPECS,
1055
+ };
1056
+ //# sourceMappingURL=vitrine.js.map