@neatico/visual-core 0.14.64 → 0.14.66

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.
@@ -6,11 +6,13 @@ exports.canonicalRecipeId = canonicalRecipeId;
6
6
  exports.pageEntryCount = pageEntryCount;
7
7
  exports.designCandidates = designCandidates;
8
8
  exports.structureFingerprint = structureFingerprint;
9
+ const exploreCollection_js_1 = require("./exploreCollection.js");
9
10
  const compositionScenes_js_1 = require("./compositionScenes.js");
10
11
  const socialLayouts_js_1 = require("./socialLayouts.js");
11
12
  const content_js_1 = require("./content.js");
12
13
  /** Complete, deterministic token sets; decorative geometry belongs to individual compositions. */
13
14
  exports.DESIGN_THEMES = [
15
+ ...exploreCollection_js_1.EXPLORE_THEMES,
14
16
  {
15
17
  id: "neatico",
16
18
  palette: { background: "#FAF9F6", surface: "#FFFFFF", ink: "#202124", muted: "#62646B", accent: "#F4CE36", line: "#E4E1D9" },
@@ -188,7 +190,7 @@ function designTheme(id) {
188
190
  plum: "electric",
189
191
  };
190
192
  return (exports.DESIGN_THEMES.find((theme) => theme.id === (alias[id] || id)) ||
191
- exports.DESIGN_THEMES[0]);
193
+ exports.DESIGN_THEMES.find(theme => theme.id === "neatico"));
192
194
  }
193
195
  const family = (id, semantic) => {
194
196
  if (/code|terminal/.test(id))
@@ -370,6 +372,7 @@ function canonicalRecipeId(id, kind) {
370
372
  }
371
373
  /** Existing authored catalogs remain first-class candidates, rather than being discarded. */
372
374
  const ALL_DESIGN_RECIPES = [
375
+ ...exploreCollection_js_1.EXPLORE_RECIPES,
373
376
  ...["cover", "closing"].flatMap((role) => ["editorial", "panel", "split"].map((style) => ({
374
377
  id: `bookend-${role}-${style}`,
375
378
  kind: "presentation",
@@ -508,6 +511,7 @@ function designCandidates(page, context) {
508
511
  const available = page.blocks.some((b) => b.kind === "image" && b.status === "available");
509
512
  const exclude = new Set(context.excluded || []);
510
513
  const candidates = exports.DESIGN_RECIPES.filter((r) => r.kind === context.kind &&
514
+ (!r.id.startsWith("authored-explore-") || (context.ratio >= 1.4 && context.ratio <= 2 && (0, exploreCollection_js_1.exploreCompositionFits)(page, r.id))) &&
511
515
  r.automatic !== false &&
512
516
  (!r.scenes || Boolean(page.scene && r.scenes.includes(page.scene))) &&
513
517
  !(r.family.startsWith("hero") &&
@@ -535,6 +539,7 @@ function designCandidates(page, context) {
535
539
  ? r.family.startsWith("steps") || r.family.startsWith("code")
536
540
  : true;
537
541
  return ((r.id.startsWith("bookend-") ? 80 : 0) +
542
+ (r.id.startsWith("authored-explore-") ? 110 : 0) +
538
543
  (page.scene === "brand-collection" && r.id === "brand-collection-grid"
539
544
  ? 80
540
545
  : 0) +
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EXPLORE_RECIPES = exports.EXPLORE_LAYOUTS = exports.EXPLORE_THEMES = exports.DEFAULT_PRESENTATION_THEME = void 0;
4
+ exports.exploreCompositionFits = exploreCompositionFits;
5
+ exports.DEFAULT_PRESENTATION_THEME = "explore-teal";
6
+ /** Explore's editorial colorways, including light and solid-background variants. */
7
+ const colors = [
8
+ ["teal", "#214B4A", "#E1EEEB", "#F7FAF9", "#1C3533"],
9
+ ["slate", "#3F5477", "#E6EAF1", "#F9FAFC", "#1D283B"],
10
+ ["cobalt", "#2847A9", "#E5EBFC", "#F8FAFF", "#19294E"],
11
+ ["clay", "#9D4F32", "#F8E7DE", "#FFFAF6", "#3F2920"],
12
+ ["terracotta", "#AC3F25", "#FBE5DC", "#FFFAF5", "#3E241B"],
13
+ ["lavender", "#6A497B", "#EEE6F5", "#FCFAFD", "#34243D"],
14
+ ["petrol", "#315664", "#E2EDF1", "#F8FBFC", "#223E48"],
15
+ ["sand", "#6D5140", "#EFE4D8", "#FCFAF7", "#392C23"],
16
+ ["indigo", "#43387D", "#EAE5F6", "#FAF9FD", "#292444"],
17
+ ["olive", "#4F633A", "#E8EEDC", "#FBFCF8", "#2D3824"],
18
+ ["olive-classic", "#E8EEDC", "#607447", "#4F6339", "#FFFFFF"],
19
+ ["rose", "#9B3E61", "#F6E2EB", "#FFFAFC", "#402332"],
20
+ ["wine", "#7A3048", "#EFDEE5", "#FCF8FA", "#39202C"],
21
+ ["sunflower", "#8A5D12", "#F7EBC6", "#FFFDF5", "#392F1D"],
22
+ ["seafoam", "#216C62", "#DDF0E8", "#F7FCFA", "#193D36"],
23
+ ["graphite", "#D5B98C", "#30343A", "#1B1E23", "#F5F2EA"],
24
+ ["midnight", "#91B7DE", "#22344C", "#132236", "#F3F6FA"],
25
+ ["ice", "#27697B", "#DDEFF5", "#F8FCFE", "#193C49"],
26
+ ["copper", "#9A563B", "#F1E2D7", "#FCF9F5", "#3F2C23"],
27
+ ];
28
+ exports.EXPLORE_THEMES = colors.map(([id, accent, surface, background, ink]) => ({
29
+ id: `explore-${id}`,
30
+ palette: { accent, surface, background, ink, muted: ink, line: surface },
31
+ typography: {
32
+ heading: "Playfair Display",
33
+ body: "Inter",
34
+ code: "JetBrains Mono",
35
+ headingWeight: 600,
36
+ },
37
+ geometry: { radius: 4, spacing: 1.1, rule: "hairline" },
38
+ }));
39
+ exports.EXPLORE_LAYOUTS = [
40
+ [
41
+ "cover",
42
+ "paragraph",
43
+ 0,
44
+ 0,
45
+ "Open with a spacious editorial headline, a short premise and an optional real photograph.",
46
+ ],
47
+ [
48
+ "overview",
49
+ "features",
50
+ 2,
51
+ 4,
52
+ "Orient readers with a numbered overview of parallel subjects.",
53
+ ],
54
+ [
55
+ "section",
56
+ "paragraph",
57
+ 0,
58
+ 0,
59
+ "Introduce one new section or central principle in a strong color field.",
60
+ ],
61
+ [
62
+ "pillars",
63
+ "features",
64
+ 2,
65
+ 3,
66
+ "Explain two or three equally important ideas in spacious editorial columns.",
67
+ ],
68
+ [
69
+ "focus",
70
+ "paragraph",
71
+ 0,
72
+ 0,
73
+ "Develop one idea in an asymmetric reading composition.",
74
+ ],
75
+ [
76
+ "workflow",
77
+ "steps",
78
+ 2,
79
+ 4,
80
+ "Show an ordered workflow with explicit actions and useful descriptions.",
81
+ ],
82
+ [
83
+ "timeline",
84
+ "steps",
85
+ 2,
86
+ 4,
87
+ "Present a short chronological sequence on a horizontal reading axis.",
88
+ ],
89
+ [
90
+ "comparison",
91
+ "comparison",
92
+ 2,
93
+ 2,
94
+ "Compare two supplied alternatives with corresponding points.",
95
+ ],
96
+ [
97
+ "worksheet",
98
+ "parameters",
99
+ 2,
100
+ 4,
101
+ "Explain named fields with their concrete values and instructions.",
102
+ ],
103
+ [
104
+ "photo-story",
105
+ "image",
106
+ 0,
107
+ 0,
108
+ "Use a real contextual photograph beside a concise explanation.",
109
+ ],
110
+ [
111
+ "quote",
112
+ "quote",
113
+ 0,
114
+ 0,
115
+ "Give an exact source statement room to breathe, retaining supplied attribution.",
116
+ ],
117
+ [
118
+ "chart",
119
+ "chart",
120
+ 2,
121
+ 6,
122
+ "Compare source-backed quantities in labeled bars with the original unit.",
123
+ ],
124
+ [
125
+ "ledger",
126
+ "parameters",
127
+ 2,
128
+ 5,
129
+ "Arrange named facts and explanations in a flat editorial ledger.",
130
+ ],
131
+ [
132
+ "faq",
133
+ "features",
134
+ 2,
135
+ 3,
136
+ "Answer a small set of meaningful questions with concise responses.",
137
+ ],
138
+ [
139
+ "action-plan",
140
+ "steps",
141
+ 2,
142
+ 3,
143
+ "Make the next few actions prominent, with descriptions that explain the work.",
144
+ ],
145
+ [
146
+ "closing",
147
+ "paragraph",
148
+ 0,
149
+ 0,
150
+ "Close with one clear takeaway and a concrete next action.",
151
+ ],
152
+ ];
153
+ exports.EXPLORE_RECIPES = exports.EXPLORE_LAYOUTS.map(([id, kind, min, max]) => ({
154
+ id: `authored-explore-${id}`,
155
+ kind: "presentation",
156
+ renderer: "semantic",
157
+ automatic: true,
158
+ family: kind === "paragraph" ? `hero-explore-${id}` : kind,
159
+ minEntries: min,
160
+ maxEntries: max,
161
+ accepts: (["cover", "photo-story"].includes(id)
162
+ ? ["paragraph", "image"]
163
+ : [kind]),
164
+ requiresImage: id === "photo-story",
165
+ }));
166
+ function exploreCompositionFits(page, id) {
167
+ if (!id.startsWith("authored-explore-"))
168
+ return true;
169
+ const variant = id.slice("authored-explore-".length);
170
+ if (variant === "cover")
171
+ return page.purpose === "cover";
172
+ if (variant === "closing")
173
+ return page.purpose === "conclude";
174
+ if (["cover", "conclude"].includes(page.purpose))
175
+ return false;
176
+ if (variant === "section")
177
+ return (page.blocks.length === 1 &&
178
+ page.blocks[0].kind === "paragraph" &&
179
+ page.blocks[0].text.length <= 160);
180
+ return true;
181
+ }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FORMAL_COMPOSITIONS = exports.FORMAL_REFERENCE_COMPOSITIONS = void 0;
4
4
  exports.applicableCompositions = applicableCompositions;
5
+ const exploreCollection_js_1 = require("./exploreCollection.js");
5
6
  const compositionUseCases_js_1 = require("./compositionUseCases.js");
6
7
  const nativeSocialParameters_js_1 = require("./nativeSocialParameters.js");
7
8
  const designSystem_js_1 = require("./designSystem.js");
@@ -11,7 +12,7 @@ const compositionNumbers_js_1 = require("./compositionNumbers.js");
11
12
  const both = ['presentation', 'social'];
12
13
  const semantic = (id, accepts, minEntries = 0, maxEntries = 12, requiresImage = false) => ({ id, renderer: 'semantic', kind: 'social', family: id, accepts, minEntries, maxEntries, requiresImage });
13
14
  function define(recipe, kinds, id, displayNumber, min = .25, max = 4) {
14
- return { ...(0, recipeParameters_js_1.recipeParameterContract)(recipe, kinds), semanticDescription: (0, compositionUseCases_js_1.compositionUseCase)(recipe.id), id, displayNumber, aspectRatios: { min, max }, recipe };
15
+ return { ...(0, recipeParameters_js_1.recipeParameterContract)(recipe, kinds), semanticDescription: exploreCollection_js_1.EXPLORE_LAYOUTS.find(item => recipe.id === `authored-explore-${item[0]}`)?.[4] || (0, compositionUseCases_js_1.compositionUseCase)(recipe.id), id, displayNumber, aspectRatios: { min, max }, recipe };
15
16
  }
16
17
  /** Structural reference contracts shared with the product's existing renderers.
17
18
  * Colorways and historical aliases remain outside active discovery.
@@ -104,7 +105,7 @@ exports.FORMAL_COMPOSITIONS = [
104
105
  const recipe = designSystem_js_1.DESIGN_RECIPES.find(item => item.id === preset.id && item.kind === 'social');
105
106
  return { ...define({ ...recipe, renderer: 'catalog' }, ['social'], `social:${preset.id}`, compositionNumbers_js_1.SOCIAL_DISPLAY_NUMBERS[preset.id], .5, 2.4), ...(0, nativeSocialParameters_js_1.nativeSocialParameterContract)(preset.id) };
106
107
  }),
107
- ...uniqueSemantic.map(recipe => define(recipe, semanticRecipes.filter(other => other.id === recipe.id).map(other => other.kind), `semantic:${recipe.kind}:${recipe.id}`, compositionNumbers_js_1.SEMANTIC_DISPLAY_NUMBERS[`semantic:${recipe.kind}:${recipe.id}`])),
108
+ ...uniqueSemantic.map(recipe => define(recipe, semanticRecipes.filter(other => other.id === recipe.id).map(other => other.kind), `semantic:${recipe.kind}:${recipe.id}`, recipe.id.startsWith("authored-explore-") ? `X${String(exploreCollection_js_1.EXPLORE_LAYOUTS.findIndex(item => recipe.id === `authored-explore-${item[0]}`) + 1).padStart(2, "0")}` : compositionNumbers_js_1.SEMANTIC_DISPLAY_NUMBERS[`semantic:${recipe.kind}:${recipe.id}`], recipe.id.startsWith("authored-explore-") ? 1.4 : .25, recipe.id.startsWith("authored-explore-") ? 2 : 4)),
108
109
  ...founderDefinitions.map(([id, accepts, min, max], index) => define(semantic(id, accepts, min, max), ['social'], `semantic:social:${id}`, `F${index + 1}`, 1.3)),
109
110
  ...[0, 1, 2].flatMap(variant => [article(`article-cover-${variant}`, 'cover', variant), ...articleKinds.map(kind => article(`article-${kind}-${variant}`, kind, variant))]),
110
111
  ...[3, 4, 5, 6, 7, 8, 9, 10].map(variant => article(`article-features-${variant}`, 'features', variant)),
package/dist/cjs/index.js CHANGED
@@ -42,3 +42,4 @@ Object.defineProperty(exports, "compositionParameterDefaults", { enumerable: tru
42
42
  var compositionNumbers_js_1 = require("./compositionNumbers.js");
43
43
  Object.defineProperty(exports, "SOCIAL_DISPLAY_NUMBERS", { enumerable: true, get: function () { return compositionNumbers_js_1.SOCIAL_DISPLAY_NUMBERS; } });
44
44
  __exportStar(require("./textPresentation.js"), exports);
45
+ __exportStar(require("./exploreCollection.js"), exports);
@@ -109,7 +109,9 @@ function blockSchema(kind, maximum) {
109
109
  function fixedBlocks(recipe) {
110
110
  const id = recipe.id;
111
111
  let kinds;
112
- if (id.endsWith("ledger-chart"))
112
+ if (["authored-explore-cover", "authored-explore-photo-story"].includes(id))
113
+ kinds = ["paragraph", "image"];
114
+ else if (id.endsWith("ledger-chart"))
113
115
  kinds = ["parameters", "chart"];
114
116
  else if (id.endsWith("detail-sidebar"))
115
117
  kinds = ["paragraph", "parameters"];
@@ -151,8 +153,13 @@ function fixedBlocks(recipe) {
151
153
  const fields = schema.properties;
152
154
  const count = Math.max(recipe.minEntries || 1, Math.min(3, recipe.maxEntries || 3));
153
155
  if (kind === "paragraph")
154
- fields.text.maxLength =
155
- ["authored-reference-detail-sidebar", "authored-reference-diagonal", "authored-prose-inset"].includes(id) ? 160 : 120;
156
+ fields.text.maxLength = [
157
+ "authored-reference-detail-sidebar",
158
+ "authored-reference-diagonal",
159
+ "authored-prose-inset",
160
+ ].includes(id)
161
+ ? 160
162
+ : 120;
156
163
  if (kind === "features" || kind === "steps") {
157
164
  const entries = fields.entries;
158
165
  entries.minItems = id === "authored-reference-story" ? 2 : count;
@@ -164,7 +171,24 @@ function fixedBlocks(recipe) {
164
171
  description: "Editable native illustration for the item. Choose the visual concept that explains its meaning. This field controls a graphic, not prose.",
165
172
  };
166
173
  entries.items.required.push("illustration");
167
- entries.items.properties.icon = { type: "string", enum: ["BsJournalText", "BsPersonCheck", "BsShare", "BsPeople", "BsSearch", "BsShieldCheck", "BsBarChart", "BsCodeSlash", "BsSignpostSplit", "BsCheck2Circle", "BsFolder2Open", "BsGrid1X2"], description: "Optional authored icon variant; illustration remains the primary visual concept." };
174
+ entries.items.properties.icon = {
175
+ type: "string",
176
+ enum: [
177
+ "BsJournalText",
178
+ "BsPersonCheck",
179
+ "BsShare",
180
+ "BsPeople",
181
+ "BsSearch",
182
+ "BsShieldCheck",
183
+ "BsBarChart",
184
+ "BsCodeSlash",
185
+ "BsSignpostSplit",
186
+ "BsCheck2Circle",
187
+ "BsFolder2Open",
188
+ "BsGrid1X2",
189
+ ],
190
+ description: "Optional authored icon variant; illustration remains the primary visual concept.",
191
+ };
168
192
  entries.items.properties.title.maxLength = 28;
169
193
  entries.items.properties.description.maxLength =
170
194
  id === "authored-feature-focus" ? 160 : 48;
@@ -177,7 +201,11 @@ function fixedBlocks(recipe) {
177
201
  id.includes("speaker-")
178
202
  ? 2
179
203
  : count;
180
- fields.rows.minItems = id.endsWith("speaker-lineup") ? 1 : id === "pricing" ? 2 : rows;
204
+ fields.rows.minItems = id.endsWith("speaker-lineup")
205
+ ? 1
206
+ : id === "pricing"
207
+ ? 2
208
+ : rows;
181
209
  fields.rows.maxItems = rows;
182
210
  fields.rows.items.properties.name.maxLength = 20;
183
211
  fields.rows.items.properties.value.maxLength = 24;
@@ -192,7 +220,8 @@ function fixedBlocks(recipe) {
192
220
  column.title.maxLength = 20;
193
221
  column.points.minItems = id === "authored-founder-comparison" ? 1 : 2;
194
222
  column.points.maxItems = 3;
195
- column.points.items.maxLength = id === "authored-founder-comparison" ? 64 : 40;
223
+ column.points.items.maxLength =
224
+ id === "authored-founder-comparison" ? 64 : 40;
196
225
  column.values.maxItems = 3;
197
226
  column.values.items.maxLength = 16;
198
227
  column.values.minItems = 0;
@@ -229,19 +258,61 @@ function fixedBlocks(recipe) {
229
258
  fields.chart = { const: "bar" };
230
259
  if (/donut|reference-share|reference-metrics/.test(id)) {
231
260
  fields.chart = { const: "bar" };
232
- fields.unit = { type: "string", maxLength: 24, pattern: "%", description: "Percentage unit, optionally qualified as illustrative or simulated." };
261
+ fields.unit = {
262
+ type: "string",
263
+ maxLength: 24,
264
+ pattern: "%",
265
+ description: "Percentage unit, optionally qualified as illustrative or simulated.",
266
+ };
233
267
  fields.values.description =
234
268
  "Nonnegative shares totaling exactly 100. Use labeled illustrative shares when the input has no comparable numbers.";
235
269
  fields.values.items.properties.value.minimum = 0;
236
270
  fields.values.items.properties.value.maximum = 100;
237
271
  }
238
272
  }
273
+ if (id.startsWith("authored-explore-")) {
274
+ if (kind === "paragraph") {
275
+ fields.text.maxLength = 220;
276
+ if (id !== "authored-explore-focus") {
277
+ delete fields.heading;
278
+ delete fields.level;
279
+ }
280
+ }
281
+ if (kind === "features" || kind === "steps") {
282
+ fields.entries.minItems = Math.max(2, recipe.minEntries);
283
+ fields.entries.maxItems = recipe.maxEntries;
284
+ fields.entries.items.properties.title.maxLength = 44;
285
+ fields.entries.items.properties.description.maxLength = 120;
286
+ delete fields.entries.items.properties.illustration;
287
+ delete fields.entries.items.properties.icon;
288
+ fields.entries.items.required = fields.entries.items.required.filter((key) => !["illustration", "icon"].includes(key));
289
+ }
290
+ if (kind === "parameters") {
291
+ fields.rows.minItems = recipe.minEntries;
292
+ fields.rows.maxItems = recipe.maxEntries;
293
+ fields.rows.items.properties.name.maxLength = 36;
294
+ fields.rows.items.properties.value.maxLength = 120;
295
+ fields.rows.items.properties.description.maxLength = 60;
296
+ }
297
+ if (kind === "comparison") {
298
+ fields.columns.items.properties.points.maxItems = 3;
299
+ fields.columns.items.properties.points.items.maxLength = 100;
300
+ for (const key of ["price", "values", "statuses"])
301
+ delete fields.columns.items.properties[key];
302
+ }
303
+ if (kind === "chart") {
304
+ fields.chart = { const: "bar" };
305
+ fields.values.minItems = 2;
306
+ fields.values.maxItems = 6;
307
+ fields.values.items.properties.value.minimum = 0;
308
+ }
309
+ }
239
310
  return schema;
240
311
  });
241
312
  return {
242
313
  type: "array",
243
314
  items,
244
- minItems: items.length,
315
+ minItems: id === "authored-explore-cover" ? 1 : items.length,
245
316
  maxItems: items.length,
246
317
  additionalItems: false,
247
318
  description: "Fixed ordered parameters for this renderer. Return every block exactly once in this order; do not add explanatory blocks.",
@@ -1,8 +1,10 @@
1
+ import { EXPLORE_THEMES, EXPLORE_RECIPES, exploreCompositionFits } from './exploreCollection.js';
1
2
  import { AUTHORED_COMPOSITIONS, sceneRecipePreference, } from "./compositionScenes.js";
2
3
  import { SOCIAL_LAYOUT_CATALOG } from "./socialLayouts.js";
3
4
  import { contentBlockText, } from "./content.js";
4
5
  /** Complete, deterministic token sets; decorative geometry belongs to individual compositions. */
5
6
  export const DESIGN_THEMES = [
7
+ ...EXPLORE_THEMES,
6
8
  {
7
9
  id: "neatico",
8
10
  palette: { background: "#FAF9F6", surface: "#FFFFFF", ink: "#202124", muted: "#62646B", accent: "#F4CE36", line: "#E4E1D9" },
@@ -180,7 +182,7 @@ export function designTheme(id) {
180
182
  plum: "electric",
181
183
  };
182
184
  return (DESIGN_THEMES.find((theme) => theme.id === (alias[id] || id)) ||
183
- DESIGN_THEMES[0]);
185
+ DESIGN_THEMES.find(theme => theme.id === "neatico"));
184
186
  }
185
187
  const family = (id, semantic) => {
186
188
  if (/code|terminal/.test(id))
@@ -362,6 +364,7 @@ export function canonicalRecipeId(id, kind) {
362
364
  }
363
365
  /** Existing authored catalogs remain first-class candidates, rather than being discarded. */
364
366
  const ALL_DESIGN_RECIPES = [
367
+ ...EXPLORE_RECIPES,
365
368
  ...["cover", "closing"].flatMap((role) => ["editorial", "panel", "split"].map((style) => ({
366
369
  id: `bookend-${role}-${style}`,
367
370
  kind: "presentation",
@@ -500,6 +503,7 @@ export function designCandidates(page, context) {
500
503
  const available = page.blocks.some((b) => b.kind === "image" && b.status === "available");
501
504
  const exclude = new Set(context.excluded || []);
502
505
  const candidates = DESIGN_RECIPES.filter((r) => r.kind === context.kind &&
506
+ (!r.id.startsWith("authored-explore-") || (context.ratio >= 1.4 && context.ratio <= 2 && exploreCompositionFits(page, r.id))) &&
503
507
  r.automatic !== false &&
504
508
  (!r.scenes || Boolean(page.scene && r.scenes.includes(page.scene))) &&
505
509
  !(r.family.startsWith("hero") &&
@@ -527,6 +531,7 @@ export function designCandidates(page, context) {
527
531
  ? r.family.startsWith("steps") || r.family.startsWith("code")
528
532
  : true;
529
533
  return ((r.id.startsWith("bookend-") ? 80 : 0) +
534
+ (r.id.startsWith("authored-explore-") ? 110 : 0) +
530
535
  (page.scene === "brand-collection" && r.id === "brand-collection-grid"
531
536
  ? 80
532
537
  : 0) +
@@ -0,0 +1,7 @@
1
+ import type { DesignRecipe, DesignTheme } from "./designSystem.js";
2
+ import type { ContentPage } from "./content.js";
3
+ export declare const DEFAULT_PRESENTATION_THEME = "explore-teal";
4
+ export declare const EXPLORE_THEMES: readonly DesignTheme[];
5
+ export declare const EXPLORE_LAYOUTS: readonly [readonly ["cover", "paragraph", 0, 0, "Open with a spacious editorial headline, a short premise and an optional real photograph."], readonly ["overview", "features", 2, 4, "Orient readers with a numbered overview of parallel subjects."], readonly ["section", "paragraph", 0, 0, "Introduce one new section or central principle in a strong color field."], readonly ["pillars", "features", 2, 3, "Explain two or three equally important ideas in spacious editorial columns."], readonly ["focus", "paragraph", 0, 0, "Develop one idea in an asymmetric reading composition."], readonly ["workflow", "steps", 2, 4, "Show an ordered workflow with explicit actions and useful descriptions."], readonly ["timeline", "steps", 2, 4, "Present a short chronological sequence on a horizontal reading axis."], readonly ["comparison", "comparison", 2, 2, "Compare two supplied alternatives with corresponding points."], readonly ["worksheet", "parameters", 2, 4, "Explain named fields with their concrete values and instructions."], readonly ["photo-story", "image", 0, 0, "Use a real contextual photograph beside a concise explanation."], readonly ["quote", "quote", 0, 0, "Give an exact source statement room to breathe, retaining supplied attribution."], readonly ["chart", "chart", 2, 6, "Compare source-backed quantities in labeled bars with the original unit."], readonly ["ledger", "parameters", 2, 5, "Arrange named facts and explanations in a flat editorial ledger."], readonly ["faq", "features", 2, 3, "Answer a small set of meaningful questions with concise responses."], readonly ["action-plan", "steps", 2, 3, "Make the next few actions prominent, with descriptions that explain the work."], readonly ["closing", "paragraph", 0, 0, "Close with one clear takeaway and a concrete next action."]];
6
+ export declare const EXPLORE_RECIPES: readonly DesignRecipe[];
7
+ export declare function exploreCompositionFits(page: ContentPage, id: string): boolean;
@@ -0,0 +1,177 @@
1
+ export const DEFAULT_PRESENTATION_THEME = "explore-teal";
2
+ /** Explore's editorial colorways, including light and solid-background variants. */
3
+ const colors = [
4
+ ["teal", "#214B4A", "#E1EEEB", "#F7FAF9", "#1C3533"],
5
+ ["slate", "#3F5477", "#E6EAF1", "#F9FAFC", "#1D283B"],
6
+ ["cobalt", "#2847A9", "#E5EBFC", "#F8FAFF", "#19294E"],
7
+ ["clay", "#9D4F32", "#F8E7DE", "#FFFAF6", "#3F2920"],
8
+ ["terracotta", "#AC3F25", "#FBE5DC", "#FFFAF5", "#3E241B"],
9
+ ["lavender", "#6A497B", "#EEE6F5", "#FCFAFD", "#34243D"],
10
+ ["petrol", "#315664", "#E2EDF1", "#F8FBFC", "#223E48"],
11
+ ["sand", "#6D5140", "#EFE4D8", "#FCFAF7", "#392C23"],
12
+ ["indigo", "#43387D", "#EAE5F6", "#FAF9FD", "#292444"],
13
+ ["olive", "#4F633A", "#E8EEDC", "#FBFCF8", "#2D3824"],
14
+ ["olive-classic", "#E8EEDC", "#607447", "#4F6339", "#FFFFFF"],
15
+ ["rose", "#9B3E61", "#F6E2EB", "#FFFAFC", "#402332"],
16
+ ["wine", "#7A3048", "#EFDEE5", "#FCF8FA", "#39202C"],
17
+ ["sunflower", "#8A5D12", "#F7EBC6", "#FFFDF5", "#392F1D"],
18
+ ["seafoam", "#216C62", "#DDF0E8", "#F7FCFA", "#193D36"],
19
+ ["graphite", "#D5B98C", "#30343A", "#1B1E23", "#F5F2EA"],
20
+ ["midnight", "#91B7DE", "#22344C", "#132236", "#F3F6FA"],
21
+ ["ice", "#27697B", "#DDEFF5", "#F8FCFE", "#193C49"],
22
+ ["copper", "#9A563B", "#F1E2D7", "#FCF9F5", "#3F2C23"],
23
+ ];
24
+ export const EXPLORE_THEMES = colors.map(([id, accent, surface, background, ink]) => ({
25
+ id: `explore-${id}`,
26
+ palette: { accent, surface, background, ink, muted: ink, line: surface },
27
+ typography: {
28
+ heading: "Playfair Display",
29
+ body: "Inter",
30
+ code: "JetBrains Mono",
31
+ headingWeight: 600,
32
+ },
33
+ geometry: { radius: 4, spacing: 1.1, rule: "hairline" },
34
+ }));
35
+ export const EXPLORE_LAYOUTS = [
36
+ [
37
+ "cover",
38
+ "paragraph",
39
+ 0,
40
+ 0,
41
+ "Open with a spacious editorial headline, a short premise and an optional real photograph.",
42
+ ],
43
+ [
44
+ "overview",
45
+ "features",
46
+ 2,
47
+ 4,
48
+ "Orient readers with a numbered overview of parallel subjects.",
49
+ ],
50
+ [
51
+ "section",
52
+ "paragraph",
53
+ 0,
54
+ 0,
55
+ "Introduce one new section or central principle in a strong color field.",
56
+ ],
57
+ [
58
+ "pillars",
59
+ "features",
60
+ 2,
61
+ 3,
62
+ "Explain two or three equally important ideas in spacious editorial columns.",
63
+ ],
64
+ [
65
+ "focus",
66
+ "paragraph",
67
+ 0,
68
+ 0,
69
+ "Develop one idea in an asymmetric reading composition.",
70
+ ],
71
+ [
72
+ "workflow",
73
+ "steps",
74
+ 2,
75
+ 4,
76
+ "Show an ordered workflow with explicit actions and useful descriptions.",
77
+ ],
78
+ [
79
+ "timeline",
80
+ "steps",
81
+ 2,
82
+ 4,
83
+ "Present a short chronological sequence on a horizontal reading axis.",
84
+ ],
85
+ [
86
+ "comparison",
87
+ "comparison",
88
+ 2,
89
+ 2,
90
+ "Compare two supplied alternatives with corresponding points.",
91
+ ],
92
+ [
93
+ "worksheet",
94
+ "parameters",
95
+ 2,
96
+ 4,
97
+ "Explain named fields with their concrete values and instructions.",
98
+ ],
99
+ [
100
+ "photo-story",
101
+ "image",
102
+ 0,
103
+ 0,
104
+ "Use a real contextual photograph beside a concise explanation.",
105
+ ],
106
+ [
107
+ "quote",
108
+ "quote",
109
+ 0,
110
+ 0,
111
+ "Give an exact source statement room to breathe, retaining supplied attribution.",
112
+ ],
113
+ [
114
+ "chart",
115
+ "chart",
116
+ 2,
117
+ 6,
118
+ "Compare source-backed quantities in labeled bars with the original unit.",
119
+ ],
120
+ [
121
+ "ledger",
122
+ "parameters",
123
+ 2,
124
+ 5,
125
+ "Arrange named facts and explanations in a flat editorial ledger.",
126
+ ],
127
+ [
128
+ "faq",
129
+ "features",
130
+ 2,
131
+ 3,
132
+ "Answer a small set of meaningful questions with concise responses.",
133
+ ],
134
+ [
135
+ "action-plan",
136
+ "steps",
137
+ 2,
138
+ 3,
139
+ "Make the next few actions prominent, with descriptions that explain the work.",
140
+ ],
141
+ [
142
+ "closing",
143
+ "paragraph",
144
+ 0,
145
+ 0,
146
+ "Close with one clear takeaway and a concrete next action.",
147
+ ],
148
+ ];
149
+ export const EXPLORE_RECIPES = EXPLORE_LAYOUTS.map(([id, kind, min, max]) => ({
150
+ id: `authored-explore-${id}`,
151
+ kind: "presentation",
152
+ renderer: "semantic",
153
+ automatic: true,
154
+ family: kind === "paragraph" ? `hero-explore-${id}` : kind,
155
+ minEntries: min,
156
+ maxEntries: max,
157
+ accepts: (["cover", "photo-story"].includes(id)
158
+ ? ["paragraph", "image"]
159
+ : [kind]),
160
+ requiresImage: id === "photo-story",
161
+ }));
162
+ export function exploreCompositionFits(page, id) {
163
+ if (!id.startsWith("authored-explore-"))
164
+ return true;
165
+ const variant = id.slice("authored-explore-".length);
166
+ if (variant === "cover")
167
+ return page.purpose === "cover";
168
+ if (variant === "closing")
169
+ return page.purpose === "conclude";
170
+ if (["cover", "conclude"].includes(page.purpose))
171
+ return false;
172
+ if (variant === "section")
173
+ return (page.blocks.length === 1 &&
174
+ page.blocks[0].kind === "paragraph" &&
175
+ page.blocks[0].text.length <= 160);
176
+ return true;
177
+ }
@@ -1,3 +1,4 @@
1
+ import { EXPLORE_LAYOUTS } from './exploreCollection.js';
1
2
  import { compositionUseCase } from './compositionUseCases.js';
2
3
  import { nativeSocialParameterContract } from './nativeSocialParameters.js';
3
4
  import { DESIGN_RECIPES } from './designSystem.js';
@@ -7,7 +8,7 @@ import { SEMANTIC_DISPLAY_NUMBERS, SOCIAL_DISPLAY_NUMBERS } from './compositionN
7
8
  const both = ['presentation', 'social'];
8
9
  const semantic = (id, accepts, minEntries = 0, maxEntries = 12, requiresImage = false) => ({ id, renderer: 'semantic', kind: 'social', family: id, accepts, minEntries, maxEntries, requiresImage });
9
10
  function define(recipe, kinds, id, displayNumber, min = .25, max = 4) {
10
- return { ...recipeParameterContract(recipe, kinds), semanticDescription: compositionUseCase(recipe.id), id, displayNumber, aspectRatios: { min, max }, recipe };
11
+ return { ...recipeParameterContract(recipe, kinds), semanticDescription: EXPLORE_LAYOUTS.find(item => recipe.id === `authored-explore-${item[0]}`)?.[4] || compositionUseCase(recipe.id), id, displayNumber, aspectRatios: { min, max }, recipe };
11
12
  }
12
13
  /** Structural reference contracts shared with the product's existing renderers.
13
14
  * Colorways and historical aliases remain outside active discovery.
@@ -100,7 +101,7 @@ export const FORMAL_COMPOSITIONS = [
100
101
  const recipe = DESIGN_RECIPES.find(item => item.id === preset.id && item.kind === 'social');
101
102
  return { ...define({ ...recipe, renderer: 'catalog' }, ['social'], `social:${preset.id}`, SOCIAL_DISPLAY_NUMBERS[preset.id], .5, 2.4), ...nativeSocialParameterContract(preset.id) };
102
103
  }),
103
- ...uniqueSemantic.map(recipe => define(recipe, semanticRecipes.filter(other => other.id === recipe.id).map(other => other.kind), `semantic:${recipe.kind}:${recipe.id}`, SEMANTIC_DISPLAY_NUMBERS[`semantic:${recipe.kind}:${recipe.id}`])),
104
+ ...uniqueSemantic.map(recipe => define(recipe, semanticRecipes.filter(other => other.id === recipe.id).map(other => other.kind), `semantic:${recipe.kind}:${recipe.id}`, recipe.id.startsWith("authored-explore-") ? `X${String(EXPLORE_LAYOUTS.findIndex(item => recipe.id === `authored-explore-${item[0]}`) + 1).padStart(2, "0")}` : SEMANTIC_DISPLAY_NUMBERS[`semantic:${recipe.kind}:${recipe.id}`], recipe.id.startsWith("authored-explore-") ? 1.4 : .25, recipe.id.startsWith("authored-explore-") ? 2 : 4)),
104
105
  ...founderDefinitions.map(([id, accepts, min, max], index) => define(semantic(id, accepts, min, max), ['social'], `semantic:social:${id}`, `F${index + 1}`, 1.3)),
105
106
  ...[0, 1, 2].flatMap(variant => [article(`article-cover-${variant}`, 'cover', variant), ...articleKinds.map(kind => article(`article-${kind}-${variant}`, kind, variant))]),
106
107
  ...[3, 4, 5, 6, 7, 8, 9, 10].map(variant => article(`article-features-${variant}`, 'features', variant)),
package/dist/index.d.ts CHANGED
@@ -23,3 +23,4 @@ export * from "./compositionUseCases.js";
23
23
  export { compositionParameterDefaults } from './parameterDefaults.js';
24
24
  export { SOCIAL_DISPLAY_NUMBERS } from './compositionNumbers.js';
25
25
  export * from './textPresentation.js';
26
+ export * from './exploreCollection.js';
package/dist/index.js CHANGED
@@ -23,3 +23,4 @@ export * from "./compositionUseCases.js";
23
23
  export { compositionParameterDefaults } from './parameterDefaults.js';
24
24
  export { SOCIAL_DISPLAY_NUMBERS } from './compositionNumbers.js';
25
25
  export * from './textPresentation.js';
26
+ export * from './exploreCollection.js';
@@ -1,5 +1,5 @@
1
1
  import { COMPOSITION_ILLUSTRATIONS } from "./nativeSocialParameters.js";
2
- import { textPresentationSchema } from './textPresentation.js';
2
+ import { textPresentationSchema } from "./textPresentation.js";
3
3
  import { DESIGN_RECIPES } from "./designSystem.js";
4
4
  const text = (description, maxLength = 600) => ({
5
5
  type: "string",
@@ -105,7 +105,9 @@ function blockSchema(kind, maximum) {
105
105
  function fixedBlocks(recipe) {
106
106
  const id = recipe.id;
107
107
  let kinds;
108
- if (id.endsWith("ledger-chart"))
108
+ if (["authored-explore-cover", "authored-explore-photo-story"].includes(id))
109
+ kinds = ["paragraph", "image"];
110
+ else if (id.endsWith("ledger-chart"))
109
111
  kinds = ["parameters", "chart"];
110
112
  else if (id.endsWith("detail-sidebar"))
111
113
  kinds = ["paragraph", "parameters"];
@@ -147,8 +149,13 @@ function fixedBlocks(recipe) {
147
149
  const fields = schema.properties;
148
150
  const count = Math.max(recipe.minEntries || 1, Math.min(3, recipe.maxEntries || 3));
149
151
  if (kind === "paragraph")
150
- fields.text.maxLength =
151
- ["authored-reference-detail-sidebar", "authored-reference-diagonal", "authored-prose-inset"].includes(id) ? 160 : 120;
152
+ fields.text.maxLength = [
153
+ "authored-reference-detail-sidebar",
154
+ "authored-reference-diagonal",
155
+ "authored-prose-inset",
156
+ ].includes(id)
157
+ ? 160
158
+ : 120;
152
159
  if (kind === "features" || kind === "steps") {
153
160
  const entries = fields.entries;
154
161
  entries.minItems = id === "authored-reference-story" ? 2 : count;
@@ -160,7 +167,24 @@ function fixedBlocks(recipe) {
160
167
  description: "Editable native illustration for the item. Choose the visual concept that explains its meaning. This field controls a graphic, not prose.",
161
168
  };
162
169
  entries.items.required.push("illustration");
163
- entries.items.properties.icon = { type: "string", enum: ["BsJournalText", "BsPersonCheck", "BsShare", "BsPeople", "BsSearch", "BsShieldCheck", "BsBarChart", "BsCodeSlash", "BsSignpostSplit", "BsCheck2Circle", "BsFolder2Open", "BsGrid1X2"], description: "Optional authored icon variant; illustration remains the primary visual concept." };
170
+ entries.items.properties.icon = {
171
+ type: "string",
172
+ enum: [
173
+ "BsJournalText",
174
+ "BsPersonCheck",
175
+ "BsShare",
176
+ "BsPeople",
177
+ "BsSearch",
178
+ "BsShieldCheck",
179
+ "BsBarChart",
180
+ "BsCodeSlash",
181
+ "BsSignpostSplit",
182
+ "BsCheck2Circle",
183
+ "BsFolder2Open",
184
+ "BsGrid1X2",
185
+ ],
186
+ description: "Optional authored icon variant; illustration remains the primary visual concept.",
187
+ };
164
188
  entries.items.properties.title.maxLength = 28;
165
189
  entries.items.properties.description.maxLength =
166
190
  id === "authored-feature-focus" ? 160 : 48;
@@ -173,7 +197,11 @@ function fixedBlocks(recipe) {
173
197
  id.includes("speaker-")
174
198
  ? 2
175
199
  : count;
176
- fields.rows.minItems = id.endsWith("speaker-lineup") ? 1 : id === "pricing" ? 2 : rows;
200
+ fields.rows.minItems = id.endsWith("speaker-lineup")
201
+ ? 1
202
+ : id === "pricing"
203
+ ? 2
204
+ : rows;
177
205
  fields.rows.maxItems = rows;
178
206
  fields.rows.items.properties.name.maxLength = 20;
179
207
  fields.rows.items.properties.value.maxLength = 24;
@@ -188,7 +216,8 @@ function fixedBlocks(recipe) {
188
216
  column.title.maxLength = 20;
189
217
  column.points.minItems = id === "authored-founder-comparison" ? 1 : 2;
190
218
  column.points.maxItems = 3;
191
- column.points.items.maxLength = id === "authored-founder-comparison" ? 64 : 40;
219
+ column.points.items.maxLength =
220
+ id === "authored-founder-comparison" ? 64 : 40;
192
221
  column.values.maxItems = 3;
193
222
  column.values.items.maxLength = 16;
194
223
  column.values.minItems = 0;
@@ -225,19 +254,61 @@ function fixedBlocks(recipe) {
225
254
  fields.chart = { const: "bar" };
226
255
  if (/donut|reference-share|reference-metrics/.test(id)) {
227
256
  fields.chart = { const: "bar" };
228
- fields.unit = { type: "string", maxLength: 24, pattern: "%", description: "Percentage unit, optionally qualified as illustrative or simulated." };
257
+ fields.unit = {
258
+ type: "string",
259
+ maxLength: 24,
260
+ pattern: "%",
261
+ description: "Percentage unit, optionally qualified as illustrative or simulated.",
262
+ };
229
263
  fields.values.description =
230
264
  "Nonnegative shares totaling exactly 100. Use labeled illustrative shares when the input has no comparable numbers.";
231
265
  fields.values.items.properties.value.minimum = 0;
232
266
  fields.values.items.properties.value.maximum = 100;
233
267
  }
234
268
  }
269
+ if (id.startsWith("authored-explore-")) {
270
+ if (kind === "paragraph") {
271
+ fields.text.maxLength = 220;
272
+ if (id !== "authored-explore-focus") {
273
+ delete fields.heading;
274
+ delete fields.level;
275
+ }
276
+ }
277
+ if (kind === "features" || kind === "steps") {
278
+ fields.entries.minItems = Math.max(2, recipe.minEntries);
279
+ fields.entries.maxItems = recipe.maxEntries;
280
+ fields.entries.items.properties.title.maxLength = 44;
281
+ fields.entries.items.properties.description.maxLength = 120;
282
+ delete fields.entries.items.properties.illustration;
283
+ delete fields.entries.items.properties.icon;
284
+ fields.entries.items.required = fields.entries.items.required.filter((key) => !["illustration", "icon"].includes(key));
285
+ }
286
+ if (kind === "parameters") {
287
+ fields.rows.minItems = recipe.minEntries;
288
+ fields.rows.maxItems = recipe.maxEntries;
289
+ fields.rows.items.properties.name.maxLength = 36;
290
+ fields.rows.items.properties.value.maxLength = 120;
291
+ fields.rows.items.properties.description.maxLength = 60;
292
+ }
293
+ if (kind === "comparison") {
294
+ fields.columns.items.properties.points.maxItems = 3;
295
+ fields.columns.items.properties.points.items.maxLength = 100;
296
+ for (const key of ["price", "values", "statuses"])
297
+ delete fields.columns.items.properties[key];
298
+ }
299
+ if (kind === "chart") {
300
+ fields.chart = { const: "bar" };
301
+ fields.values.minItems = 2;
302
+ fields.values.maxItems = 6;
303
+ fields.values.items.properties.value.minimum = 0;
304
+ }
305
+ }
235
306
  return schema;
236
307
  });
237
308
  return {
238
309
  type: "array",
239
310
  items,
240
- minItems: items.length,
311
+ minItems: id === "authored-explore-cover" ? 1 : items.length,
241
312
  maxItems: items.length,
242
313
  additionalItems: false,
243
314
  description: "Fixed ordered parameters for this renderer. Return every block exactly once in this order; do not add explanatory blocks.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neatico/visual-core",
3
- "version": "0.14.64",
3
+ "version": "0.14.66",
4
4
  "description": "Framework-independent geometry, measured text wrapping, and extensible scene registries for visual editors.",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",