create-cogenta 0.2.1 → 0.5.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 (183) 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/llm-setup.d.ts +10 -1
  163. package/dist/llm-setup.d.ts.map +1 -1
  164. package/dist/llm-setup.js +12 -2
  165. package/dist/llm-setup.js.map +1 -1
  166. package/dist/playground-reset.d.ts.map +1 -1
  167. package/dist/playground-reset.js +22 -4
  168. package/dist/playground-reset.js.map +1 -1
  169. package/dist/prompts.d.ts +12 -0
  170. package/dist/prompts.d.ts.map +1 -1
  171. package/dist/prompts.js +32 -0
  172. package/dist/prompts.js.map +1 -1
  173. package/dist/recap.d.ts.map +1 -1
  174. package/dist/recap.js +6 -0
  175. package/dist/recap.js.map +1 -1
  176. package/dist/scaffold.d.ts +22 -2
  177. package/dist/scaffold.d.ts.map +1 -1
  178. package/dist/scaffold.js +185 -23
  179. package/dist/scaffold.js.map +1 -1
  180. package/dist/wizard.d.ts.map +1 -1
  181. package/dist/wizard.js +4 -1
  182. package/dist/wizard.js.map +1 -1
  183. package/package.json +14 -11
@@ -1,23 +1,41 @@
1
+ import { CogentaError } from '@cogenta/core';
1
2
  import { createContentStore, defineCollection, f, validateCollectionSet, } from '@cogenta/schema';
2
- import { definePageCollection, 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 `vitrine` blueprint's content model (L9 task 8, batch A): the classic
5
- * one-pager showcase site for a small business or independent — a list of
6
- * services and a few testimonials, both real, editable collections rather
7
- * than data baked into the page itself.
7
+ * The `vitrine` blueprint's content model (L9 task 8, batch A; raised to a
8
+ * pro template by L25): the classic one-pager showcase site for a small
9
+ * business or consultancy — a list of services and a few testimonials,
10
+ * both real, editable collections rather than data baked into the page
11
+ * itself.
12
+ *
13
+ * `icon` (a symbol name `@cogenta/theme-kit`'s `renderIcon` recognises) and
14
+ * `coverImage` (contract D `theme@1.4`'s `entryImage`) are what let
15
+ * `theme-entreprise`'s `featureGrid` and `collectionList` show a service
16
+ * the same way `theme-saas`'s own `feature` collection already does.
8
17
  */
9
18
  export const service = defineCollection({
10
19
  name: 'service',
11
20
  labels: { singular: 'Service', plural: 'Services' },
12
21
  // Routed, not just listed: `collectionList` (used on the home page below)
13
22
  // always builds a link for every entry it renders (`entryHref`,
14
- // `@cogenta/theme-canonical`), so a collection it targets must have a
15
- // route or that render call throws.
23
+ // `@cogenta/theme-kit`), so a collection it targets must have a route or
24
+ // that render call throws.
16
25
  routing: { pattern: '/services/:slug' },
17
26
  fields: {
18
27
  name: f.text({ required: true, max: 120 }),
19
28
  slug: f.slug({ from: 'name', unique: true }),
20
29
  description: f.text({ max: 400, multiline: true }),
30
+ icon: f.text({
31
+ max: 64,
32
+ admin: {
33
+ label: 'Icon',
34
+ help: 'One of the symbol names @cogenta/theme-kit recognises (e.g. "chart", "shield", "briefcase", "code", "trending-up", "tag"). Left blank, the service renders with no icon chip.',
35
+ },
36
+ }),
37
+ coverImage: f.media({ accept: ['image'] }),
38
+ ...SEO_FIELDS,
21
39
  },
22
40
  indexes: [['slug']],
23
41
  permissions: {
@@ -34,6 +52,7 @@ export const testimonial = defineCollection({
34
52
  authorName: f.text({ required: true, max: 120 }),
35
53
  authorRole: f.text({ max: 120 }),
36
54
  quote: f.text({ required: true, max: 500, multiline: true }),
55
+ avatar: f.media({ accept: ['image'] }),
37
56
  },
38
57
  permissions: {
39
58
  read: ['public'],
@@ -45,160 +64,317 @@ export const testimonial = defineCollection({
45
64
  export const page = definePageCollection('/:slug');
46
65
  export const VITRINE_COLLECTIONS = [service, testimonial, page];
47
66
  validateCollectionSet(VITRINE_COLLECTIONS);
67
+ /**
68
+ * Six real B2B capabilities, each with the icon `featureGrid` (home page)
69
+ * and this same entry's own `icon` field both point at — a `collectionList`
70
+ * of `service` and the home page's `featureGrid` read as one system rather
71
+ * than two unrelated sections.
72
+ */
48
73
  export const VITRINE_DEMO_SERVICES = [
49
74
  {
50
75
  name: 'Brand strategy',
51
76
  slug: 'brand-strategy',
52
- description: 'Positioning, messaging and visual identity worked out before a single page is built.',
77
+ description: 'We start every engagement here, because a redesign built on the wrong positioning just ships the confusion faster. Three weeks of interviews with your best customers and closest competitors end in a positioning document, a messaging framework, and a visual identity your team can defend in a board meeting without us in the room.',
78
+ icon: 'tag',
79
+ },
80
+ {
81
+ name: 'Web design & build',
82
+ slug: 'web-design-build',
83
+ description: 'Most of the sites we replace took longer to explain than to load. We design and build on Cogenta itself, so your team edits copy and swaps testimonials without filing a ticket — and the finished site still says what you do in the first five seconds, on every screen size.',
84
+ icon: 'code',
85
+ },
86
+ {
87
+ name: 'Growth marketing',
88
+ slug: 'growth-marketing',
89
+ description: 'We run paid, lifecycle and content programmes against one dashboard tied to closed revenue, not clicks. A channel that stops paying for itself gets cut inside a reporting cycle, not a fiscal quarter — most engagements end with a smaller media budget and a larger pipeline than they started with.',
90
+ icon: 'trending-up',
91
+ },
92
+ {
93
+ name: 'Operations consulting',
94
+ slug: 'operations-consulting',
95
+ description: 'Most operations problems are three broken handoffs, not thirty. We map how work actually moves through your organisation, not how the org chart says it should, and fix the handful of handoffs that are really costing you cycle time — against a metric your leadership already tracks.',
96
+ icon: 'briefcase',
53
97
  },
54
98
  {
55
- name: 'Web design',
56
- slug: 'web-design',
57
- description: 'A site that says what the business does in the first five seconds, on every screen size.',
99
+ name: 'Financial advisory',
100
+ slug: 'financial-advisory',
101
+ description: 'We build the model your finance team will still be using eighteen months from now, not a one-off deck. Every forecast ties back to the same general-ledger export your controller already trusts, so the numbers survive contact with a real board, and a real audit.',
102
+ icon: 'chart',
58
103
  },
59
104
  {
60
- name: 'Ongoing support',
61
- slug: 'ongoing-support',
62
- description: 'Small, steady changes after launch content updates, fixes, the occasional new page.',
105
+ name: 'Security & compliance',
106
+ slug: 'security-compliance',
107
+ description: 'We inherited more than one compliance programme that was a binder nobody had opened since the last audit. Ours ships with a named owner and a real audit trail for every control, mapped to SOC 2 or ISO 27001 depending on what your customers actually ask for.',
108
+ icon: 'shield',
63
109
  },
64
110
  ];
65
111
  export const VITRINE_DEMO_TESTIMONIALS = [
66
112
  {
67
113
  authorName: 'Amina Diallo',
68
114
  authorRole: 'Founder, Atelier Diallo',
69
- quote: 'The new site paid for itself in the first month people finally understood what we do before they called.',
115
+ quote: 'Northfield rebuilt our positioning before they touched a single page of the site, and it showed — the new site paid for itself in the first month, because for the first time people understood what we do before they ever called. We still edit it ourselves, weekly, without asking anyone for help.',
70
116
  },
71
117
  {
72
118
  authorName: 'Marco Bellini',
73
119
  authorRole: 'Owner, Bellini Consulting',
74
- quote: 'Fast, clear, no surprises. Exactly what a small business needs from a web project.',
120
+ quote: 'Fast, clear, no surprises, and a fixed quote that held from the first call to the last invoice — exactly what a small business needs from a consulting engagement and almost never gets. Northfield delivered the brand and the site in five weeks and has been one email away ever since.',
121
+ },
122
+ {
123
+ authorName: 'Priya Chandra',
124
+ authorRole: 'COO, Chandra & Partners',
125
+ quote: "Northfield's operations review took three weeks and changed how four teams hand off work to each other. We are still finding the recommendations landing a full quarter later, in meetings Northfield was never in the room for — that is the real test of whether a consultant fixed something.",
75
126
  },
76
127
  ];
77
128
  const BLOCK_VERSION = '1.0.0';
129
+ function proseParagraph(key, text) {
130
+ return {
131
+ _key: key,
132
+ _type: 'prose',
133
+ _version: BLOCK_VERSION,
134
+ body: richTextParagraph(`${key}-body`, text),
135
+ };
136
+ }
137
+ /** A multi-paragraph `prose` block — same shape convention as `restaurant.ts`'s own `proseBlock`, for the About page's real founding story rather than one thin paragraph. */
138
+ function proseBlock(key, paragraphs) {
139
+ return {
140
+ _key: key,
141
+ _type: 'prose',
142
+ _version: BLOCK_VERSION,
143
+ body: paragraphs.map((text, index) => ({
144
+ _key: `${key}-p${index}`,
145
+ _type: 'block',
146
+ style: 'normal',
147
+ children: [{ _key: `${key}-p${index}-s`, _type: 'span', text, marks: [] }],
148
+ markDefs: [],
149
+ })),
150
+ };
151
+ }
78
152
  /**
79
- * `home` (hero + a live list of services + a closing call to action) and
80
- * `about` (a short prose intro plus the same live list of testimonials)
81
- * both compositions of blocks already in the frozen vocabulary (contract B),
82
- * rendered generically by `@cogenta/theme-canonical`. Services and
83
- * testimonials are read through `collectionList`, the same block `blog`
84
- * uses for its recent-posts list, rather than duplicating their text as
85
- * static block content: editing a service in the admin then genuinely
86
- * changes what the home page shows.
153
+ * `home` (eleven blocks, the "confident B2B one-pager" composition the L25
154
+ * brief asks for: hero trust strip services numbers a wide product
155
+ * shot the full services grid with covers client outcome → a second
156
+ * voice questions call to action → a short about teaser), `about` (the
157
+ * full story plus every real testimonial, mirrored as `quote` blocks so an
158
+ * edit to the collection is visible without touching a page), and
159
+ * `contact` (how to reach the business).
160
+ *
161
+ * A function of `media` (`SeedContext.media`) and the real ids
162
+ * `seedVitrineDemoContent` assigns its own services (`serviceIdBySlug`),
163
+ * not a static const: the hero's `media`, the trust strip's logos, the
164
+ * services grid's covers and the featureGrid's links all need ids only the
165
+ * scaffold knows at seed time. Both parameters default to empty so
166
+ * `buildVitrineDemoPages({})` (the blueprint test's own call, and every
167
+ * other blueprint's equivalent) still renders a complete, valid page —
168
+ * `logoStrip`/`mediaFigure`/the hero's own media are simply omitted rather
169
+ * than emitted with an empty required list.
87
170
  */
88
- export const VITRINE_DEMO_PAGES = [
89
- {
90
- title: 'Home',
91
- slug: 'home',
92
- blocks: [
93
- {
94
- _key: 'demo-home-hero',
95
- _type: 'hero',
96
- _version: BLOCK_VERSION,
97
- eyebrow: 'Vitrine',
98
- title: 'A showcase site that says what you do',
99
- subtitle: 'Scaffolded by create-cogenta from the "vitrine" blueprint, with real services and testimonials already in place.',
100
- actions: [
101
- { label: 'See our services', target: { href: '#services' }, emphasis: 'primary' },
102
- ],
103
- },
104
- {
105
- _key: 'demo-home-services',
106
- _type: 'collectionList',
107
- _version: BLOCK_VERSION,
108
- title: 'Services',
109
- collection: 'service',
110
- sort: { field: 'createdAt', direction: 'asc' },
111
- limit: 10,
112
- layout: 'grid',
113
- },
114
- {
115
- _key: 'demo-home-method',
116
- _type: 'featureGrid',
117
- _version: BLOCK_VERSION,
118
- title: 'How a project runs',
119
- items: [
120
- {
121
- _key: 'demo-method-1',
122
- icon: 'compass',
123
- title: 'We start with the question',
124
- text: 'One call about who the site is for and what it has to make happen. No brief template, no discovery deck.',
125
- },
126
- {
127
- _key: 'demo-method-2',
128
- icon: 'draft',
129
- title: 'You see it before it is built',
130
- text: 'A first design on the real content, not on placeholder text — so what you approve is what ships.',
131
- },
132
- {
133
- _key: 'demo-method-3',
134
- icon: 'handover',
135
- title: 'You keep the keys',
136
- text: 'Everything is normal editable content afterwards. No agency retainer to change a phone number.',
171
+ export function buildVitrineDemoPages(media = {}, serviceIdBySlug = new Map()) {
172
+ const serviceLink = (slug) => {
173
+ const id = serviceIdBySlug.get(slug);
174
+ return id === undefined ? undefined : { collection: 'service', id };
175
+ };
176
+ // `logoStrip` (blocks@2.0) requires at least one logo — a media map with
177
+ // no `logo-*` entries (a scaffold with no demo-art seeded, or this
178
+ // function called directly, as the blueprint test does) must therefore
179
+ // omit the whole block rather than emit an empty, contract-invalid one.
180
+ const logoItems = [0, 1, 2, 3, 4]
181
+ .map((index) => media[`logo-${index}`])
182
+ .filter((id) => id !== undefined)
183
+ .map((id, index) => ({ _key: `demo-logo-${index}`, media: id }));
184
+ const [testimonial1, testimonial2, testimonial3] = VITRINE_DEMO_TESTIMONIALS;
185
+ return [
186
+ {
187
+ title: 'Home',
188
+ slug: 'home',
189
+ blocks: [
190
+ {
191
+ _key: 'demo-home-hero',
192
+ _type: 'hero',
193
+ _version: BLOCK_VERSION,
194
+ eyebrow: 'Northfield Consulting · Est. 2011',
195
+ title: 'The consultancy growing companies call before their systems fall over',
196
+ subtitle: 'We handle the six functions that break first when headcount outpaces process — brand, web, growth, operations, finance and security — each led by a partner who has run it, not just advised on it.',
197
+ ...(media.hero === undefined ? {} : { media: media.hero }),
198
+ actions: [
199
+ { label: 'See our services', target: { href: '#services' }, emphasis: 'primary' },
200
+ { label: 'Get a quote', target: { href: '/contact' } },
201
+ ],
202
+ },
203
+ ...(logoItems.length === 0
204
+ ? []
205
+ : [
206
+ {
207
+ _key: 'demo-home-logos',
208
+ _type: 'logoStrip',
209
+ _version: BLOCK_VERSION,
210
+ logos: logoItems,
211
+ caption: 'Trusted by teams at',
212
+ },
213
+ ]),
214
+ {
215
+ _key: 'demo-home-services',
216
+ _type: 'featureGrid',
217
+ _version: BLOCK_VERSION,
218
+ title: 'What we do',
219
+ items: VITRINE_DEMO_SERVICES.map((demo, index) => {
220
+ const link = serviceLink(demo.slug);
221
+ return {
222
+ _key: `demo-service-${index}`,
223
+ icon: demo.icon,
224
+ title: demo.name,
225
+ text: demo.description,
226
+ ...(link === undefined ? {} : { link }),
227
+ };
228
+ }),
229
+ },
230
+ {
231
+ _key: 'demo-home-numbers',
232
+ _type: 'stats',
233
+ _version: BLOCK_VERSION,
234
+ items: [
235
+ { _key: 'demo-home-stat-1', value: '3', unit: 'weeks', label: 'typical project' },
236
+ { _key: 'demo-home-stat-2', value: '120', unit: '+', label: 'engagements delivered' },
237
+ { _key: 'demo-home-stat-3', value: '100', unit: '%', label: 'content you can edit' },
238
+ { _key: 'demo-home-stat-4', value: '0', label: 'lines of JavaScript shipped' },
239
+ ],
240
+ },
241
+ // `mediaFigure.media` (blocks@2.0) is required — with no `shot`
242
+ // media seeded, the block is omitted rather than emitted invalid
243
+ // (same reasoning as `logoStrip` above).
244
+ ...(media.shot === undefined
245
+ ? []
246
+ : [
247
+ {
248
+ _key: 'demo-home-shot',
249
+ _type: 'mediaFigure',
250
+ _version: BLOCK_VERSION,
251
+ media: media.shot,
252
+ caption: 'The engagement dashboard every Northfield team works from — the same tracker the client sees.',
253
+ ratio: '16:9',
254
+ align: 'wide',
255
+ },
256
+ ]),
257
+ {
258
+ _key: 'demo-home-services-grid',
259
+ _type: 'collectionList',
260
+ _version: BLOCK_VERSION,
261
+ title: 'Every service, in detail',
262
+ collection: 'service',
263
+ sort: { field: 'createdAt', direction: 'asc' },
264
+ limit: 10,
265
+ layout: 'grid',
266
+ },
267
+ {
268
+ _key: 'demo-home-testimonial',
269
+ _type: 'testimonial',
270
+ _version: BLOCK_VERSION,
271
+ quote: richTextParagraph('demo-home-testimonial-quote', testimonial1.quote),
272
+ attribution: {
273
+ name: testimonial1.authorName,
274
+ role: testimonial1.authorRole,
275
+ ...(media['avatar-0'] === undefined ? {} : { avatar: media['avatar-0'] }),
137
276
  },
138
- ],
139
- },
140
- {
141
- _key: 'demo-home-numbers',
142
- _type: 'stats',
143
- _version: BLOCK_VERSION,
144
- items: [
145
- { _key: 'demo-home-stat-1', value: '3', unit: 'weeks', label: 'typical project' },
146
- { _key: 'demo-home-stat-2', value: '100', unit: '%', label: 'content you can edit' },
147
- { _key: 'demo-home-stat-3', value: '0', label: 'lines of JavaScript shipped' },
148
- ],
149
- },
150
- {
151
- _key: 'demo-home-cta',
152
- _type: 'cta',
153
- _version: BLOCK_VERSION,
154
- title: 'Ready to get started?',
155
- text: 'Every part of this page — the services, the testimonials, this call to action — is normal editable content.',
156
- actions: [{ label: 'Get in touch', target: { href: '/about' }, emphasis: 'primary' }],
157
- },
277
+ },
278
+ {
279
+ _key: 'demo-home-quote',
280
+ _type: 'quote',
281
+ _version: BLOCK_VERSION,
282
+ text: testimonial2.quote,
283
+ author: testimonial2.authorName,
284
+ role: testimonial2.authorRole,
285
+ },
286
+ homeFaq(),
287
+ {
288
+ _key: 'demo-home-cta',
289
+ _type: 'cta',
290
+ _version: BLOCK_VERSION,
291
+ title: 'Ready to get started?',
292
+ text: 'Book a thirty-minute call and leave with a scoped, fixed-price plan — not a follow-up email promising one.',
293
+ actions: [{ label: 'Get a quote', target: { href: '/contact' }, emphasis: 'primary' }],
294
+ },
295
+ proseParagraph('demo-home-about-teaser', `${testimonial3.authorName}'s operations review is one of well over a hundred engagements like it since Northfield opened in 2011. Read the founding story, and the rest of our client list, on the About page.`),
296
+ ],
297
+ },
298
+ {
299
+ title: 'About',
300
+ slug: 'about',
301
+ blocks: [
302
+ proseBlock('demo-about-prose', [
303
+ 'Northfield Consulting opened in 2011 with a simple complaint: growing companies were buying six different kinds of help from six different vendors, none of whom talked to each other, and paying for the seams as much as the work.',
304
+ 'We built one practice instead — brand, web, growth, operations, finance and security — staffed by partners who have actually run each function inside a company, not just advised one from the outside. A single point of contact means the web team knows what the brand team decided, and the finance model reflects what operations actually changed.',
305
+ 'Fifteen years and well over a hundred engagements later, the test we hold ourselves to has not changed: does the client still need us in the room a quarter after we leave? Most of the answers on this page came from clients who told us no, and meant it as a compliment.',
306
+ ]),
307
+ // Every testimonial from the real, editable collection, mirrored
308
+ // here as `quote` blocks — unlike `service` (routed above, so a
309
+ // link can point at it), a testimonial has no page of its own
310
+ // worth linking to, and `quote` — text/author/role, contract B's
311
+ // vocabulary block for exactly this — is the honest fit.
312
+ ...VITRINE_DEMO_TESTIMONIALS.map((demo, index) => ({
313
+ _key: `demo-about-quote-${index + 1}`,
314
+ _type: 'quote',
315
+ _version: BLOCK_VERSION,
316
+ text: demo.quote,
317
+ author: demo.authorName,
318
+ role: demo.authorRole,
319
+ })),
320
+ ],
321
+ },
322
+ {
323
+ title: 'Contact',
324
+ slug: 'contact',
325
+ blocks: [
326
+ proseParagraph('demo-contact-prose', 'Tell us what you are trying to get done and which of the six practices it touches — brand, web, growth, operations, finance, or security. We will tell you honestly whether we can help on the first call, no discovery deck required.'),
327
+ {
328
+ _key: 'demo-contact-cta',
329
+ _type: 'cta',
330
+ _version: BLOCK_VERSION,
331
+ title: 'Get a quote',
332
+ text: 'Thirty minutes, no obligation. We reply the same business day.',
333
+ actions: [
334
+ {
335
+ label: 'Email us',
336
+ target: { href: 'mailto:hello@example.com' },
337
+ emphasis: 'primary',
338
+ },
339
+ ],
340
+ },
341
+ ],
342
+ },
343
+ ];
344
+ }
345
+ function homeFaq() {
346
+ const items = [
347
+ [
348
+ 'How long does a typical engagement take?',
349
+ 'Most engagements run three to six weeks end to end. We scope and price the whole thing on a single call before a single hour is billed, so there is no discovery-phase invoice waiting at the end.',
158
350
  ],
159
- },
160
- {
161
- title: 'About',
162
- slug: 'about',
163
- blocks: [
164
- {
165
- _key: 'demo-about-prose',
166
- _type: 'prose',
167
- _version: BLOCK_VERSION,
168
- body: [
169
- {
170
- _key: 'demo-about-p1',
171
- _type: 'block',
172
- style: 'normal',
173
- children: [
174
- {
175
- _key: 'demo-about-p1-span',
176
- _type: 'span',
177
- text: 'This is a demo showcase site, scaffolded by create-cogenta from the "vitrine" blueprint. Its services and testimonials were seeded by the installer so there is real content to look at from the first run.',
178
- marks: [],
179
- },
180
- ],
181
- markDefs: [],
182
- },
183
- ],
184
- },
185
- // Testimonials are static `quote` blocks here, not a `collectionList`:
186
- // unlike `service` (routed above, so a link can point at it), a
187
- // testimonial has no page of its own worth linking to, and `quote`
188
- // — text/author/role, contract B's vocabulary block for exactly this
189
- // — is the honest fit. The `testimonial` collection stays real and
190
- // editable in the admin; these two mirror its demo rows.
191
- ...VITRINE_DEMO_TESTIMONIALS.map((demo, index) => ({
192
- _key: `demo-about-quote-${index + 1}`,
193
- _type: 'quote',
194
- _version: BLOCK_VERSION,
195
- text: demo.quote,
196
- author: demo.authorName,
197
- role: demo.authorRole,
198
- })),
351
+ [
352
+ 'Do we own everything once the project ships?',
353
+ 'Yes — the site, the brand files, the models and the source are yours outright. Northfield keeps no retainer requirement and no lock-in: change a phone number, swap a testimonial, or walk away entirely, all without calling us first.',
199
354
  ],
200
- },
201
- ];
355
+ [
356
+ 'Can we start with just one service?',
357
+ 'Most clients do. Brand strategy and web design & build are the two most common starting points, and roughly half of our clients come back for a second service once the first one has paid for itself.',
358
+ ],
359
+ [
360
+ 'How do you price a project?',
361
+ 'A fixed quote after a short discovery call, scoped to the outcome you actually need — never an open-ended hourly rate with no ceiling and no way to budget against it.',
362
+ ],
363
+ ];
364
+ return {
365
+ _key: 'demo-home-faq',
366
+ _type: 'faq',
367
+ _version: BLOCK_VERSION,
368
+ title: 'Questions we hear often',
369
+ items: items.map(([question, answer], index) => ({
370
+ _key: `demo-home-faq-${index}`,
371
+ question,
372
+ answer: richTextParagraph(`demo-home-faq-${index}-a`, answer),
373
+ })),
374
+ };
375
+ }
376
+ /** `VITRINE_DEMO_PAGES` — the fixed-shape alias every existing caller (and `blueprint-demo-blocks.test.ts`) used before L25's media-driven rewrite. Equivalent to `buildVitrineDemoPages({})`. */
377
+ export const VITRINE_DEMO_PAGES = buildVitrineDemoPages({});
202
378
  export const VITRINE_RECOMMENDED_AGENTS = [
203
379
  {
204
380
  name: 'seoAgent',
@@ -211,29 +387,128 @@ export const VITRINE_RECOMMENDED_AGENTS = [
211
387
  reason: 'Catches oversized hero media and third-party scripts that would slow the landing page down.',
212
388
  },
213
389
  ];
390
+ /**
391
+ * `vitrine`'s own starting skin (`starting-skins.js`) — asserted present
392
+ * with a real check, not a `!`, since `STARTING_SKINS` is keyed by
393
+ * blueprint id and TypeScript cannot see that this particular key is
394
+ * always populated.
395
+ */
396
+ function vitrinePalette() {
397
+ const skin = STARTING_SKINS.vitrine;
398
+ if (skin === undefined) {
399
+ // Same code `resolveBlueprint` (`registry.ts`) uses for its own
400
+ // "this cannot happen unless the registry itself is broken" guard —
401
+ // this is a bug in starting-skins.ts, never a user-facing condition.
402
+ throw new CogentaError({
403
+ code: 'BLUEPRINT_REGISTRY_CORRUPT',
404
+ message: 'STARTING_SKINS.vitrine is missing.',
405
+ hint: 'The "vitrine" entry must stay declared in starting-skins.ts for this blueprint to render its demo art.',
406
+ });
407
+ }
408
+ return skin.color;
409
+ }
410
+ /**
411
+ * Procedural visuals this blueprint seeds (L25): a flat geometric hero
412
+ * backdrop, five neutral client logos for the trust strip, one wide cover
413
+ * for the engagement-dashboard figure, one avatar per testimonial, and one
414
+ * cover photo per demo service — all from the same starting-skin palette
415
+ * (`starting-skins.js`) this blueprint already ships.
416
+ */
417
+ export const VITRINE_MEDIA_SPECS = [
418
+ {
419
+ name: 'hero',
420
+ spec: heroArt(vitrinePalette(), 'sun', 61),
421
+ alt: 'A consulting team at work',
422
+ photo: 'vitrine/hero.jpg',
423
+ },
424
+ {
425
+ name: 'shot',
426
+ spec: coverArt(vitrinePalette(), 62),
427
+ alt: 'The engagement dashboard',
428
+ photo: 'vitrine/dashboard.jpg',
429
+ },
430
+ ...[0, 1, 2, 3, 4].map((index) => ({
431
+ name: `logo-${index}`,
432
+ spec: logoArt(70 + index),
433
+ alt: `Client logo ${index + 1}`,
434
+ })),
435
+ ...VITRINE_DEMO_TESTIMONIALS.map((demo, index) => ({
436
+ name: `avatar-${index}`,
437
+ spec: avatarArt(vitrinePalette(), 80 + index),
438
+ alt: `Portrait of ${demo.authorName}`,
439
+ photo: `vitrine/avatar-${index + 1}.jpg`,
440
+ })),
441
+ ...VITRINE_DEMO_SERVICES.map((demo, index) => ({
442
+ name: `service-${demo.slug}`,
443
+ spec: coverArt(vitrinePalette(), 90 + index),
444
+ alt: `${demo.name} cover art`,
445
+ })),
446
+ ];
447
+ /** Header/footer navigation and the header call-to-action button (L25, D4). */
448
+ export const VITRINE_MENUS = {
449
+ header: [
450
+ { label: 'Services', url: '/services/brand-strategy' },
451
+ { label: 'About', url: '/about' },
452
+ { label: 'Case studies', url: '#' },
453
+ { label: 'Contact', url: '/contact' },
454
+ ],
455
+ footer: [
456
+ { label: 'Services', url: '/services/brand-strategy' },
457
+ { label: 'Company', url: '/about' },
458
+ { label: 'Legal', url: '#' },
459
+ ],
460
+ headerAction: { label: 'Get a quote', url: '/contact' },
461
+ };
462
+ export const VITRINE_SITE_SETTINGS = {
463
+ 'general.tagline': 'A consultancy that runs like software.',
464
+ 'general.socialLinks': [
465
+ { label: 'LinkedIn', url: 'https://linkedin.com/company/example' },
466
+ { label: 'X', url: 'https://x.com/example' },
467
+ { label: 'YouTube', url: 'https://youtube.com/@example' },
468
+ ],
469
+ 'general.footerNote': '1 Market Street, Suite 400, San Francisco, CA 94105',
470
+ };
214
471
  /**
215
472
  * Inserts the `vitrine` blueprint's demo content through the real
216
- * `ContentStore` — never mocked (house rule).
473
+ * `ContentStore` — never mocked (house rule). Services are created first so
474
+ * their real ids exist for the home page's `featureGrid` links and, via
475
+ * `media`, for their own `coverImage`.
217
476
  */
218
- async function seedVitrineDemoContent(db, defaultLocale, adminId) {
477
+ async function seedVitrineDemoContent(ctx) {
478
+ const { db, defaultLocale, adminId, media } = ctx;
219
479
  const serviceStore = createContentStore({ db, collection: service, defaultLocale });
220
480
  const testimonialStore = createContentStore({ db, collection: testimonial, defaultLocale });
221
481
  const pageStore = createContentStore({ db, collection: page, defaultLocale });
482
+ const serviceIdBySlug = new Map();
222
483
  for (const demo of VITRINE_DEMO_SERVICES) {
223
- await serviceStore.create({
484
+ const cover = media[`service-${demo.slug}`];
485
+ const created = await serviceStore.create({
224
486
  status: 'published',
225
487
  createdBy: adminId,
226
- values: { name: demo.name, slug: demo.slug, description: demo.description },
488
+ values: {
489
+ name: demo.name,
490
+ slug: demo.slug,
491
+ description: demo.description,
492
+ icon: demo.icon,
493
+ ...(cover === undefined ? {} : { coverImage: cover }),
494
+ },
227
495
  });
496
+ serviceIdBySlug.set(demo.slug, created.id);
228
497
  }
229
- for (const demo of VITRINE_DEMO_TESTIMONIALS) {
498
+ for (const [index, demo] of VITRINE_DEMO_TESTIMONIALS.entries()) {
499
+ const avatar = media[`avatar-${index}`];
230
500
  await testimonialStore.create({
231
501
  status: 'published',
232
502
  createdBy: adminId,
233
- values: { authorName: demo.authorName, authorRole: demo.authorRole, quote: demo.quote },
503
+ values: {
504
+ authorName: demo.authorName,
505
+ authorRole: demo.authorRole,
506
+ quote: demo.quote,
507
+ ...(avatar === undefined ? {} : { avatar }),
508
+ },
234
509
  });
235
510
  }
236
- for (const demo of VITRINE_DEMO_PAGES) {
511
+ for (const demo of buildVitrineDemoPages(media, serviceIdBySlug)) {
237
512
  await pageStore.create({
238
513
  status: 'published',
239
514
  createdBy: adminId,
@@ -246,5 +521,9 @@ export const vitrineContentPack = {
246
521
  collections: VITRINE_COLLECTIONS,
247
522
  recommendedAgents: VITRINE_RECOMMENDED_AGENTS,
248
523
  seedDemoContent: seedVitrineDemoContent,
524
+ defaultTheme: '@cogenta/theme-entreprise',
525
+ menus: VITRINE_MENUS,
526
+ siteSettings: VITRINE_SITE_SETTINGS,
527
+ mediaSpecs: VITRINE_MEDIA_SPECS,
249
528
  };
250
529
  //# sourceMappingURL=vitrine.js.map