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