@neatico/visual-core 0.14.3 → 0.14.64

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 (68) hide show
  1. package/dist/candidateRanking.d.ts +31 -0
  2. package/dist/candidateRanking.js +55 -0
  3. package/dist/cjs/candidateRanking.js +61 -0
  4. package/dist/cjs/composition.js +62 -36
  5. package/dist/cjs/compositionNumbers.js +111 -0
  6. package/dist/cjs/compositionScenes.js +91 -38
  7. package/dist/cjs/compositionUseCases.js +145 -0
  8. package/dist/cjs/content.js +574 -120
  9. package/dist/cjs/contentBrief.js +82 -0
  10. package/dist/cjs/designBlueprint.js +94 -19
  11. package/dist/cjs/designSystem.js +536 -77
  12. package/dist/cjs/editorial.js +76 -20
  13. package/dist/cjs/fontCatalog.js +81 -23
  14. package/dist/cjs/formalCompositions.js +120 -0
  15. package/dist/cjs/index.js +13 -0
  16. package/dist/cjs/layoutContracts.js +57 -11
  17. package/dist/cjs/layouts.js +5 -92
  18. package/dist/cjs/nativeSocialParameters.js +145 -0
  19. package/dist/cjs/pagination.js +27 -7
  20. package/dist/cjs/parameterDefaults.js +20 -0
  21. package/dist/cjs/recipeParameters.js +304 -0
  22. package/dist/cjs/socialLayouts.js +127 -387
  23. package/dist/cjs/styleManifest.js +107 -0
  24. package/dist/cjs/text.js +12 -2
  25. package/dist/cjs/textPresentation.js +68 -0
  26. package/dist/composition.d.ts +4 -4
  27. package/dist/composition.js +62 -36
  28. package/dist/compositionNumbers.d.ts +4 -0
  29. package/dist/compositionNumbers.js +108 -0
  30. package/dist/compositionScenes.d.ts +2 -2
  31. package/dist/compositionScenes.js +91 -38
  32. package/dist/compositionUseCases.d.ts +1 -0
  33. package/dist/compositionUseCases.js +142 -0
  34. package/dist/content.d.ts +65 -20
  35. package/dist/content.js +575 -121
  36. package/dist/contentBrief.d.ts +16 -0
  37. package/dist/contentBrief.js +78 -0
  38. package/dist/designBlueprint.d.ts +4 -4
  39. package/dist/designBlueprint.js +95 -20
  40. package/dist/designSystem.d.ts +11 -7
  41. package/dist/designSystem.js +537 -79
  42. package/dist/editorial.d.ts +1 -1
  43. package/dist/editorial.js +77 -21
  44. package/dist/fontCatalog.d.ts +1 -1
  45. package/dist/fontCatalog.js +81 -23
  46. package/dist/formalCompositions.d.ts +26 -0
  47. package/dist/formalCompositions.js +116 -0
  48. package/dist/index.d.ts +10 -0
  49. package/dist/index.js +10 -0
  50. package/dist/layoutContracts.d.ts +3 -3
  51. package/dist/layoutContracts.js +58 -12
  52. package/dist/layouts.d.ts +2 -2
  53. package/dist/layouts.js +5 -92
  54. package/dist/nativeSocialParameters.d.ts +38 -0
  55. package/dist/nativeSocialParameters.js +139 -0
  56. package/dist/pagination.js +27 -7
  57. package/dist/parameterDefaults.d.ts +2 -0
  58. package/dist/parameterDefaults.js +17 -0
  59. package/dist/recipeParameters.d.ts +34 -0
  60. package/dist/recipeParameters.js +300 -0
  61. package/dist/socialLayouts.d.ts +40 -232
  62. package/dist/socialLayouts.js +127 -387
  63. package/dist/styleManifest.d.ts +20 -0
  64. package/dist/styleManifest.js +103 -0
  65. package/dist/text.js +12 -2
  66. package/dist/textPresentation.d.ts +29 -0
  67. package/dist/textPresentation.js +64 -0
  68. package/package.json +1 -1
@@ -1,126 +1,585 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DESIGN_RECIPES = exports.DESIGN_THEMES = void 0;
3
+ exports.DESIGN_RECIPES = exports.MANUAL_COMPOSITION_REASONS = exports.DESIGN_THEMES = void 0;
4
4
  exports.designTheme = designTheme;
5
+ exports.canonicalRecipeId = canonicalRecipeId;
5
6
  exports.pageEntryCount = pageEntryCount;
6
7
  exports.designCandidates = designCandidates;
7
8
  exports.structureFingerprint = structureFingerprint;
8
9
  const compositionScenes_js_1 = require("./compositionScenes.js");
9
- const layouts_js_1 = require("./layouts.js");
10
10
  const socialLayouts_js_1 = require("./socialLayouts.js");
11
11
  const content_js_1 = require("./content.js");
12
12
  /** Complete, deterministic token sets; decorative geometry belongs to individual compositions. */
13
13
  exports.DESIGN_THEMES = [
14
- { id: 'studio', palette: { background: '#F5F3EE', surface: '#FFFFFF', ink: '#20251F', muted: '#5F685C', accent: '#376346', line: '#D7DED3' }, typography: { heading: 'Inter', body: 'Inter', code: 'monospace', headingWeight: 700 }, geometry: { radius: 4, spacing: 1, rule: 'hairline' } },
15
- { id: 'ink', palette: { background: '#121B29', surface: '#1D2B3E', ink: '#F5F7FB', muted: '#B3C2D6', accent: '#85C8F3', line: '#3D526C' }, typography: { heading: 'Inter', body: 'Inter', code: 'monospace', headingWeight: 700 }, geometry: { radius: 12, spacing: 1, rule: 'accent' } },
16
- { id: 'editorial', palette: { background: '#FCF8F1', surface: '#FFFFFF', ink: '#30281F', muted: '#756657', accent: '#A74328', line: '#DED2C1' }, typography: { heading: 'Georgia', body: 'Inter', code: 'monospace', headingWeight: 700 }, geometry: { radius: 0, spacing: 1.1, rule: 'hairline' } },
17
- { id: 'electric', palette: { background: '#F3F4FF', surface: '#FFFFFF', ink: '#222047', muted: '#615D7B', accent: '#5340D9', line: '#DAD5F2' }, typography: { heading: 'Inter', body: 'Inter', code: 'monospace', headingWeight: 800 }, geometry: { radius: 16, spacing: 1, rule: 'none' } },
18
- { id: 'signal', palette: { background: '#FFE04B', surface: '#FFF5B7', ink: '#242418', muted: '#535039', accent: '#272B21', line: '#B8A233' }, typography: { heading: 'Inter', body: 'Inter', code: 'monospace', headingWeight: 800 }, geometry: { radius: 2, spacing: 1.05, rule: 'none' } },
14
+ {
15
+ id: "neatico",
16
+ palette: { background: "#FAF9F6", surface: "#FFFFFF", ink: "#202124", muted: "#62646B", accent: "#F4CE36", line: "#E4E1D9" },
17
+ typography: { heading: "Manrope", body: "Inter", code: "monospace", headingWeight: 700 },
18
+ geometry: { radius: 12, spacing: 1, rule: "accent" },
19
+ },
20
+ {
21
+ id: "archive",
22
+ palette: { background: "#F6F1E7", surface: "#FFFCF5", ink: "#19382E", muted: "#59665B", accent: "#C17A35", line: "#D7CEBC" },
23
+ typography: { heading: "Instrument Serif", body: "DM Sans", code: "JetBrains Mono", headingWeight: 400 },
24
+ geometry: { radius: 4, spacing: 1.1, rule: "hairline" },
25
+ },
26
+ {
27
+ "id": "bloom",
28
+ "palette": {
29
+ "background": "#FFF1F6",
30
+ "surface": "#FFFFFF",
31
+ "ink": "#341838",
32
+ "muted": "#70536D",
33
+ "accent": "#CC1D61",
34
+ "line": "#F3BED5"
35
+ },
36
+ "typography": {
37
+ "heading": "Montserrat",
38
+ "body": "Nunito Sans",
39
+ "code": "JetBrains Mono",
40
+ "headingWeight": 800
41
+ },
42
+ "geometry": {
43
+ "radius": 20,
44
+ "spacing": 1,
45
+ "rule": "accent"
46
+ }
47
+ },
48
+ {
49
+ "id": "splash",
50
+ "palette": {
51
+ "background": "#E9FAFF",
52
+ "surface": "#FFFFFF",
53
+ "ink": "#162E60",
54
+ "muted": "#4D637E",
55
+ "accent": "#2454E6",
56
+ "line": "#B8DEFA"
57
+ },
58
+ "typography": {
59
+ "heading": "Space Grotesk",
60
+ "body": "DM Sans",
61
+ "code": "JetBrains Mono",
62
+ "headingWeight": 700
63
+ },
64
+ "geometry": {
65
+ "radius": 20,
66
+ "spacing": 1,
67
+ "rule": "accent"
68
+ }
69
+ },
70
+ {
71
+ "id": "candy",
72
+ "palette": {
73
+ "background": "#F3EDFF",
74
+ "surface": "#FFFFFF",
75
+ "ink": "#35205C",
76
+ "muted": "#69557F",
77
+ "accent": "#7841D5",
78
+ "line": "#DCCAF6"
79
+ },
80
+ "typography": {
81
+ "heading": "Manrope",
82
+ "body": "Nunito Sans",
83
+ "code": "JetBrains Mono",
84
+ "headingWeight": 800
85
+ },
86
+ "geometry": {
87
+ "radius": 20,
88
+ "spacing": 1,
89
+ "rule": "accent"
90
+ }
91
+ },
92
+ {
93
+ id: "studio",
94
+ palette: {
95
+ background: "#F5F3EE",
96
+ surface: "#FFFFFF",
97
+ ink: "#20251F",
98
+ muted: "#5F685C",
99
+ accent: "#376346",
100
+ line: "#D7DED3",
101
+ },
102
+ typography: {
103
+ heading: "DM Sans",
104
+ body: "Source Sans 3",
105
+ code: "monospace",
106
+ headingWeight: 700,
107
+ },
108
+ geometry: { radius: 4, spacing: 1, rule: "hairline" },
109
+ },
110
+ {
111
+ id: "ink",
112
+ palette: {
113
+ background: "#121B29",
114
+ surface: "#1D2B3E",
115
+ ink: "#F5F7FB",
116
+ muted: "#B3C2D6",
117
+ accent: "#85C8F3",
118
+ line: "#3D526C",
119
+ },
120
+ typography: {
121
+ heading: "Space Grotesk",
122
+ body: "Inter",
123
+ code: "monospace",
124
+ headingWeight: 700,
125
+ },
126
+ geometry: { radius: 12, spacing: 1, rule: "accent" },
127
+ },
128
+ {
129
+ id: "editorial",
130
+ palette: {
131
+ background: "#FCF8F1",
132
+ surface: "#FFFFFF",
133
+ ink: "#30281F",
134
+ muted: "#756657",
135
+ accent: "#A74328",
136
+ line: "#DED2C1",
137
+ },
138
+ typography: {
139
+ heading: "Lora",
140
+ body: "Source Sans 3",
141
+ code: "monospace",
142
+ headingWeight: 700,
143
+ },
144
+ geometry: { radius: 0, spacing: 1.1, rule: "hairline" },
145
+ },
146
+ {
147
+ id: "electric",
148
+ palette: {
149
+ background: "#F3F4FF",
150
+ surface: "#FFFFFF",
151
+ ink: "#222047",
152
+ muted: "#615D7B",
153
+ accent: "#5340D9",
154
+ line: "#DAD5F2",
155
+ },
156
+ typography: {
157
+ heading: "Space Grotesk",
158
+ body: "DM Sans",
159
+ code: "monospace",
160
+ headingWeight: 800,
161
+ },
162
+ geometry: { radius: 16, spacing: 1, rule: "none" },
163
+ },
164
+ {
165
+ id: "signal",
166
+ palette: {
167
+ background: "#FFE04B",
168
+ surface: "#FFF5B7",
169
+ ink: "#242418",
170
+ muted: "#535039",
171
+ accent: "#272B21",
172
+ line: "#B8A233",
173
+ },
174
+ typography: {
175
+ heading: "Oswald",
176
+ body: "Inter",
177
+ code: "monospace",
178
+ headingWeight: 800,
179
+ },
180
+ geometry: { radius: 2, spacing: 1.05, rule: "none" },
181
+ },
19
182
  ];
20
183
  function designTheme(id) {
21
- const alias = { neatico: 'electric', paper: 'studio', midnight: 'ink', ocean: 'ink', plum: 'electric' };
22
- return exports.DESIGN_THEMES.find(theme => theme.id === (alias[id] || id)) || exports.DESIGN_THEMES[0];
184
+ const alias = {
185
+ paper: "studio",
186
+ midnight: "ink",
187
+ ocean: "ink",
188
+ plum: "electric",
189
+ };
190
+ return (exports.DESIGN_THEMES.find((theme) => theme.id === (alias[id] || id)) ||
191
+ exports.DESIGN_THEMES[0]);
23
192
  }
24
193
  const family = (id, semantic) => {
25
194
  if (/code|terminal/.test(id))
26
- return 'code';
195
+ return "code";
27
196
  if (/comparison|compare|before-after|myth|problem-solution/.test(id))
28
- return 'comparison';
197
+ return "comparison";
29
198
  if (/process|steps|timeline|roadmap|journey|syllabus|workflow/.test(id))
30
- return 'steps';
199
+ return "steps";
31
200
  if (/chart|metric|stat|score|usage-breakdown/.test(id))
32
- return 'chart';
201
+ return "chart";
33
202
  if (/quote/.test(id))
34
- return 'quote';
203
+ return "quote";
35
204
  if (/table|specification/.test(id))
36
- return 'parameters';
205
+ return "parameters";
37
206
  if (/image|photo|gallery/.test(id))
38
- return 'image';
207
+ return "image";
39
208
  if (/cover|hero|banner|statement|closing|section/.test(id))
40
- return 'hero';
41
- if (semantic === 'steps')
42
- return 'steps';
43
- if (semantic === 'comparison')
44
- return 'comparison';
45
- return 'features';
209
+ return "hero";
210
+ if (semantic === "steps")
211
+ return "steps";
212
+ if (semantic === "comparison")
213
+ return "comparison";
214
+ return "features";
46
215
  };
47
- const accepted = (family) => ({ code: ['code'], comparison: ['comparison'], steps: ['steps'], chart: ['chart'], quote: ['quote'], parameters: ['parameters'], image: ['image'], hero: ['paragraph'], prose: ['paragraph'], features: ['features', 'paragraph'] }[family]);
216
+ const accepted = (family) => ({
217
+ code: ["code"],
218
+ comparison: ["comparison"],
219
+ steps: ["steps"],
220
+ chart: ["chart"],
221
+ quote: ["quote"],
222
+ parameters: ["parameters"],
223
+ image: ["image"],
224
+ hero: ["paragraph", "image"],
225
+ prose: ["paragraph"],
226
+ features: ["features", "paragraph"],
227
+ })[family];
48
228
  // Explicit semantic contracts, not name/scene guesses. A travel itinerary, calendar,
49
229
  // price promotion or fake UI cannot be inferred from an arbitrary paragraph.
50
230
  const socialContracts = {
51
- 'news-focus': ['paragraph'], manifesto: ['paragraph'], editorial: ['paragraph'], notice: ['paragraph'],
52
- 'block-banner': ['paragraph'], 'definition-card': ['paragraph'], 'concept-explainer': ['paragraph'],
53
- 'og-cover': ['paragraph'], 'gradient-cover': ['paragraph'], 'bold-diagonal-cover': ['paragraph'], 'split-cover': ['paragraph'],
54
- 'article-summary-card': ['paragraph'], 'paragraph-summary-card': ['paragraph'],
55
- bento: ['features'], spotlight: ['features'], report: ['features'], checklist: ['features'],
56
- 'contents-index': ['features'], 'contents-grid': ['features'], 'tips-card': ['features'],
57
- 'digest-card': ['features'], 'tips-grid': ['features'], 'feature-chip-hero': ['features'],
58
- steps: ['steps'], 'contents-chapters': ['steps'], 'illustration-process': ['steps'],
59
- comparison: ['comparison'], 'before-after': ['comparison'],
60
- 'code-snippet': ['code'], specification: ['parameters'],
61
- quote: ['quote'], 'editorial-quote': ['quote'], 'magazine-pull-quote': ['quote'],
231
+ "note-question": ["paragraph"], "note-marker": ["paragraph"], "note-editorial": ["paragraph"], "note-news": ["paragraph"], "note-checklist": ["features", "steps", "paragraph"], "note-photo": ["image", "paragraph"],
232
+ "news-focus": ["paragraph"],
233
+ manifesto: ["paragraph"],
234
+ editorial: ["paragraph"],
235
+ notice: ["paragraph"],
236
+ "block-banner": ["paragraph"],
237
+ "definition-card": ["paragraph"],
238
+ "concept-explainer": ["paragraph"],
239
+ "og-cover": ["paragraph"],
240
+ "gradient-cover": ["paragraph"],
241
+ "bold-diagonal-cover": ["paragraph"],
242
+ "split-cover": ["paragraph"],
243
+ "article-summary-card": ["paragraph"],
244
+ "paragraph-summary-card": ["paragraph"],
245
+ bento: ["features"],
246
+ spotlight: ["features"],
247
+ report: ["features"],
248
+ checklist: ["features"],
249
+ "contents-index": ["features"],
250
+ "contents-grid": ["features"],
251
+ "tips-card": ["features"],
252
+ "digest-card": ["features"],
253
+ "tips-grid": ["features"],
254
+ "feature-chip-hero": ["features"],
255
+ steps: ["steps"],
256
+ "contents-chapters": ["steps"],
257
+ "illustration-process": ["steps"],
258
+ comparison: ["comparison"],
259
+ "before-after": ["comparison"],
260
+ "code-snippet": ["code"],
261
+ specification: ["parameters"],
262
+ course: ["steps"],
263
+ "case-study": ["features"],
264
+ safety: ["features"],
265
+ faq: ["features"],
266
+ testimonial: ["quote"],
267
+ quote: ["quote"],
268
+ "editorial-quote": ["quote"],
269
+ "magazine-pull-quote": ["quote"],
270
+ };
271
+ /** Domain compositions require explicit semantic intent, never a keyword guess. */
272
+ const socialScenes = {
273
+ course: ["tutorial", "roadmap"],
274
+ safety: ["checklist"],
275
+ faq: ["questions"],
276
+ "case-study": ["case-study"],
277
+ testimonial: ["testimonial"],
278
+ };
279
+ exports.MANUAL_COMPOSITION_REASONS = {
280
+ launch: "Legacy artwork simulates product UI; use authored-feature-bento or authored-image-split with a resolved asset.",
281
+ metric: "Legacy value slot lacks a chart-to-value binding; use typed chart compositions.",
282
+ myth: "Fixed wrong/right semantics cannot be inferred from ordinary comparison columns.",
283
+ story: "Legacy artwork is illustrative; use authored-image-editorial with a resolved image.",
284
+ team: "Requires typed people and corresponding portrait assets.",
285
+ "split-bar-compare": "Requires paired measured series; generic comparison text is insufficient.",
286
+ "benchmark-bars": "Requires explicit benchmark values, not invented percentages.",
287
+ "usage-breakdown": "Requires typed parts and a validated total; current chart blocks support bar/line measurements, not inferred percentage shares.",
288
+ organization: "Requires an explicit parent/child relationship contract.",
289
+ "research-highlight": "Requires methodology and limitations mapped independently.",
290
+ "product-showcase": "Use authored-image-split/campaign with a resolved image, not legacy fake UI.",
291
+ };
292
+ const parameterScenes = {
293
+ "time-notice": ["event-details"],
294
+ ticket: ["event-details"],
295
+ webinar: ["event-details"],
296
+ community: ["event-details"],
297
+ schedule: ["event-details"],
298
+ pricing: ["pricing-details"],
299
+ offer: ["offer-details"],
300
+ hiring: ["recruitment-details"],
301
+ menu: ["menu-details"],
302
+ itinerary: ["travel-details"],
62
303
  };
63
304
  // Automatic presentation matching requires an explicit semantic contract too.
64
305
  // A feature paragraph must never become a price plan, employee or risk entry.
65
- const presentationContracts = {
66
- cover: ['paragraph'], section: ['paragraph'], statement: ['paragraph'], closing: ['paragraph'],
67
- agenda: ['features'], bullets: ['features'], 'two-column': ['features'],
68
- 'three-column': ['features'], cards: ['features'], checklist: ['features'],
69
- process: ['steps'], timeline: ['steps'], roadmap: ['steps'], 'progress-roadmap': ['steps'],
70
- comparison: ['comparison'], 'before-after': ['comparison'], 'problem-solution': ['comparison'],
71
- metrics: ['chart'], 'bar-chart': ['chart'], 'usage-breakdown': ['chart'],
72
- quote: ['quote'], 'editorial-quote': ['quote'], 'magazine-pull-quote': ['quote'],
73
- table: ['parameters'], specification: ['parameters'], 'code-snippet': ['code'],
74
- 'image-left': ['image'], 'image-right': ['image'], 'image-full': ['image'],
75
- 'product-showcase': ['image'],
76
- };
77
306
  const recipe = (id, kind, semantic) => {
78
- const contract = (kind === 'social' ? socialContracts : presentationContracts)[id];
307
+ if (parameterScenes[id])
308
+ return {
309
+ id,
310
+ kind,
311
+ renderer: "semantic",
312
+ automatic: true,
313
+ family: `domain-${id}`,
314
+ accepts: ["parameters"],
315
+ minEntries: 1,
316
+ maxEntries: 6,
317
+ requiresImage: false,
318
+ scenes: parameterScenes[id],
319
+ };
320
+ const contract = socialContracts[id];
79
321
  const group = contract?.[0] || family(id, semantic);
80
- return { id, kind, renderer: 'catalog', automatic: Boolean(contract), family: group, accepts: contract || accepted(group), minEntries: group === 'comparison' ? 2 : kind === 'social' && (group === 'features' || group === 'steps' || group === 'parameters') ? 1 : 0, maxEntries: group === 'hero' ? 0 : kind === 'presentation' ? 4 : 6, requiresImage: group === 'image' };
322
+ return {
323
+ id,
324
+ kind,
325
+ renderer: "catalog",
326
+ automatic: Boolean(contract),
327
+ scenes: socialScenes[id],
328
+ ...(!contract
329
+ ? {
330
+ manualReason: exports.MANUAL_COMPOSITION_REASONS[id] ||
331
+ "No verified semantic projection.",
332
+ }
333
+ : {}),
334
+ family: group,
335
+ accepts: contract || accepted(group),
336
+ minEntries: group === "comparison"
337
+ ? 2
338
+ : kind === "social" &&
339
+ (group === "features" ||
340
+ group === "steps" ||
341
+ group === "parameters")
342
+ ? 1
343
+ : 0,
344
+ maxEntries: group === "hero" ? 0 : kind === "presentation" ? 4 : 6,
345
+ requiresImage: group === "image",
346
+ };
81
347
  };
348
+ /** Stable aliases keep saved documents readable while excluding duplicate candidates. */
349
+ function canonicalRecipeId(id, kind) {
350
+ if (/^semantic-(code|parameters|comparison|chart)-editorial$/.test(id))
351
+ return canonicalRecipeId(id.replace(/-editorial$/, "-stack"), kind);
352
+ if (id === "semantic-code-stack")
353
+ return "authored-code-editorial";
354
+ if (id === "semantic-comparison-stack")
355
+ return "authored-comparison-panels";
356
+ if (kind === "social" && id === "semantic-hero-split")
357
+ return "semantic-hero-editorial";
358
+ if (id === "semantic-quote-stack")
359
+ return "authored-quote-poster";
360
+ if (id === "semantic-quote-editorial")
361
+ return "authored-quote-editorial";
362
+ if (kind === "presentation" && id === "semantic-hero-split")
363
+ return "bookend-cover-split";
364
+ if (kind === "presentation" &&
365
+ ["semantic-hero-stack", "semantic-hero-editorial"].includes(id))
366
+ return "bookend-cover-editorial";
367
+ if (kind === "social" && id === "semantic-hero-stack")
368
+ return "semantic-hero-editorial";
369
+ return id;
370
+ }
82
371
  /** Existing authored catalogs remain first-class candidates, rather than being discarded. */
83
- exports.DESIGN_RECIPES = [
84
- ...['cover', 'closing'].flatMap(role => ['editorial', 'panel', 'split'].map(style => ({ id: `bookend-${role}-${style}`, kind: 'presentation', renderer: 'semantic', family: `${role}-${style}`, accepts: ['paragraph'], minEntries: 0, maxEntries: 0, requiresImage: false }))),
85
- ...['presentation', 'social'].map(kind => ({ id: 'brand-collection-grid', kind, renderer: 'semantic', family: 'brand-collection', accepts: ['features'], minEntries: 2, maxEntries: 12, requiresImage: false })),
86
- ...layouts_js_1.PRESENTATION_LAYOUT_IDS.map(id => recipe(id, 'presentation')),
87
- ...socialLayouts_js_1.SOCIAL_LAYOUT_CATALOG.map(item => recipe(item.id, 'social', item.scene)),
88
- ...['presentation', 'social'].flatMap(kind => [
89
- ...['banner', 'split', 'editorial', 'campaign', 'inset'].map(style => ({ id: `authored-image-${style}`, kind, renderer: 'semantic', family: `image-${style}`, accepts: ['image'], minEntries: 0, maxEntries: 0, requiresImage: true })),
90
- { id: 'authored-prose-inset', kind, renderer: 'semantic', family: 'prose-inset', accepts: ['paragraph'], minEntries: 0, maxEntries: 0, requiresImage: false },
91
- ...compositionScenes_js_1.AUTHORED_COMPOSITIONS.map(composition => ({ id: `authored-${composition.id}`, kind, renderer: 'semantic', family: composition.id, accepts: [composition.kind], minEntries: composition.min, maxEntries: composition.max, requiresImage: false })),
92
- ...['code', 'parameters', 'steps', 'comparison', 'features', 'quote', 'chart', 'hero', 'prose'].flatMap(group => ['split', 'stack', 'editorial'].map(variant => ({ id: `semantic-${group}-${variant}`, kind, renderer: 'semantic', family: `${group}-${variant}`, accepts: accepted(group), minEntries: 0, maxEntries: 20, requiresImage: false }))),
93
- { id: 'semantic-mixed-stack', kind, renderer: 'semantic', family: 'mixed-stack', accepts: ['paragraph', 'steps', 'features', 'comparison', 'code', 'parameters', 'quote', 'chart', 'image'], minEntries: 0, maxEntries: 20, requiresImage: false },
372
+ const ALL_DESIGN_RECIPES = [
373
+ ...["cover", "closing"].flatMap((role) => ["editorial", "panel", "split"].map((style) => ({
374
+ id: `bookend-${role}-${style}`,
375
+ kind: "presentation",
376
+ renderer: "semantic",
377
+ family: `${role}-${style}`,
378
+ accepts: ["paragraph", "image"],
379
+ minEntries: 0,
380
+ maxEntries: 0,
381
+ requiresImage: false,
382
+ }))),
383
+ ...["presentation", "social"].map((kind) => ({
384
+ id: "brand-collection-grid",
385
+ kind,
386
+ renderer: "semantic",
387
+ family: "brand-collection",
388
+ accepts: ["features"],
389
+ minEntries: 2,
390
+ maxEntries: 12,
391
+ requiresImage: false,
392
+ })),
393
+ ...socialLayouts_js_1.SOCIAL_LAYOUT_CATALOG.map((item) => recipe(item.id, "social", item.scene)),
394
+ ...["presentation", "social"].flatMap((kind) => [
395
+ {
396
+ id: "authored-plan-cards",
397
+ kind,
398
+ renderer: "semantic",
399
+ family: "plan-cards",
400
+ accepts: ["comparison", "paragraph"],
401
+ minEntries: 2,
402
+ maxEntries: 4,
403
+ requiresImage: false,
404
+ },
405
+ ...["banner", "split", "editorial", "campaign", "inset"].map((style) => ({
406
+ id: `authored-image-${style}`,
407
+ kind,
408
+ renderer: "semantic",
409
+ family: `image-${style}`,
410
+ accepts: ["image", "paragraph", "features"],
411
+ minEntries: 0,
412
+ maxEntries: 3,
413
+ requiresImage: true,
414
+ })),
415
+ {
416
+ id: "authored-prose-inset",
417
+ kind,
418
+ renderer: "semantic",
419
+ family: "prose-inset",
420
+ accepts: ["paragraph"],
421
+ minEntries: 0,
422
+ maxEntries: 0,
423
+ requiresImage: false,
424
+ },
425
+ ...compositionScenes_js_1.AUTHORED_COMPOSITIONS.map((composition) => ({
426
+ id: `authored-${composition.id}`,
427
+ kind,
428
+ renderer: "semantic",
429
+ family: composition.id,
430
+ accepts: [composition.kind, "paragraph"],
431
+ minEntries: composition.min,
432
+ maxEntries: composition.max,
433
+ requiresImage: false,
434
+ })),
435
+ ...[
436
+ "code",
437
+ "parameters",
438
+ "steps",
439
+ "comparison",
440
+ "features",
441
+ "quote",
442
+ "chart",
443
+ "hero",
444
+ "prose",
445
+ ].flatMap((group) => (["code", "parameters", "comparison", "chart"].includes(group)
446
+ ? ["split", "stack"]
447
+ : ["split", "stack", "editorial"]).map((variant) => ({
448
+ id: `semantic-${group}-${variant}`,
449
+ kind,
450
+ renderer: "semantic",
451
+ family: `${group}-${variant}`,
452
+ accepts: group === "prose" && variant === "editorial"
453
+ ? ["paragraph", "image"]
454
+ : accepted(group),
455
+ minEntries: 0,
456
+ maxEntries: 20,
457
+ requiresImage: false,
458
+ }))),
459
+ {
460
+ id: "semantic-mixed-stack",
461
+ kind,
462
+ renderer: "semantic",
463
+ family: "mixed-stack",
464
+ accepts: [
465
+ "paragraph",
466
+ "steps",
467
+ "features",
468
+ "comparison",
469
+ "code",
470
+ "parameters",
471
+ "quote",
472
+ "chart",
473
+ "image",
474
+ ],
475
+ minEntries: 0,
476
+ maxEntries: 20,
477
+ requiresImage: false,
478
+ },
94
479
  ]),
95
480
  ];
481
+ exports.DESIGN_RECIPES = ALL_DESIGN_RECIPES.filter((r) => canonicalRecipeId(r.id, r.kind) === r.id);
96
482
  function pageEntryCount(page) {
97
- return page.blocks.reduce((sum, block) => sum + (block.kind === 'steps' || block.kind === 'features' ? block.entries.length : block.kind === 'comparison' ? block.columns.length : block.kind === 'parameters' ? block.rows.length : block.kind === 'chart' ? block.values.length : 0), 0);
483
+ return page.blocks.reduce((sum, block) => sum +
484
+ (block.kind === "steps" || block.kind === "features"
485
+ ? block.entries.length
486
+ : block.kind === "comparison"
487
+ ? block.columns.length
488
+ : block.kind === "parameters"
489
+ ? block.rows.length
490
+ : block.kind === "chart"
491
+ ? block.values.length
492
+ : 0), 0);
98
493
  }
99
494
  function designCandidates(page, context) {
100
- if (!Number.isFinite(context.ratio) || context.ratio < .25 || context.ratio > 4)
495
+ if (!Number.isFinite(context.ratio) ||
496
+ context.ratio < 0.25 ||
497
+ context.ratio > 4)
101
498
  return [];
102
- const primary = page.blocks.filter(b => b.kind !== 'paragraph' && !(b.kind === 'image' && b.status === 'missing'));
103
- const kinds = [...new Set((primary.length ? primary : page.blocks.filter(b => b.kind !== 'image')).map(b => b.kind))];
104
- const count = pageEntryCount(page), characters = page.blocks.flatMap(content_js_1.contentBlockText).join('').length;
105
- const available = page.blocks.some(b => b.kind === 'image' && b.status === 'available');
499
+ const primary = page.blocks.filter((b) => b.kind !== "paragraph" && !(b.kind === "image" && b.status === "missing"));
500
+ const kinds = [
501
+ ...new Set((primary.length
502
+ ? primary
503
+ : page.blocks.filter((b) => b.kind !== "image")).map((b) => b.kind)),
504
+ ];
505
+ const richComparison = page.blocks.some((b) => b.kind === "comparison" &&
506
+ b.columns.some((c) => c.price || c.values || c.statuses));
507
+ const count = pageEntryCount(page), characters = page.blocks.flatMap(content_js_1.contentBlockText).join("").length;
508
+ const available = page.blocks.some((b) => b.kind === "image" && b.status === "available");
106
509
  const exclude = new Set(context.excluded || []);
107
- const candidates = exports.DESIGN_RECIPES.filter(r => r.kind === context.kind && r.automatic !== false && (!(r.family.startsWith('hero') && page.purpose !== 'cover' && page.purpose !== 'conclude' && characters > 120)) && (!r.id.startsWith('bookend-') || (page.purpose === 'cover' ? r.id.startsWith('bookend-cover-') : page.purpose === 'conclude' && r.id.startsWith('bookend-closing-'))) && (r.id !== 'brand-collection-grid' || page.scene === 'brand-collection') && !exclude.has(r.id) && count >= r.minEntries && count <= r.maxEntries && (!r.requiresImage || available) && kinds.every(k => r.accepts.includes(k)) && !(r.renderer === 'catalog' && (primary.length > 1 || characters > 1000)));
510
+ const candidates = exports.DESIGN_RECIPES.filter((r) => r.kind === context.kind &&
511
+ r.automatic !== false &&
512
+ (!r.scenes || Boolean(page.scene && r.scenes.includes(page.scene))) &&
513
+ !(r.family.startsWith("hero") &&
514
+ page.purpose !== "cover" &&
515
+ page.purpose !== "conclude" &&
516
+ characters > 120) &&
517
+ (!r.id.startsWith("bookend-") ||
518
+ (page.purpose === "cover"
519
+ ? r.id.startsWith("bookend-cover-")
520
+ : page.purpose === "conclude" &&
521
+ r.id.startsWith("bookend-closing-"))) &&
522
+ (r.id !== "brand-collection-grid" || page.scene === "brand-collection") &&
523
+ !exclude.has(r.id) &&
524
+ count >= r.minEntries &&
525
+ count <= r.maxEntries &&
526
+ (!r.requiresImage || available) &&
527
+ kinds.every((k) => r.accepts.includes(k)) &&
528
+ !(r.renderer === "catalog" &&
529
+ (richComparison || primary.length > 1 || characters > 1000)));
108
530
  const score = (r) => {
109
- const repeats = (context.previousFamilies || []).filter(f => f === r.family).length;
110
- const intent = page.purpose === 'cover' || page.purpose === 'conclude' ? r.family.startsWith('hero') : page.purpose === 'instruct' ? r.family.startsWith('steps') || r.family.startsWith('code') : true;
111
- return (r.id.startsWith('bookend-') ? 80 : 0) + (page.scene === 'brand-collection' && r.id === 'brand-collection-grid' ? 80 : 0) + (0, compositionScenes_js_1.sceneRecipePreference)(page.scene, r.id) + (intent ? 30 : 0) + (r.renderer === 'catalog' ? 8 : 0) - (r.id.includes('mixed') ? 20 : 0) - repeats * 18 + (context.ratio < 1 && r.id.endsWith('stack') ? 6 : 0);
531
+ const repeats = (context.previousFamilies || []).filter((f) => f === r.family).length;
532
+ const intent = page.purpose === "cover" || page.purpose === "conclude"
533
+ ? r.family.startsWith("hero")
534
+ : page.purpose === "instruct"
535
+ ? r.family.startsWith("steps") || r.family.startsWith("code")
536
+ : true;
537
+ return ((r.id.startsWith("bookend-") ? 80 : 0) +
538
+ (page.scene === "brand-collection" && r.id === "brand-collection-grid"
539
+ ? 80
540
+ : 0) +
541
+ (0, compositionScenes_js_1.sceneRecipePreference)(page.scene, r.id) +
542
+ (intent ? 30 : 0) +
543
+ (r.renderer === "catalog" ? 8 : 0) -
544
+ (r.id.includes("mixed") ? 20 : 0) -
545
+ repeats * 18 +
546
+ (context.ratio < 1 && r.id.endsWith("stack") ? 6 : 0));
112
547
  };
113
- const ranked = candidates.map((r, index) => ({ r, index, score: score(r) })).sort((a, b) => b.score - a.score || a.index - b.index).map(e => e.r);
548
+ const ranked = candidates
549
+ .map((r, index) => ({ r, index, score: score(r) }))
550
+ .sort((a, b) => b.score - a.score || a.index - b.index)
551
+ .map((e) => e.r);
114
552
  const limit = Math.max(1, Math.min(32, context.limit ?? 12));
115
553
  // Catalog size must not prevent semantic renderers from reaching measurement.
116
- const semantic = ranked.filter(r => r.renderer === 'semantic').slice(0, Math.min(8, Math.ceil(limit / 2)));
117
- const reserved = new Set(semantic.map(r => r.id));
118
- const selected = new Set([...semantic, ...ranked.filter(r => !reserved.has(r.id)).slice(0, limit - semantic.length)].map(r => r.id));
119
- return ranked.filter(r => selected.has(r.id));
554
+ const semantic = ranked
555
+ .filter((r) => r.renderer === "semantic")
556
+ .slice(0, Math.min(8, Math.ceil(limit / 2)));
557
+ const reserved = new Set(semantic.map((r) => r.id));
558
+ const selected = new Set([
559
+ ...semantic,
560
+ ...ranked
561
+ .filter((r) => !reserved.has(r.id))
562
+ .slice(0, limit - semantic.length),
563
+ ].map((r) => r.id));
564
+ return ranked.filter((r) => selected.has(r.id));
120
565
  }
121
566
  /** Ignores color, copy and generated IDs; previews that only recolor are the same composition. */
122
567
  function structureFingerprint(items, width, height) {
123
- if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
124
- throw new Error('INVALID_CANVAS');
125
- return items.map(item => [item.role, ...[item.x / width, item.y / height, item.width / width, item.height / height].map(v => Math.round(v * 24))].join(':')).sort().join('|');
568
+ if (!Number.isFinite(width) ||
569
+ !Number.isFinite(height) ||
570
+ width <= 0 ||
571
+ height <= 0)
572
+ throw new Error("INVALID_CANVAS");
573
+ return items
574
+ .map((item) => [
575
+ item.role,
576
+ ...[
577
+ item.x / width,
578
+ item.y / height,
579
+ item.width / width,
580
+ item.height / height,
581
+ ].map((v) => Math.round(v * 24)),
582
+ ].join(":"))
583
+ .sort()
584
+ .join("|");
126
585
  }