create-cogenta 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/dist/blueprint-defaults.d.ts.map +1 -1
  2. package/dist/blueprint-defaults.js +6 -0
  3. package/dist/blueprint-defaults.js.map +1 -1
  4. package/dist/blueprints/assets/photos/association/community-cleanup.jpg +0 -0
  5. package/dist/blueprints/assets/photos/association/fundraising-dinner.jpg +0 -0
  6. package/dist/blueprints/assets/photos/association/garden-planting.jpg +0 -0
  7. package/dist/blueprints/assets/photos/association/harvest-food-drive.jpg +0 -0
  8. package/dist/blueprints/assets/photos/association/hero.jpg +0 -0
  9. package/dist/blueprints/assets/photos/association/volunteer-orientation.jpg +0 -0
  10. package/dist/blueprints/assets/photos/association/winter-coat-collection.jpg +0 -0
  11. package/dist/blueprints/assets/photos/blog/blog-runs-itself.jpg +0 -0
  12. package/dist/blueprints/assets/photos/blog/desk-setup.jpg +0 -0
  13. package/dist/blueprints/assets/photos/blog/editing.jpg +0 -0
  14. package/dist/blueprints/assets/photos/blog/hero.jpg +0 -0
  15. package/dist/blueprints/assets/photos/blog/notebooks.jpg +0 -0
  16. package/dist/blueprints/assets/photos/blog/plain-text-editor.jpg +0 -0
  17. package/dist/blueprints/assets/photos/blog/reading-nonfiction.jpg +0 -0
  18. package/dist/blueprints/assets/photos/blog/small-tool.jpg +0 -0
  19. package/dist/blueprints/assets/photos/blog/writing-schedule.jpg +0 -0
  20. package/dist/blueprints/assets/photos/magazine/business-2.jpg +0 -0
  21. package/dist/blueprints/assets/photos/magazine/business-3.jpg +0 -0
  22. package/dist/blueprints/assets/photos/magazine/business.jpg +0 -0
  23. package/dist/blueprints/assets/photos/magazine/culture-2.jpg +0 -0
  24. package/dist/blueprints/assets/photos/magazine/culture-3.jpg +0 -0
  25. package/dist/blueprints/assets/photos/magazine/culture.jpg +0 -0
  26. package/dist/blueprints/assets/photos/magazine/news-2.jpg +0 -0
  27. package/dist/blueprints/assets/photos/magazine/news-3.jpg +0 -0
  28. package/dist/blueprints/assets/photos/magazine/news.jpg +0 -0
  29. package/dist/blueprints/assets/photos/magazine/opinion-2.jpg +0 -0
  30. package/dist/blueprints/assets/photos/magazine/opinion-3.jpg +0 -0
  31. package/dist/blueprints/assets/photos/magazine/opinion.jpg +0 -0
  32. package/dist/blueprints/assets/photos/portfolio/adatum-publishing.jpg +0 -0
  33. package/dist/blueprints/assets/photos/portfolio/contoso-mobile-app.jpg +0 -0
  34. package/dist/blueprints/assets/photos/portfolio/fabrikam-annual-report.jpg +0 -0
  35. package/dist/blueprints/assets/photos/portfolio/hero.jpg +0 -0
  36. package/dist/blueprints/assets/photos/portfolio/litware-signage.jpg +0 -0
  37. package/dist/blueprints/assets/photos/portfolio/northwind-rebrand.jpg +0 -0
  38. package/dist/blueprints/assets/photos/portfolio/proseware-launch.jpg +0 -0
  39. package/dist/blueprints/assets/photos/portfolio/tailspin-streaming.jpg +0 -0
  40. package/dist/blueprints/assets/photos/portfolio/wingtip-wayfinding.jpg +0 -0
  41. package/dist/blueprints/assets/photos/restaurant/charred-octopus.jpg +0 -0
  42. package/dist/blueprints/assets/photos/restaurant/chocolate-tart.jpg +0 -0
  43. package/dist/blueprints/assets/photos/restaurant/creme-brulee.jpg +0 -0
  44. package/dist/blueprints/assets/photos/restaurant/hero.jpg +0 -0
  45. package/dist/blueprints/assets/photos/restaurant/house-red.jpg +0 -0
  46. package/dist/blueprints/assets/photos/restaurant/house-white.jpg +0 -0
  47. package/dist/blueprints/assets/photos/restaurant/pan-seared-trout.jpg +0 -0
  48. package/dist/blueprints/assets/photos/restaurant/poached-pear.jpg +0 -0
  49. package/dist/blueprints/assets/photos/restaurant/roasted-beet-salad.jpg +0 -0
  50. package/dist/blueprints/assets/photos/restaurant/slow-roast-duck-leg.jpg +0 -0
  51. package/dist/blueprints/assets/photos/restaurant/soup-of-the-day.jpg +0 -0
  52. package/dist/blueprints/assets/photos/restaurant/sparkling-water.jpg +0 -0
  53. package/dist/blueprints/assets/photos/restaurant/wild-mushroom-risotto.jpg +0 -0
  54. package/dist/blueprints/assets/photos/saas/avatar-1.jpg +0 -0
  55. package/dist/blueprints/assets/photos/saas/hero.jpg +0 -0
  56. package/dist/blueprints/assets/photos/store/camp-blanket.jpg +0 -0
  57. package/dist/blueprints/assets/photos/store/canvas-tote.jpg +0 -0
  58. package/dist/blueprints/assets/photos/store/cast-iron-skillet.jpg +0 -0
  59. package/dist/blueprints/assets/photos/store/ceramic-pour-over-set.jpg +0 -0
  60. package/dist/blueprints/assets/photos/store/enamel-mug.jpg +0 -0
  61. package/dist/blueprints/assets/photos/store/everyday-tee.jpg +0 -0
  62. package/dist/blueprints/assets/photos/store/field-jacket.jpg +0 -0
  63. package/dist/blueprints/assets/photos/store/hero.jpg +0 -0
  64. package/dist/blueprints/assets/photos/store/leather-card-holder.jpg +0 -0
  65. package/dist/blueprints/assets/photos/store/linen-table-runner.jpg +0 -0
  66. package/dist/blueprints/assets/photos/store/trail-tote.jpg +0 -0
  67. package/dist/blueprints/assets/photos/store/wool-beanie.jpg +0 -0
  68. package/dist/blueprints/assets/photos/store/wool-overshirt.jpg +0 -0
  69. package/dist/blueprints/assets/photos/vitrine/avatar-1.jpg +0 -0
  70. package/dist/blueprints/assets/photos/vitrine/avatar-2.jpg +0 -0
  71. package/dist/blueprints/assets/photos/vitrine/avatar-3.jpg +0 -0
  72. package/dist/blueprints/assets/photos/vitrine/dashboard.jpg +0 -0
  73. package/dist/blueprints/assets/photos/vitrine/hero.jpg +0 -0
  74. package/dist/blueprints/association.d.ts +93 -15
  75. package/dist/blueprints/association.d.ts.map +1 -1
  76. package/dist/blueprints/association.js +637 -130
  77. package/dist/blueprints/association.js.map +1 -1
  78. package/dist/blueprints/blog.d.ts +81 -94
  79. package/dist/blueprints/blog.d.ts.map +1 -1
  80. package/dist/blueprints/blog.js +513 -177
  81. package/dist/blueprints/blog.js.map +1 -1
  82. package/dist/blueprints/content-pack.d.ts +83 -2
  83. package/dist/blueprints/content-pack.d.ts.map +1 -1
  84. package/dist/blueprints/content-pack.js +54 -1
  85. package/dist/blueprints/content-pack.js.map +1 -1
  86. package/dist/blueprints/content-packs.d.ts +4 -3
  87. package/dist/blueprints/content-packs.d.ts.map +1 -1
  88. package/dist/blueprints/content-packs.js +6 -3
  89. package/dist/blueprints/content-packs.js.map +1 -1
  90. package/dist/blueprints/demo-media.d.ts +39 -0
  91. package/dist/blueprints/demo-media.d.ts.map +1 -0
  92. package/dist/blueprints/demo-media.js +30 -0
  93. package/dist/blueprints/demo-media.js.map +1 -0
  94. package/dist/blueprints/documentation.d.ts +78 -15
  95. package/dist/blueprints/documentation.d.ts.map +1 -1
  96. package/dist/blueprints/documentation.js +585 -104
  97. package/dist/blueprints/documentation.js.map +1 -1
  98. package/dist/blueprints/magazine.d.ts +81 -15
  99. package/dist/blueprints/magazine.d.ts.map +1 -1
  100. package/dist/blueprints/magazine.js +431 -85
  101. package/dist/blueprints/magazine.js.map +1 -1
  102. package/dist/blueprints/menus.d.ts +25 -0
  103. package/dist/blueprints/menus.d.ts.map +1 -0
  104. package/dist/blueprints/menus.js +33 -0
  105. package/dist/blueprints/menus.js.map +1 -0
  106. package/dist/blueprints/photo-assets.d.ts +3 -0
  107. package/dist/blueprints/photo-assets.d.ts.map +1 -0
  108. package/dist/blueprints/photo-assets.js +33 -0
  109. package/dist/blueprints/photo-assets.js.map +1 -0
  110. package/dist/blueprints/portfolio.d.ts +81 -13
  111. package/dist/blueprints/portfolio.d.ts.map +1 -1
  112. package/dist/blueprints/portfolio.js +605 -102
  113. package/dist/blueprints/portfolio.js.map +1 -1
  114. package/dist/blueprints/registry.d.ts +3 -1
  115. package/dist/blueprints/registry.d.ts.map +1 -1
  116. package/dist/blueprints/registry.js +4 -1
  117. package/dist/blueprints/registry.js.map +1 -1
  118. package/dist/blueprints/restaurant.d.ts +65 -20
  119. package/dist/blueprints/restaurant.d.ts.map +1 -1
  120. package/dist/blueprints/restaurant.js +350 -117
  121. package/dist/blueprints/restaurant.js.map +1 -1
  122. package/dist/blueprints/saas.d.ts +81 -14
  123. package/dist/blueprints/saas.d.ts.map +1 -1
  124. package/dist/blueprints/saas.js +469 -132
  125. package/dist/blueprints/saas.js.map +1 -1
  126. package/dist/blueprints/site-settings-seed.d.ts +17 -0
  127. package/dist/blueprints/site-settings-seed.d.ts.map +1 -0
  128. package/dist/blueprints/site-settings-seed.js +45 -0
  129. package/dist/blueprints/site-settings-seed.js.map +1 -0
  130. package/dist/blueprints/starting-skins.d.ts +19 -0
  131. package/dist/blueprints/starting-skins.d.ts.map +1 -0
  132. package/dist/blueprints/starting-skins.js +301 -0
  133. package/dist/blueprints/starting-skins.js.map +1 -0
  134. package/dist/blueprints/store.d.ts +173 -0
  135. package/dist/blueprints/store.d.ts.map +1 -0
  136. package/dist/blueprints/store.js +643 -0
  137. package/dist/blueprints/store.js.map +1 -0
  138. package/dist/blueprints/vitrine.d.ts +94 -16
  139. package/dist/blueprints/vitrine.d.ts.map +1 -1
  140. package/dist/blueprints/vitrine.js +421 -142
  141. package/dist/blueprints/vitrine.js.map +1 -1
  142. package/dist/demo-art/compositions.d.ts +48 -0
  143. package/dist/demo-art/compositions.d.ts.map +1 -0
  144. package/dist/demo-art/compositions.js +690 -0
  145. package/dist/demo-art/compositions.js.map +1 -0
  146. package/dist/demo-art/index.d.ts +8 -0
  147. package/dist/demo-art/index.d.ts.map +1 -0
  148. package/dist/demo-art/index.js +5 -0
  149. package/dist/demo-art/index.js.map +1 -0
  150. package/dist/demo-art/oklch.d.ts +39 -0
  151. package/dist/demo-art/oklch.d.ts.map +1 -0
  152. package/dist/demo-art/oklch.js +79 -0
  153. package/dist/demo-art/oklch.js.map +1 -0
  154. package/dist/demo-art/png.d.ts +24 -0
  155. package/dist/demo-art/png.d.ts.map +1 -0
  156. package/dist/demo-art/png.js +110 -0
  157. package/dist/demo-art/png.js.map +1 -0
  158. package/dist/demo-art/render.d.ts +228 -0
  159. package/dist/demo-art/render.d.ts.map +1 -0
  160. package/dist/demo-art/render.js +478 -0
  161. package/dist/demo-art/render.js.map +1 -0
  162. package/dist/playground-reset.d.ts.map +1 -1
  163. package/dist/playground-reset.js +22 -4
  164. package/dist/playground-reset.js.map +1 -1
  165. package/dist/prompts.d.ts +12 -0
  166. package/dist/prompts.d.ts.map +1 -1
  167. package/dist/prompts.js +32 -0
  168. package/dist/prompts.js.map +1 -1
  169. package/dist/recap.d.ts.map +1 -1
  170. package/dist/recap.js +6 -0
  171. package/dist/recap.js.map +1 -1
  172. package/dist/scaffold.d.ts +22 -2
  173. package/dist/scaffold.d.ts.map +1 -1
  174. package/dist/scaffold.js +185 -23
  175. package/dist/scaffold.js.map +1 -1
  176. package/dist/wizard.d.ts.map +1 -1
  177. package/dist/wizard.js +4 -1
  178. package/dist/wizard.js.map +1 -1
  179. package/package.json +14 -11
@@ -1,14 +1,22 @@
1
+ import { CogentaError } from '@cogenta/core';
1
2
  import { createContentStore, defineCollection, f, validateCollectionSet, } from '@cogenta/schema';
2
- import { definePageCollection, richTextParagraph, toBlockZoneEntry, } from './content-pack.js';
3
+ import { avatarArt, coverArt, heroArt, logoArt } from '../demo-art/compositions.js';
4
+ import { definePageCollection, richTextParagraph, SEO_FIELDS, toBlockZoneEntry, } from './content-pack.js';
5
+ import { STARTING_SKINS } from './starting-skins.js';
3
6
  /**
4
- * The `saas` blueprint's content model (L9 task 8, batch B): a marketing
7
+ * The `saas` blueprint's content model (L25, "templates pro"): a marketing
5
8
  * site for a software product — a `feature` collection routed so it can be
6
- * listed live on the home page, exactly like `vitrine`'s `service`.
7
- * Deliberately no `pricingPlan` collection or billing model: pricing tiers
8
- * on a real SaaS site are usually a handful of numbers with no independent
9
- * lifecycle (no author, no publish date, no individual page), so they are
10
- * page-authored content on the `pricing` page below rather than a second
11
- * collection invented to hold three rows.
9
+ * listed live and linked to from the home page, exactly like `vitrine`'s
10
+ * `service`. Deliberately no `pricingPlan` collection or billing model:
11
+ * pricing tiers on a real SaaS site are usually a handful of numbers with no
12
+ * independent lifecycle (no author, no publish date, no individual page), so
13
+ * they are page-authored content on the `pricing` page below rather than a
14
+ * second collection invented to hold three rows.
15
+ *
16
+ * `icon` (a symbol name `@cogenta/theme-kit`'s `renderIcon` recognises) and
17
+ * `coverImage` (contract D `theme@1.4`'s `entryImage`) are what let
18
+ * `theme-saas`'s `collectionList` show a feature the same way its
19
+ * `featureGrid` does — an icon chip first, a photo only as the fallback.
12
20
  */
13
21
  export const feature = defineCollection({
14
22
  name: 'feature',
@@ -18,6 +26,15 @@ export const feature = defineCollection({
18
26
  name: f.text({ required: true, max: 120 }),
19
27
  slug: f.slug({ from: 'name', unique: true }),
20
28
  description: f.text({ max: 300, multiline: true }),
29
+ icon: f.text({
30
+ max: 64,
31
+ admin: {
32
+ label: 'Icon',
33
+ help: 'One of the symbol names @cogenta/theme-kit recognises (e.g. "bolt", "shield", "chart", "users", "globe", "sparkles"). Left blank, the feature falls back to its cover image.',
34
+ },
35
+ }),
36
+ coverImage: f.media({ accept: ['image'] }),
37
+ ...SEO_FIELDS,
21
38
  },
22
39
  indexes: [['slug']],
23
40
  permissions: {
@@ -30,147 +47,360 @@ export const feature = defineCollection({
30
47
  export const page = definePageCollection('/:slug');
31
48
  export const SAAS_COLLECTIONS = [feature, page];
32
49
  validateCollectionSet(SAAS_COLLECTIONS);
50
+ /**
51
+ * Six real product capabilities, each with the icon `featureGrid` (home
52
+ * page) and this same entry's own `icon` field both point at — a
53
+ * `collectionList` of `feature` and the home page's `featureGrid` read as
54
+ * one system rather than two unrelated sections.
55
+ */
33
56
  export const SAAS_DEMO_FEATURES = [
34
57
  {
35
- name: 'Real-time collaboration',
36
- slug: 'real-time-collaboration',
37
- description: 'Everyone on the team sees the same state, updated the instant it changes.',
58
+ name: 'Workflow automation',
59
+ slug: 'workflow-automation',
60
+ description: 'Trigger multi-step approvals, notifications and handoffs the moment a status changes — no script to maintain, no cron job to babysit. Build the flow once in a visual editor, watch every run in real time, and route exceptions to a human without losing your place in the queue.',
61
+ icon: 'bolt',
62
+ },
63
+ {
64
+ name: 'Audit log',
65
+ slug: 'audit-log',
66
+ description: 'Every change is written once, in order, and never edited after the fact — the same ledger your own compliance review reads from. Filter by actor, resource or time window, and export the result as a signed record that satisfies an external auditor without a follow-up email.',
67
+ icon: 'shield',
68
+ },
69
+ {
70
+ name: 'Single sign-on',
71
+ slug: 'single-sign-on',
72
+ description: 'SAML and OIDC out of the box, provisioned through your identity provider in minutes, not a support ticket. An offboarded employee loses access everywhere in one step, not six, and group membership synced from your directory keeps roles correct without anyone updating them by hand.',
73
+ icon: 'users',
38
74
  },
39
75
  {
40
- name: 'One-click integrations',
41
- slug: 'one-click-integrations',
42
- description: 'Connect the tools already in use without a single line of code.',
76
+ name: 'Integrations',
77
+ slug: 'integrations',
78
+ description: 'Two-way sync with the tools already in the stack — calendar, chat, storage and the usual project trackers — plus outbound webhooks and a documented API for anything bespoke. Nothing here is a walled garden: what comes in stays in sync, and what goes out is never trapped behind a paywall tier.',
79
+ icon: 'globe',
43
80
  },
44
81
  {
45
- name: 'Audit trail built in',
46
- slug: 'audit-trail-built-in',
47
- description: 'Every change is logged, diffed and reversible nothing happens silently.',
82
+ name: 'Analytics',
83
+ slug: 'analytics',
84
+ description: 'Real usage numbers, not vanity counters: active seats this week, the features nobody opens, and the reports someone actually downloads. Every chart is a saved, shareable view, so the number quoted in a leadership meeting is the same one the team is already looking at.',
85
+ icon: 'chart',
86
+ },
87
+ {
88
+ name: 'API',
89
+ slug: 'api',
90
+ description: 'A typed, versioned REST API with a sandbox key on day one and rate limits generous enough for a real integration, not just a demo. Everything the product does in the browser, a script can do too — through the same permission model and the same audit trail behind it.',
91
+ icon: 'sparkles',
48
92
  },
49
93
  ];
50
94
  const BLOCK_VERSION = '1.0.0';
95
+ /**
96
+ * One `prose` block, whose `body` (contract B rich text) can hold more than
97
+ * one paragraph — `text` may be a single string (one paragraph, the
98
+ * original shape every call site but `about` still uses) or an array of
99
+ * strings (one `richTextParagraph` block per entry, concatenated). Real
100
+ * "about" copy reads as more than one paragraph; forcing it into one would
101
+ * have meant either a wall of run-on sentences or losing the paragraph
102
+ * break entirely.
103
+ */
51
104
  function proseParagraph(key, text) {
105
+ const paragraphs = typeof text === 'string' ? [text] : text;
52
106
  return {
53
107
  _key: key,
54
108
  _type: 'prose',
55
109
  _version: BLOCK_VERSION,
56
- body: [
57
- {
58
- _key: `${key}-block`,
59
- _type: 'block',
60
- style: 'normal',
61
- children: [{ _key: `${key}-span`, _type: 'span', text, marks: [] }],
62
- markDefs: [],
63
- },
64
- ],
110
+ body: paragraphs.flatMap((paragraph, index) => richTextParagraph(`${key}-body-${index}`, paragraph)),
65
111
  };
66
112
  }
67
113
  /**
68
- * `home` (hero + a live list of features + a cta to sign up) and `pricing`
69
- * (a short prose intro plus a static `stats` row the page-authored
70
- * numbers a pricing page usually leads with, not a full billing model).
114
+ * `home` (ten blocks, the Linear/Stripe/Vercel composition the L25 brief
115
+ * asks for: hero trust strip features product shot → numbers →
116
+ * social proof pricing questions final call to action), `pricing`
117
+ * (its own hero, the same pricing table, and a billing-specific FAQ), and
118
+ * `about` (a short mission statement plus a second, distinct voice of
119
+ * social proof).
120
+ *
121
+ * A function of `media`/`featureIds` (`SeedContext.media`, and the ids
122
+ * `seedSaasDemoContent` assigns its own features before this runs), not a
123
+ * static const: the hero's `media`, the product shot, the trust strip's
124
+ * logos and both testimonials' avatars all need ids only the scaffold
125
+ * knows at seed time, and the feature grid links to each feature's own
126
+ * real page.
71
127
  */
72
- export const SAAS_DEMO_PAGES = [
73
- {
74
- title: 'Home',
75
- slug: 'home',
76
- blocks: [
77
- {
78
- _key: 'demo-home-hero',
79
- _type: 'hero',
80
- _version: BLOCK_VERSION,
81
- eyebrow: 'SaaS',
82
- title: 'Ship faster, with less friction',
83
- subtitle: 'Scaffolded by create-cogenta from the "saas" blueprint, with real demo features already in place.',
84
- actions: [{ label: 'Start free trial', target: { href: '/pricing' }, emphasis: 'primary' }],
85
- },
86
- {
87
- _key: 'demo-home-features',
88
- _type: 'collectionList',
89
- _version: BLOCK_VERSION,
90
- title: 'Features',
91
- collection: 'feature',
92
- sort: { field: 'createdAt', direction: 'asc' },
93
- limit: 10,
94
- layout: 'grid',
95
- },
96
- {
97
- _key: 'demo-home-why',
98
- _type: 'featureGrid',
99
- _version: BLOCK_VERSION,
100
- title: 'Why teams move here',
101
- items: [
102
- {
103
- _key: 'demo-why-1',
104
- icon: 'bolt',
105
- title: 'Set up in an afternoon',
106
- text: 'Import what you already have, invite the team, and keep working. Nothing to migrate first.',
107
- },
108
- {
109
- _key: 'demo-why-2',
110
- icon: 'lock',
111
- title: 'Your data stays yours',
112
- text: 'A full export in one click, in a format something else can actually read. No lock-in by file format.',
128
+ export function buildSaasDemoPages(media, featureIdBySlug) {
129
+ const featureLink = (slug) => {
130
+ const id = featureIdBySlug.get(slug);
131
+ return id === undefined ? undefined : { collection: 'feature', id };
132
+ };
133
+ // `logoStrip` (blocks@2.0) requires at least one logo — a media map with
134
+ // no `logo-*` entries (a scaffold with no demo-art seeded, or this
135
+ // function called directly, as the blueprint test does) must therefore
136
+ // omit the whole block rather than emit an empty, contract-invalid one.
137
+ const logoItems = [0, 1, 2, 3, 4, 5]
138
+ .map((index) => media[`logo-${index}`])
139
+ .filter((id) => id !== undefined)
140
+ .map((id, index) => ({ _key: `demo-logo-${index}`, media: id }));
141
+ return [
142
+ {
143
+ title: 'Home',
144
+ slug: 'home',
145
+ blocks: [
146
+ {
147
+ _key: 'demo-home-hero',
148
+ _type: 'hero',
149
+ _version: BLOCK_VERSION,
150
+ eyebrow: 'Now in public beta',
151
+ title: 'Ship faster, with less friction',
152
+ subtitle: 'One workspace for the whole team to plan, automate and ship, without the pile of spreadsheets and shared inboxes it usually takes to get there. This page and the six features below it were scaffolded by create-cogenta from the "saas" blueprint — real demo content, ready to edit from the first run.',
153
+ ...(media.hero === undefined ? {} : { media: media.hero }),
154
+ actions: [
155
+ { label: 'Start free', target: { href: '/pricing' }, emphasis: 'primary' },
156
+ { label: 'Book a demo', target: { href: '/about' } },
157
+ ],
158
+ },
159
+ ...(logoItems.length === 0
160
+ ? []
161
+ : [
162
+ {
163
+ _key: 'demo-home-logos',
164
+ _type: 'logoStrip',
165
+ _version: BLOCK_VERSION,
166
+ logos: logoItems,
167
+ caption: 'Trusted by teams at',
168
+ },
169
+ ]),
170
+ {
171
+ _key: 'demo-home-features',
172
+ _type: 'featureGrid',
173
+ _version: BLOCK_VERSION,
174
+ title: 'Everything the team needs to ship',
175
+ items: SAAS_DEMO_FEATURES.map((demo, index) => {
176
+ const link = featureLink(demo.slug);
177
+ return {
178
+ _key: `demo-feature-${index}`,
179
+ icon: demo.icon,
180
+ title: demo.name,
181
+ text: demo.description,
182
+ ...(link === undefined ? {} : { link }),
183
+ };
184
+ }),
185
+ },
186
+ // `mediaFigure.media` (blocks@2.0) is required — with no `product`
187
+ // media seeded, the block is omitted rather than emitted invalid
188
+ // (same reasoning as `logoStrip` above).
189
+ ...(media.product === undefined
190
+ ? []
191
+ : [
192
+ {
193
+ _key: 'demo-home-shot',
194
+ _type: 'mediaFigure',
195
+ _version: BLOCK_VERSION,
196
+ media: media.product,
197
+ caption: 'The board view, live for the whole team.',
198
+ ratio: '16:9',
199
+ align: 'wide',
200
+ },
201
+ ]),
202
+ {
203
+ _key: 'demo-home-stats',
204
+ _type: 'statCounter',
205
+ _version: BLOCK_VERSION,
206
+ title: 'Trusted at scale',
207
+ stats: [
208
+ { _key: 'demo-stat-1', value: '99.98', label: 'Uptime, trailing 12 months' },
209
+ { _key: 'demo-stat-2', value: '4.8', label: 'Average review score' },
210
+ { _key: 'demo-stat-3', value: '2,400+', label: 'Teams onboarded' },
211
+ { _key: 'demo-stat-4', value: '<200ms', label: 'Median API response' },
212
+ ],
213
+ },
214
+ {
215
+ _key: 'demo-home-testimonial',
216
+ _type: 'testimonial',
217
+ _version: BLOCK_VERSION,
218
+ quote: richTextParagraph('demo-testimonial-quote', 'We replaced four spreadsheets and a shared inbox with one workspace. Approvals that used to take a week now close the same afternoon, and for the first time our finance team can see exactly where a request is stuck without pinging three people to ask.'),
219
+ attribution: {
220
+ name: 'Priya Nandakumar',
221
+ role: 'Head of Operations, Kelso Freight',
222
+ ...(media.avatar === undefined ? {} : { avatar: media.avatar }),
113
223
  },
114
- {
115
- _key: 'demo-why-3',
116
- icon: 'chart',
117
- title: 'Priced by what you use',
118
- text: 'One plan, billed on active seats. Nobody pays for the colleague who logs in twice a year.',
224
+ },
225
+ {
226
+ _key: 'demo-home-quote',
227
+ _type: 'quote',
228
+ _version: BLOCK_VERSION,
229
+ text: 'The audit log alone is why our compliance review took an afternoon instead of a month. Every question the auditor asked, we answered by filtering one screen instead of hunting through six systems for a paper trail that may or may not have existed.',
230
+ author: 'Marcus Webb',
231
+ role: 'IT Director, Fenwick & Rowe',
232
+ },
233
+ {
234
+ _key: 'demo-home-pricing',
235
+ _type: 'pricingTable',
236
+ _version: BLOCK_VERSION,
237
+ title: 'Simple, seat-based pricing',
238
+ tiers: pricingTiers(),
239
+ },
240
+ homeFaq(),
241
+ {
242
+ _key: 'demo-home-cta',
243
+ _type: 'cta',
244
+ _version: BLOCK_VERSION,
245
+ title: 'Start building today',
246
+ text: 'No credit card required for the trial. Every section above is normal editable content.',
247
+ actions: [
248
+ { label: 'Start free', target: { href: '/pricing' }, emphasis: 'primary' },
249
+ { label: 'Book a demo', target: { href: '/about' } },
250
+ ],
251
+ },
252
+ ],
253
+ },
254
+ {
255
+ title: 'Pricing',
256
+ slug: 'pricing',
257
+ blocks: [
258
+ {
259
+ _key: 'demo-pricing-hero',
260
+ _type: 'hero',
261
+ _version: BLOCK_VERSION,
262
+ eyebrow: 'Pricing',
263
+ title: 'One plan for every team, billed on active seats',
264
+ subtitle: 'No setup fee, no annual lock-in required, and a real person to talk to before you commit to Enterprise. Every plan includes the full feature set below — Pro adds SSO and priority support, and moving between tiers takes effect immediately, prorated to the day.',
265
+ actions: [{ label: 'Start free', target: { href: '#' }, emphasis: 'primary' }],
266
+ },
267
+ {
268
+ _key: 'demo-pricing-table',
269
+ _type: 'pricingTable',
270
+ _version: BLOCK_VERSION,
271
+ tiers: pricingTiers(),
272
+ },
273
+ {
274
+ _key: 'demo-pricing-faq',
275
+ _type: 'faq',
276
+ _version: BLOCK_VERSION,
277
+ title: 'Questions about billing',
278
+ items: [
279
+ {
280
+ _key: 'demo-pricing-faq-1',
281
+ question: 'What happens when the trial ends?',
282
+ answer: richTextParagraph('demo-pricing-faq-1-a', 'The workspace goes read-only rather than being deleted. Everything is still there, and still exportable, whether you subscribe that week or six months later — nothing is purged on a timer.'),
283
+ },
284
+ {
285
+ _key: 'demo-pricing-faq-2',
286
+ question: 'Do you charge for people who barely log in?',
287
+ answer: richTextParagraph('demo-pricing-faq-2-a', 'No. A seat counts in a month only if it was actually used that month, and the invoice itemises exactly which ones did, so finance never has to take our word for it.'),
288
+ },
289
+ {
290
+ _key: 'demo-pricing-faq-3',
291
+ question: 'Can we pay by invoice instead of card?',
292
+ answer: richTextParagraph('demo-pricing-faq-3-a', 'From five seats up, billed yearly, on thirty-day terms with a PO number on the invoice if you need one. Below that the card flow costs everyone less than the paperwork would.'),
293
+ },
294
+ {
295
+ _key: 'demo-pricing-faq-4',
296
+ question: 'Can we change plans mid-cycle?',
297
+ answer: richTextParagraph('demo-pricing-faq-4-a', 'Yes, immediately, with a prorated charge or credit for the days remaining, shown on the invoice before it is charged — never a silent rollover to next month.'),
298
+ },
299
+ ],
300
+ },
301
+ ],
302
+ },
303
+ {
304
+ title: 'About',
305
+ slug: 'about',
306
+ blocks: [
307
+ proseParagraph('demo-about-prose', [
308
+ 'We started building this after watching a team lose a week to an approval that should have taken an hour — not because anyone was slow, but because the request lived in an inbox, the sign-off lived in a spreadsheet, and nobody could see both at once. So we built one workspace where the request, the approval and the record of what happened are the same object, not three.',
309
+ 'This is a demo SaaS site, scaffolded by create-cogenta from the "saas" blueprint. Its features and this page were seeded by the installer so there is real content to look at from the first run — every word of it is normal, editable content, not placeholder copy waiting to be swapped out.',
310
+ ]),
311
+ {
312
+ _key: 'demo-about-testimonial',
313
+ _type: 'testimonial',
314
+ _version: BLOCK_VERSION,
315
+ quote: richTextParagraph('demo-about-testimonial-quote', 'Support answered inside the hour, on a Sunday, before we’d even finished writing up the incident. Three months later the same person who took that first ticket is still the one who replies — no hand-off, no repeating the whole story from scratch.'),
316
+ attribution: {
317
+ name: 'Dana Osei',
318
+ role: 'Engineering Manager, Vaultline',
319
+ ...(media['avatar-about'] === undefined ? {} : { avatar: media['avatar-about'] }),
119
320
  },
120
- ],
121
- },
122
- {
123
- _key: 'demo-home-cta',
124
- _type: 'cta',
125
- _version: BLOCK_VERSION,
126
- title: 'Ready to try it?',
127
- text: 'No credit card required for the trial. Every feature above is normal editable content.',
128
- actions: [{ label: 'See pricing', target: { href: '/pricing' }, emphasis: 'primary' }],
129
- },
321
+ },
322
+ ],
323
+ },
324
+ ];
325
+ }
326
+ function pricingTiers() {
327
+ return [
328
+ {
329
+ _key: 'demo-tier-starter',
330
+ name: 'Starter',
331
+ price: '$0',
332
+ interval: '/month',
333
+ features: ['Up to 5 seats', 'Core workflows', 'Community support'],
334
+ action: { label: 'Start free', target: { href: '#' } },
335
+ },
336
+ {
337
+ _key: 'demo-tier-pro',
338
+ name: 'Pro',
339
+ price: '$24',
340
+ interval: '/seat/month',
341
+ features: [
342
+ 'Unlimited seats',
343
+ 'Workflow automation',
344
+ 'SSO (SAML & OIDC)',
345
+ 'Full audit log',
346
+ 'Priority support',
347
+ ],
348
+ action: { label: 'Start free', target: { href: '#' }, emphasis: 'primary' },
349
+ highlighted: true,
350
+ },
351
+ {
352
+ _key: 'demo-tier-enterprise',
353
+ name: 'Enterprise',
354
+ price: 'Custom',
355
+ features: [
356
+ 'Everything in Pro',
357
+ 'Dedicated onboarding',
358
+ 'Custom contract & invoicing',
359
+ 'A named account manager',
360
+ ],
361
+ action: { label: 'Talk to sales', target: { href: '/about' } },
362
+ },
363
+ ];
364
+ }
365
+ function homeFaq() {
366
+ const items = [
367
+ [
368
+ 'How long does it take to get started?',
369
+ 'Most teams are running real workflows the same afternoon they sign up — there is no migration step to complete first. Import a spreadsheet, invite the team, and the first approval can go out before lunch.',
130
370
  ],
131
- },
132
- {
133
- title: 'Pricing',
134
- slug: 'pricing',
135
- blocks: [
136
- proseParagraph('demo-pricing-prose', 'This is a demo SaaS site, scaffolded by create-cogenta from the "saas" blueprint. Its features and this page were seeded by the installer so there is real content to look at from the first run.'),
137
- {
138
- _key: 'demo-pricing-stats',
139
- _type: 'stats',
140
- _version: BLOCK_VERSION,
141
- title: 'Trusted at scale',
142
- items: [
143
- { _key: 'demo-stat-1', value: '4.8', unit: '/5', label: 'average rating' },
144
- { _key: 'demo-stat-2', value: '99.9', unit: '%', label: 'uptime' },
145
- { _key: 'demo-stat-3', value: '2,400+', label: 'teams onboarded' },
146
- ],
147
- },
148
- {
149
- _key: 'demo-pricing-faq',
150
- _type: 'faq',
151
- _version: BLOCK_VERSION,
152
- title: 'Questions about billing',
153
- items: [
154
- {
155
- _key: 'demo-pricing-faq-1',
156
- question: 'What happens when the trial ends?',
157
- answer: richTextParagraph('demo-pricing-faq-1-a', 'The workspace goes read-only rather than being deleted. Everything is still there, and still exportable, whether you subscribe that week or six months later.'),
158
- },
159
- {
160
- _key: 'demo-pricing-faq-2',
161
- question: 'Do you charge for people who barely log in?',
162
- answer: richTextParagraph('demo-pricing-faq-2-a', 'No. A seat counts in a month only if it was actually used that month, and the invoice shows which ones did.'),
163
- },
164
- {
165
- _key: 'demo-pricing-faq-3',
166
- question: 'Can we pay by invoice instead of card?',
167
- answer: richTextParagraph('demo-pricing-faq-3-a', 'From five seats up, yearly, on thirty-day terms. Below that the card flow costs everyone less than the paperwork would.'),
168
- },
169
- ],
170
- },
371
+ [
372
+ 'Does it work with the tools we already use?',
373
+ 'Yes — two-way sync with the usual suspects, plus outbound webhooks and a documented API for anything bespoke. If it has an API, it can talk to this one; if it does not, the webhook still fires.',
171
374
  ],
172
- },
173
- ];
375
+ [
376
+ 'Is our data ours if we leave?',
377
+ 'A full export, in a format something else can actually read, is one click away at any time — there is no lock-in by file format, no support ticket required, and no waiting period.',
378
+ ],
379
+ [
380
+ 'How is data secured in transit and at rest?',
381
+ 'Everything is encrypted in transit and at rest, and every access is written to the audit log described above. Backups are tested by restoring them, not just taking them, on a schedule a security review can ask to see.',
382
+ ],
383
+ [
384
+ 'Can we bring our own identity provider?',
385
+ 'Yes, SAML and OIDC are supported on every paid plan, provisioned in minutes from the settings screen — no support ticket, no waiting on a callback from us.',
386
+ ],
387
+ [
388
+ 'What kind of support do we get?',
389
+ 'Every plan includes email support with a same-day response on business days; Pro and Enterprise add priority response times, a named contact, and a shared channel for anything urgent.',
390
+ ],
391
+ ];
392
+ return {
393
+ _key: 'demo-home-faq',
394
+ _type: 'faq',
395
+ _version: BLOCK_VERSION,
396
+ title: 'Frequently asked questions',
397
+ items: items.map(([question, answer], index) => ({
398
+ _key: `demo-home-faq-${index}`,
399
+ question,
400
+ answer: richTextParagraph(`demo-home-faq-${index}-a`, answer),
401
+ })),
402
+ };
403
+ }
174
404
  export const SAAS_RECOMMENDED_AGENTS = [
175
405
  {
176
406
  name: 'performanceAgent',
@@ -183,21 +413,124 @@ export const SAAS_RECOMMENDED_AGENTS = [
183
413
  reason: 'Tracks dependency vulnerabilities — the kind of thing a SaaS buyer asks about first.',
184
414
  },
185
415
  ];
416
+ /**
417
+ * `saas`'s own starting skin (`starting-skins.js`) — asserted present with a
418
+ * real check, not a `!`, since `STARTING_SKINS` is keyed by blueprint id and
419
+ * TypeScript cannot see that this particular key is always populated.
420
+ */
421
+ function saasPalette() {
422
+ const skin = STARTING_SKINS.saas;
423
+ if (skin === undefined) {
424
+ // Same code `resolveBlueprint` (`registry.ts`) uses for its own
425
+ // "this cannot happen unless the registry itself is broken" guard — this
426
+ // is a bug in starting-skins.ts, never a user-facing condition.
427
+ throw new CogentaError({
428
+ code: 'BLUEPRINT_REGISTRY_CORRUPT',
429
+ message: 'STARTING_SKINS.saas is missing.',
430
+ hint: 'The "saas" entry must stay declared in starting-skins.ts for this blueprint to render its demo art.',
431
+ });
432
+ }
433
+ return skin.color;
434
+ }
435
+ /**
436
+ * Procedural visuals this blueprint seeds (L25): a mesh-gradient hero
437
+ * backdrop, a wide product screenshot stand-in, six neutral client logos
438
+ * for the trust strip, one avatar per testimonial (L26: the "about" page's
439
+ * testimonial went unillustrated before this — a second, distinct avatar
440
+ * fixes that rather than reusing the home page's), and one cover photo per
441
+ * demo feature — all from the same starting-skin palette
442
+ * (`starting-skins.js`) this blueprint already ships.
443
+ */
444
+ export const SAAS_MEDIA_SPECS = [
445
+ {
446
+ name: 'hero',
447
+ spec: heroArt(saasPalette(), 'mesh', 21),
448
+ alt: 'A software team at work',
449
+ photo: 'saas/hero.jpg',
450
+ },
451
+ {
452
+ name: 'product',
453
+ spec: coverArt(saasPalette(), 22),
454
+ alt: 'Abstract product visual standing in for a screenshot of the workspace',
455
+ },
456
+ {
457
+ name: 'avatar',
458
+ spec: avatarArt(saasPalette(), 23),
459
+ alt: 'Portrait of the testimonial’s author',
460
+ photo: 'saas/avatar-1.jpg',
461
+ },
462
+ {
463
+ name: 'avatar-about',
464
+ // No second bundled photo exists for this blueprint (only
465
+ // `hero.jpg`/`avatar-1.jpg`, see `assets/photos/saas/`) — reusing
466
+ // `avatar-1.jpg` under a different name would put the same face on two
467
+ // different people, which is worse than an honest procedural portrait.
468
+ spec: avatarArt(saasPalette(), 24),
469
+ alt: 'Portrait of the "about" page testimonial’s author',
470
+ },
471
+ ...[0, 1, 2, 3, 4, 5].map((index) => ({
472
+ name: `logo-${index}`,
473
+ spec: logoArt(30 + index),
474
+ alt: `Client logo ${index + 1}`,
475
+ })),
476
+ ...SAAS_DEMO_FEATURES.map((demo, index) => ({
477
+ name: `feature-${demo.slug}`,
478
+ spec: coverArt(saasPalette(), 40 + index),
479
+ alt: `${demo.name} cover art`,
480
+ })),
481
+ ];
482
+ /** Header/footer navigation and the header call-to-action button (L25, D4). */
483
+ export const SAAS_MENUS = {
484
+ header: [
485
+ { label: 'Product', url: '#' },
486
+ { label: 'Pricing', url: '/pricing' },
487
+ { label: 'Docs', url: '#' },
488
+ { label: 'Blog', url: '#' },
489
+ { label: 'Company', url: '/about' },
490
+ ],
491
+ footer: [
492
+ { label: 'Product', url: '#' },
493
+ { label: 'Company', url: '/about' },
494
+ { label: 'Legal', url: '#' },
495
+ ],
496
+ headerAction: { label: 'Start free', url: '/pricing' },
497
+ };
498
+ export const SAAS_SITE_SETTINGS = {
499
+ 'general.tagline': 'Ship faster, with less friction.',
500
+ 'general.socialLinks': [
501
+ { label: 'X', url: 'https://x.com/example' },
502
+ { label: 'GitHub', url: 'https://github.com/example' },
503
+ { label: 'LinkedIn', url: 'https://linkedin.com/company/example' },
504
+ ],
505
+ 'general.footerNote': 'A demo SaaS site, scaffolded by create-cogenta.',
506
+ };
186
507
  /**
187
508
  * Inserts the `saas` blueprint's demo content through the real
188
- * `ContentStore` — never mocked (house rule).
509
+ * `ContentStore` — never mocked (house rule). Features are created first so
510
+ * their real ids exist for the home page's `featureGrid` links and, via
511
+ * `media`, for their own `coverImage`.
189
512
  */
190
- async function seedSaasDemoContent(db, defaultLocale, adminId) {
513
+ async function seedSaasDemoContent(ctx) {
514
+ const { db, defaultLocale, adminId, media } = ctx;
191
515
  const featureStore = createContentStore({ db, collection: feature, defaultLocale });
192
516
  const pageStore = createContentStore({ db, collection: page, defaultLocale });
517
+ const featureIdBySlug = new Map();
193
518
  for (const demo of SAAS_DEMO_FEATURES) {
194
- await featureStore.create({
519
+ const cover = media[`feature-${demo.slug}`];
520
+ const created = await featureStore.create({
195
521
  status: 'published',
196
522
  createdBy: adminId,
197
- values: { name: demo.name, slug: demo.slug, description: demo.description },
523
+ values: {
524
+ name: demo.name,
525
+ slug: demo.slug,
526
+ description: demo.description,
527
+ icon: demo.icon,
528
+ ...(cover === undefined ? {} : { coverImage: cover }),
529
+ },
198
530
  });
531
+ featureIdBySlug.set(demo.slug, created.id);
199
532
  }
200
- for (const demo of SAAS_DEMO_PAGES) {
533
+ for (const demo of buildSaasDemoPages(media, featureIdBySlug)) {
201
534
  await pageStore.create({
202
535
  status: 'published',
203
536
  createdBy: adminId,
@@ -210,5 +543,9 @@ export const saasContentPack = {
210
543
  collections: SAAS_COLLECTIONS,
211
544
  recommendedAgents: SAAS_RECOMMENDED_AGENTS,
212
545
  seedDemoContent: seedSaasDemoContent,
546
+ defaultTheme: '@cogenta/theme-saas',
547
+ menus: SAAS_MENUS,
548
+ siteSettings: SAAS_SITE_SETTINGS,
549
+ mediaSpecs: SAAS_MEDIA_SPECS,
213
550
  };
214
551
  //# sourceMappingURL=saas.js.map