@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
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createStyleManifest = createStyleManifest;
4
+ exports.validateStyleManifest = validateStyleManifest;
5
+ const designSystem_js_1 = require("./designSystem.js");
6
+ const fontCatalog_js_1 = require("./fontCatalog.js");
7
+ function createStyleManifest(themeId, copy = "", imagery) {
8
+ const theme = (0, designSystem_js_1.designTheme)(themeId);
9
+ if (!designSystem_js_1.DESIGN_THEMES.some((candidate) => candidate.id === themeId) &&
10
+ !["paper", "midnight", "ocean", "plum"].includes(themeId))
11
+ throw new Error("STYLE_UNKNOWN_THEME");
12
+ const manifest = {
13
+ version: 1,
14
+ themeId: theme.id,
15
+ themeVersion: 1,
16
+ theme: {
17
+ ...theme,
18
+ palette: { ...theme.palette },
19
+ typography: { ...theme.typography },
20
+ geometry: { ...theme.geometry },
21
+ },
22
+ fonts: {
23
+ heading: (0, fontCatalog_js_1.languageFont)(copy, theme.typography.heading),
24
+ body: (0, fontCatalog_js_1.languageFont)(copy, theme.typography.body),
25
+ code: theme.typography.code === "monospace"
26
+ ? "JetBrains Mono"
27
+ : theme.typography.code,
28
+ },
29
+ imagery: {
30
+ medium: "native",
31
+ referenceAssetIds: [],
32
+ direction: "",
33
+ ...imagery,
34
+ },
35
+ };
36
+ return validateStyleManifest(manifest);
37
+ }
38
+ function validateStyleManifest(value) {
39
+ const manifest = value;
40
+ const fail = () => {
41
+ throw new Error("STYLE_INVALID_MANIFEST");
42
+ };
43
+ if (!manifest ||
44
+ manifest.version !== 1 ||
45
+ manifest.themeVersion !== 1 ||
46
+ typeof manifest.themeId !== "string" ||
47
+ !manifest.theme ||
48
+ manifest.theme.id !== manifest.themeId ||
49
+ !designSystem_js_1.DESIGN_THEMES.some((theme) => theme.id === manifest.themeId))
50
+ return fail();
51
+ const { palette, typography, geometry } = manifest.theme;
52
+ if (!palette ||
53
+ ["background", "surface", "ink", "muted", "accent", "line"].some((key) => !/^#[\da-f]{6}$/i.test(palette[key] || "")))
54
+ return fail();
55
+ if (!typography ||
56
+ ["heading", "body", "code"].some((key) => typeof typography[key] !==
57
+ "string") ||
58
+ !Number.isInteger(typography.headingWeight) ||
59
+ typography.headingWeight < 100 ||
60
+ typography.headingWeight > 900)
61
+ return fail();
62
+ if (!geometry ||
63
+ !Number.isFinite(geometry.radius) ||
64
+ geometry.radius < 0 ||
65
+ geometry.radius > 100 ||
66
+ !Number.isFinite(geometry.spacing) ||
67
+ geometry.spacing <= 0 ||
68
+ geometry.spacing > 4 ||
69
+ !["none", "hairline", "accent"].includes(geometry.rule))
70
+ return fail();
71
+ if (!manifest.fonts ||
72
+ Object.keys(manifest.fonts).length !== 3 ||
73
+ ["heading", "body", "code"].some((key) => {
74
+ const font = manifest.fonts[key];
75
+ return typeof font !== "string" || !font.trim() || font.length > 100;
76
+ }))
77
+ return fail();
78
+ if (!manifest.imagery ||
79
+ !["native", "photography", "illustration", "3d"].includes(manifest.imagery.medium) ||
80
+ typeof manifest.imagery.direction !== "string" ||
81
+ manifest.imagery.direction.length > 2000 ||
82
+ !Array.isArray(manifest.imagery.referenceAssetIds) ||
83
+ manifest.imagery.referenceAssetIds.length > 16 ||
84
+ manifest.imagery.referenceAssetIds.some((id) => typeof id !== "string" || !id || id.length > 160))
85
+ return fail();
86
+ return {
87
+ version: 1,
88
+ themeId: manifest.themeId,
89
+ themeVersion: 1,
90
+ theme: {
91
+ id: manifest.themeId,
92
+ palette: { ...palette },
93
+ typography: { ...typography },
94
+ geometry: { ...geometry },
95
+ },
96
+ fonts: {
97
+ heading: manifest.fonts.heading,
98
+ body: manifest.fonts.body,
99
+ code: manifest.fonts.code,
100
+ },
101
+ imagery: {
102
+ medium: manifest.imagery.medium,
103
+ direction: manifest.imagery.direction,
104
+ referenceAssetIds: [...manifest.imagery.referenceAssetIds],
105
+ },
106
+ };
107
+ }
package/dist/cjs/text.js CHANGED
@@ -28,7 +28,7 @@ function wrapCopy(text, width, size, bold, measure, options = {}) {
28
28
  }
29
29
  const ranges = options.keepWords && typeof Intl.Segmenter === 'function' ? [...new Intl.Segmenter(undefined, { granularity: 'word' }).segment(paragraph)].filter(part => part.isWordLike).map(part => ({ start: part.index, end: part.index + part.segment.length, text: part.segment })) : [];
30
30
  // Keep a fitting URL/API token whole even where UAX permits a slash break.
31
- for (const match of paragraph.matchAll(/[A-Za-z0-9]+(?:[._/@+\-][A-Za-z0-9]+)+/g))
31
+ for (const match of paragraph.matchAll(/[A-Za-z0-9]+(?:[.:_/@+\-][A-Za-z0-9]+)+/g))
32
32
  ranges.push({ start: match.index, end: match.index + match[0].length, text: match[0] });
33
33
  const wrapAt = (available) => {
34
34
  const breaker = new linebreak_1.default(paragraph), tokens = [];
@@ -56,7 +56,17 @@ function wrapCopy(text, width, size, bold, measure, options = {}) {
56
56
  line = token.trimStart();
57
57
  continue;
58
58
  }
59
- const glyphs = typeof Intl.Segmenter === 'function' ? [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(token)].map(part => part.segment) : Array.from(token);
59
+ const graphemes = (value) => typeof Intl.Segmenter === 'function' ? [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(value)].map(part => part.segment) : Array.from(value);
60
+ // A range may be wider than a column, but a fitting clock must stay intact.
61
+ // Keep each clock as an atomic fallback unit instead of splitting its minutes.
62
+ const glyphs = [];
63
+ let cursor = 0;
64
+ for (const match of token.matchAll(/\d{1,2}:\d{2}(?::\d{2})?/g)) {
65
+ glyphs.push(...graphemes(token.slice(cursor, match.index)));
66
+ glyphs.push(...(measured(match[0]) <= available ? [match[0]] : graphemes(match[0])));
67
+ cursor = match.index + match[0].length;
68
+ }
69
+ glyphs.push(...graphemes(token.slice(cursor)));
60
70
  for (const glyph of glyphs) {
61
71
  if (glyph.trim() && measured(glyph) > available)
62
72
  throw new RangeError('A character exceeds the available width');
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.textPresentationSchema = textPresentationSchema;
4
+ exports.validateTextPresentations = validateTextPresentations;
5
+ const object = (properties) => ({
6
+ type: 'object', additionalProperties: false, properties, required: Object.keys(properties),
7
+ });
8
+ function textPresentationSchema() {
9
+ return {
10
+ type: 'array', minItems: 0, maxItems: 8, default: [],
11
+ description: 'Optional typography for visible text fields, including title, body, item descriptions and quotations. Use a few deliberate contrasts within the shared suite. Every line must preserve the original field text and word boundaries. Empty uses the authored typography. Never style code, evidence, IDs or asset metadata.',
12
+ items: object({
13
+ path: { type: 'string', minLength: 1, maxLength: 120, description: 'Exact dot path of the visible text field, such as body, items.0.description or blocks.0.text.' },
14
+ fit: { type: 'string', enum: ['natural', 'equal-width'], description: 'Equal-width adjusts each line independently within readable size limits; it never stretches glyphs.' },
15
+ lines: { type: 'array', minItems: 1, maxItems: 8, items: object({
16
+ text: { type: 'string', minLength: 1, maxLength: 220, description: 'One complete phrase copied exactly from the target field. All lines together must preserve that entire field.' },
17
+ font: { type: 'string', enum: ['display', 'body', 'mono'] },
18
+ scale: { type: 'number', minimum: 0.8, maximum: 1.35 },
19
+ tone: { type: 'string', enum: ['ink', 'accent', 'secondary'] },
20
+ decoration: { type: 'string', enum: ['none', 'marker', 'underline'] },
21
+ italic: { type: 'boolean' },
22
+ }) },
23
+ }),
24
+ };
25
+ }
26
+ const normalize = (text) => text.normalize('NFC').replace(/\s+/gu, ' ').trim();
27
+ const word = (character) => /[\p{Script=Latin}\p{Script=Cyrillic}\p{Script=Greek}\p{Number}]/u.test(character);
28
+ /** Validate exact source reconstruction; script-aware boundaries permit CJK breaks. */
29
+ function validateTextPresentations(value, content) {
30
+ if (value === undefined)
31
+ return [];
32
+ if (!Array.isArray(value) || value.length > 8)
33
+ throw new Error('TEXT_PRESENTATION_INVALID');
34
+ const paths = new Set();
35
+ for (const entry of value) {
36
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
37
+ throw new Error('TEXT_PRESENTATION_INVALID');
38
+ if (Object.keys(entry).some(key => !['path', 'fit', 'lines'].includes(key)))
39
+ throw new Error('TEXT_PRESENTATION_INVALID');
40
+ if (typeof entry.path !== 'string' || !/^(?:title|body|eyebrow|cta|value|attribution|(?:items|blocks)\.\d+\.(?:(?:entries|rows|columns)\.\d+\.)?(?:title|heading|text|description|name|value|attribution|points\.\d+))$/.test(entry.path) || paths.has(entry.path))
41
+ throw new Error('TEXT_PRESENTATION_PATH');
42
+ paths.add(entry.path);
43
+ const copy = entry.path.split('.').reduce((node, key) => node?.[key], content);
44
+ if (typeof copy !== 'string' || !copy.trim())
45
+ throw new Error('TEXT_PRESENTATION_PATH');
46
+ if (!['natural', 'equal-width'].includes(entry.fit) || !Array.isArray(entry.lines) || entry.lines.length < 1 || entry.lines.length > 8)
47
+ throw new Error('TEXT_PRESENTATION_INVALID');
48
+ const source = normalize(copy);
49
+ let cursor = 0;
50
+ for (const line of entry.lines) {
51
+ if (!line || typeof line !== 'object' || Array.isArray(line) || Object.keys(line).some(key => !['text', 'font', 'scale', 'tone', 'decoration', 'italic'].includes(key)))
52
+ throw new Error('TEXT_PRESENTATION_INVALID');
53
+ if (typeof line.text !== 'string' || !normalize(line.text) || Array.from(line.text).length > 220 || !['display', 'body', 'mono'].includes(line.font) || !Number.isFinite(line.scale) || line.scale < 0.8 || line.scale > 1.35 || !['ink', 'accent', 'secondary'].includes(line.tone) || !['none', 'marker', 'underline'].includes(line.decoration) || typeof line.italic !== 'boolean')
54
+ throw new Error('TEXT_PRESENTATION_INVALID');
55
+ const phrase = normalize(line.text);
56
+ if (source[cursor] === ' ')
57
+ cursor++;
58
+ if (!source.startsWith(phrase, cursor))
59
+ throw new Error('TEXT_PRESENTATION_CONTENT');
60
+ if (cursor && source[cursor - 1] !== ' ' && word(source[cursor - 1]) && word(phrase[0]))
61
+ throw new Error('TEXT_PRESENTATION_WORD_BOUNDARY');
62
+ cursor += phrase.length;
63
+ }
64
+ if (cursor !== source.length)
65
+ throw new Error('TEXT_PRESENTATION_CONTENT');
66
+ }
67
+ return value;
68
+ }
@@ -1,11 +1,11 @@
1
1
  /** Versioned data-only composition: executable CSS, HTML and URLs are not accepted. */
2
2
  export type CompositionNode = {
3
- type: 'row' | 'column' | 'text' | 'image' | 'divider' | 'icon';
3
+ type: "row" | "column" | "text" | "image" | "divider" | "icon";
4
4
  weight?: number;
5
- tone?: 'plain' | 'card' | 'contrast';
6
- role?: 'hero' | 'heading' | 'body' | 'caption';
5
+ tone?: "plain" | "card" | "contrast";
6
+ role?: "hero" | "heading" | "body" | "caption";
7
7
  bind?: string;
8
- icon?: 'idea' | 'check' | 'chart' | 'people' | 'shield' | 'arrow';
8
+ icon?: "idea" | "check" | "chart" | "people" | "shield" | "arrow";
9
9
  children?: CompositionNode[];
10
10
  };
11
11
  export interface Composition {
@@ -1,18 +1,29 @@
1
1
  export const COMPOSITION_PROMPT = `Also return composition:{version:1,root:node}. Design a hierarchy from the content, not a fixed template. A node is {type,weight?,tone?,role?,bind?,icon?,children?}. row and column contain 2-4 children; nest up to 5 levels, at most 40 nodes. Weight is relative space 0.5-4. Tone plain/card/contrast applies to groups. Text binds exactly one existing nonempty content field (title or headline, body, eyebrow, cta, value, attribution, items.0.title, items.0.description, etc); never bind notes. Include EVERY nonempty content field exactly once. Text role hero/heading/body/caption controls typography. Image is an optional original conceptual illustration, not invented evidence or a photo. Icon optionally uses idea/check/chart/people/shield/arrow. Divider separates related content. No coordinates, CSS, HTML, URLs or text in the tree. Use one strong hero, purposeful contrast, asymmetric columns, grouped evidence, and breathing room. Rows reflow for narrow formats. Different stories should have different trees, not just different colors.`;
2
2
  export function compositionFields(value) {
3
3
  const fields = {};
4
- for (const key of ['title', 'headline', 'body', 'eyebrow', 'cta', 'value', 'attribution'])
5
- if (typeof value[key] === 'string' && value[key].trim())
4
+ for (const key of [
5
+ "title",
6
+ "headline",
7
+ "body",
8
+ "eyebrow",
9
+ "cta",
10
+ "value",
11
+ "attribution",
12
+ ])
13
+ if (typeof value[key] === "string" && value[key].trim())
6
14
  fields[key] = value[key];
7
15
  if (Array.isArray(value.items))
8
- value.items.forEach((item, i) => { if (!item || typeof item !== 'object')
9
- return; for (const key of ['title', 'description']) {
10
- if (!Object.prototype.hasOwnProperty.call(item, key))
11
- continue;
12
- const v = item[key];
13
- if (typeof v === 'string' && v.trim())
14
- fields[`items.${i}.${key}`] = v;
15
- } });
16
+ value.items.forEach((item, i) => {
17
+ if (!item || typeof item !== "object")
18
+ return;
19
+ for (const key of ["title", "description"]) {
20
+ if (!Object.prototype.hasOwnProperty.call(item, key))
21
+ continue;
22
+ const v = item[key];
23
+ if (typeof v === "string" && v.trim())
24
+ fields[`items.${i}.${key}`] = v;
25
+ }
26
+ });
16
27
  return fields;
17
28
  }
18
29
  export function validateComposition(raw, fields) {
@@ -20,44 +31,59 @@ export function validateComposition(raw, fields) {
20
31
  let count = 0, images = 0;
21
32
  const seen = new Set();
22
33
  if (!value || value.version !== 1)
23
- throw new Error('Unsupported composition');
34
+ throw new Error("Unsupported composition");
24
35
  function visit(n, depth) {
25
- if (!n || typeof n !== 'object' || ++count > 40 || depth > 5 || !['row', 'column', 'text', 'image', 'divider', 'icon'].includes(n.type))
26
- throw new Error('Invalid composition node');
27
- if (n.weight !== undefined && (!Number.isFinite(n.weight) || n.weight < .5 || n.weight > 4))
28
- throw new Error('Invalid composition weight');
29
- if (n.tone !== undefined && !['plain', 'card', 'contrast'].includes(n.tone))
30
- throw new Error('Invalid composition tone');
31
- const out = { type: n.type, weight: n.weight, tone: n.tone };
32
- if (n.type === 'row' || n.type === 'column') {
33
- if (!Array.isArray(n.children) || n.children.length < 2 || n.children.length > 4)
34
- throw new Error('Invalid composition group');
35
- out.children = n.children.map(c => visit(c, depth + 1));
36
+ if (!n ||
37
+ typeof n !== "object" ||
38
+ ++count > 40 ||
39
+ depth > 5 ||
40
+ !["row", "column", "text", "image", "divider", "icon"].includes(n.type))
41
+ throw new Error("Invalid composition node");
42
+ if (n.weight !== undefined &&
43
+ (!Number.isFinite(n.weight) || n.weight < 0.5 || n.weight > 4))
44
+ throw new Error("Invalid composition weight");
45
+ if (n.tone !== undefined && !["plain", "card", "contrast"].includes(n.tone))
46
+ throw new Error("Invalid composition tone");
47
+ const out = {
48
+ type: n.type,
49
+ weight: n.weight,
50
+ tone: n.tone,
51
+ };
52
+ if (n.type === "row" || n.type === "column") {
53
+ if (!Array.isArray(n.children) ||
54
+ n.children.length < 2 ||
55
+ n.children.length > 4)
56
+ throw new Error("Invalid composition group");
57
+ out.children = n.children.map((c) => visit(c, depth + 1));
36
58
  }
37
59
  else {
38
60
  if (n.children !== undefined)
39
- throw new Error('Leaf cannot have children');
40
- if (n.type === 'text') {
41
- if (!n.bind || !Object.prototype.hasOwnProperty.call(fields, n.bind) || seen.has(n.bind))
42
- throw new Error('Invalid or duplicated binding');
43
- if (n.role !== undefined && !['hero', 'heading', 'body', 'caption'].includes(n.role))
44
- throw new Error('Invalid text role');
61
+ throw new Error("Leaf cannot have children");
62
+ if (n.type === "text") {
63
+ if (!n.bind ||
64
+ !Object.prototype.hasOwnProperty.call(fields, n.bind) ||
65
+ seen.has(n.bind))
66
+ throw new Error("Invalid or duplicated binding");
67
+ if (n.role !== undefined &&
68
+ !["hero", "heading", "body", "caption"].includes(n.role))
69
+ throw new Error("Invalid text role");
45
70
  seen.add(n.bind);
46
71
  out.bind = n.bind;
47
- out.role = n.role || 'body';
72
+ out.role = n.role || "body";
48
73
  }
49
- if (n.type === 'image' && ++images > 1)
50
- throw new Error('Only one illustration per composition');
51
- if (n.type === 'icon') {
52
- if (!n.icon || !['idea', 'check', 'chart', 'people', 'shield', 'arrow'].includes(n.icon))
53
- throw new Error('Invalid icon');
74
+ if (n.type === "image" && ++images > 1)
75
+ throw new Error("Only one illustration per composition");
76
+ if (n.type === "icon") {
77
+ if (!n.icon ||
78
+ !["idea", "check", "chart", "people", "shield", "arrow"].includes(n.icon))
79
+ throw new Error("Invalid icon");
54
80
  out.icon = n.icon;
55
81
  }
56
82
  }
57
83
  return out;
58
84
  }
59
85
  const root = visit(value.root, 0);
60
- if (Object.keys(fields).some(key => !seen.has(key)))
61
- throw new Error('Composition omits content');
86
+ if (Object.keys(fields).some((key) => !seen.has(key)))
87
+ throw new Error("Composition omits content");
62
88
  return { version: 1, root };
63
89
  }
@@ -0,0 +1,4 @@
1
+ /** Stable public display numbers, independent of runtime registration order. */
2
+ export declare const SEMANTIC_DISPLAY_NUMBERS: Readonly<Record<string, string>>;
3
+ /** Social numbers follow the established formal gallery, not registration order. */
4
+ export declare const SOCIAL_DISPLAY_NUMBERS: Readonly<Record<string, string>>;
@@ -0,0 +1,108 @@
1
+ /** Stable public display numbers, independent of runtime registration order. */
2
+ export const SEMANTIC_DISPLAY_NUMBERS = {
3
+ "semantic:presentation:bookend-cover-editorial": "M001",
4
+ "semantic:presentation:bookend-cover-panel": "M002",
5
+ "semantic:presentation:bookend-cover-split": "M003",
6
+ "semantic:presentation:bookend-closing-editorial": "M004",
7
+ "semantic:presentation:bookend-closing-panel": "M005",
8
+ "semantic:presentation:bookend-closing-split": "M006",
9
+ "semantic:presentation:brand-collection-grid": "M007",
10
+ "semantic:social:time-notice": "M099",
11
+ "semantic:social:ticket": "M100",
12
+ "semantic:social:webinar": "M101",
13
+ "semantic:social:pricing": "M102",
14
+ "semantic:social:offer": "M103",
15
+ "semantic:social:hiring": "M104",
16
+ "semantic:social:community": "M105",
17
+ "semantic:social:menu": "M106",
18
+ "semantic:social:itinerary": "M107",
19
+ "semantic:social:schedule": "M108",
20
+ "semantic:presentation:authored-plan-cards": "M109",
21
+ "semantic:presentation:authored-image-banner": "M009",
22
+ "semantic:presentation:authored-image-split": "M010",
23
+ "semantic:presentation:authored-image-editorial": "M011",
24
+ "semantic:presentation:authored-image-campaign": "M012",
25
+ "semantic:presentation:authored-image-inset": "M013",
26
+ "semantic:presentation:authored-prose-inset": "M014",
27
+ "semantic:presentation:authored-feature-focus": "M015",
28
+ "semantic:presentation:authored-feature-bento": "M016",
29
+ "semantic:presentation:authored-feature-index": "M017",
30
+ "semantic:presentation:authored-steps-journey": "M018",
31
+ "semantic:presentation:authored-steps-ribbon": "M019",
32
+ "semantic:presentation:authored-comparison-panels": "M020",
33
+ "semantic:presentation:authored-quote-editorial": "M021",
34
+ "semantic:presentation:authored-quote-poster": "M022",
35
+ "semantic:presentation:authored-code-editorial": "M023",
36
+ "semantic:presentation:authored-parameter-ledger": "M024",
37
+ "semantic:presentation:authored-parameter-cards": "M025",
38
+ "semantic:presentation:semantic-code-split": "M026",
39
+ "semantic:presentation:semantic-parameters-split": "M029",
40
+ "semantic:presentation:semantic-parameters-stack": "M030",
41
+ "semantic:presentation:semantic-steps-split": "M032",
42
+ "semantic:presentation:semantic-steps-stack": "M033",
43
+ "semantic:presentation:semantic-steps-editorial": "M034",
44
+ "semantic:presentation:semantic-comparison-split": "M035",
45
+ "semantic:presentation:semantic-features-split": "M038",
46
+ "semantic:presentation:semantic-features-stack": "M039",
47
+ "semantic:presentation:semantic-features-editorial": "M040",
48
+ "semantic:presentation:semantic-quote-split": "M041",
49
+ "semantic:presentation:semantic-chart-split": "M044",
50
+ "semantic:presentation:semantic-chart-stack": "M045",
51
+ "semantic:presentation:semantic-prose-split": "M050",
52
+ "semantic:presentation:semantic-prose-stack": "M051",
53
+ "semantic:presentation:semantic-prose-editorial": "M052",
54
+ "semantic:presentation:semantic-mixed-stack": "M053",
55
+ "semantic:social:semantic-hero-editorial": "M094"
56
+ };
57
+ /** Social numbers follow the established formal gallery, not registration order. */
58
+ export const SOCIAL_DISPLAY_NUMBERS = {
59
+ "note-question": "S001",
60
+ "note-marker": "S002",
61
+ "note-editorial": "S003",
62
+ "note-checklist": "S004",
63
+ "note-photo": "S005",
64
+ "note-news": "S006",
65
+ "chat-messenger": "S007",
66
+ "chat-imessage": "S008",
67
+ "chat-telegram": "S009",
68
+ "chat-workplace": "S010",
69
+ "time-notice": "S011",
70
+ "news-focus": "S012",
71
+ "usage-breakdown": "S013",
72
+ "organization": "S014",
73
+ "editorial-quote": "S015",
74
+ "research-highlight": "S016",
75
+ "product-showcase": "S017",
76
+ "manifesto": "S018",
77
+ "launch": "S019",
78
+ "bento": "S020",
79
+ "spotlight": "S021",
80
+ "ticket": "S022",
81
+ "webinar": "S023",
82
+ "quote": "S024",
83
+ "testimonial": "S025",
84
+ "metric": "S026",
85
+ "report": "S027",
86
+ "checklist": "S028",
87
+ "steps": "S029",
88
+ "comparison": "S030",
89
+ "myth": "S031",
90
+ "pricing": "S032",
91
+ "offer": "S033",
92
+ "editorial": "S034",
93
+ "story": "S035",
94
+ "hiring": "S036",
95
+ "team": "S037",
96
+ "course": "S038",
97
+ "case-study": "S039",
98
+ "notice": "S040",
99
+ "community": "S041",
100
+ "menu": "S042",
101
+ "specification": "S043",
102
+ "itinerary": "S044",
103
+ "schedule": "S045",
104
+ "safety": "S046",
105
+ "faq": "S047",
106
+ "split-bar-compare": "S048",
107
+ "benchmark-bars": "S049"
108
+ };
@@ -1,11 +1,11 @@
1
- import type { SemanticKind } from './content.js';
1
+ import type { SemanticKind } from "./content.js";
2
2
  /** Product scenes describe user intent; recipes describe an executable composition.
3
3
  * Multiple scenes deliberately reuse a recipe. Scene count is not design count.
4
4
  */
5
5
  export interface CompositionScene {
6
6
  id: string;
7
7
  kinds: readonly SemanticKind[];
8
- outputs: readonly ('presentation' | 'social')[];
8
+ outputs: readonly ("presentation" | "social")[];
9
9
  requiresAsset?: boolean;
10
10
  }
11
11
  export declare const COMPOSITION_SCENES: readonly CompositionScene[];
@@ -1,47 +1,100 @@
1
- const both = ['presentation', 'social'];
1
+ const both = ["presentation", "social"];
2
2
  export const COMPOSITION_SCENES = [
3
- { id: 'brand-collection', kinds: ['features'], outputs: both },
4
- { id: 'hero', kinds: ['paragraph'], outputs: both }, { id: 'chapter', kinds: ['paragraph'], outputs: ['presentation'] },
5
- { id: 'statement', kinds: ['paragraph'], outputs: both }, { id: 'closing', kinds: ['paragraph'], outputs: both },
6
- { id: 'product-launch', kinds: ['features'], outputs: both }, { id: 'feature-spotlight', kinds: ['features'], outputs: both },
7
- { id: 'feature-overview', kinds: ['features'], outputs: both }, { id: 'benefits', kinds: ['features'], outputs: both },
8
- { id: 'comparison', kinds: ['comparison'], outputs: both }, { id: 'before-after', kinds: ['comparison'], outputs: both },
9
- { id: 'plan-options', kinds: ['comparison'], outputs: both }, { id: 'process', kinds: ['steps'], outputs: both },
10
- { id: 'roadmap', kinds: ['steps'], outputs: both }, { id: 'tutorial', kinds: ['steps'], outputs: both },
11
- { id: 'checklist', kinds: ['features'], outputs: both }, { id: 'metrics', kinds: ['chart'], outputs: both },
12
- { id: 'trend', kinds: ['chart'], outputs: both }, { id: 'results', kinds: ['chart'], outputs: both },
13
- { id: 'quotation', kinds: ['quote'], outputs: both }, { id: 'testimonial', kinds: ['quote'], outputs: both },
14
- { id: 'code-example', kinds: ['code'], outputs: both }, { id: 'specifications', kinds: ['parameters'], outputs: both },
15
- { id: 'pricing-details', kinds: ['parameters'], outputs: both }, { id: 'event-details', kinds: ['parameters'], outputs: both },
16
- { id: 'recruitment-details', kinds: ['parameters'], outputs: both },
17
- { id: 'product-demonstration', kinds: ['image'], outputs: both, requiresAsset: true },
18
- { id: 'photograph', kinds: ['image'], outputs: both, requiresAsset: true },
19
- ...['banner-hero', 'product-showcase', 'editorial-cover', 'image-campaign'].map(id => ({ id, kinds: ['image'], outputs: both, requiresAsset: true })),
3
+ { id: "menu-details", kinds: ["parameters"], outputs: both },
4
+ { id: "travel-details", kinds: ["parameters"], outputs: both },
5
+ { id: "offer-details", kinds: ["parameters"], outputs: both },
6
+ { id: "questions", kinds: ["features"], outputs: both },
7
+ { id: "case-study", kinds: ["features"], outputs: both },
8
+ { id: "brand-collection", kinds: ["features"], outputs: both },
9
+ { id: "hero", kinds: ["paragraph"], outputs: both },
10
+ { id: "chapter", kinds: ["paragraph"], outputs: ["presentation"] },
11
+ { id: "statement", kinds: ["paragraph"], outputs: both },
12
+ { id: "closing", kinds: ["paragraph"], outputs: both },
13
+ { id: "product-launch", kinds: ["features"], outputs: both },
14
+ { id: "feature-spotlight", kinds: ["features"], outputs: both },
15
+ { id: "feature-overview", kinds: ["features"], outputs: both },
16
+ { id: "benefits", kinds: ["features"], outputs: both },
17
+ { id: "comparison", kinds: ["comparison"], outputs: both },
18
+ { id: "before-after", kinds: ["comparison"], outputs: both },
19
+ { id: "plan-options", kinds: ["comparison"], outputs: both },
20
+ { id: "process", kinds: ["steps"], outputs: both },
21
+ { id: "roadmap", kinds: ["steps"], outputs: both },
22
+ { id: "tutorial", kinds: ["steps"], outputs: both },
23
+ { id: "checklist", kinds: ["features"], outputs: both },
24
+ { id: "metrics", kinds: ["chart"], outputs: both },
25
+ { id: "trend", kinds: ["chart"], outputs: both },
26
+ { id: "results", kinds: ["chart"], outputs: both },
27
+ { id: "quotation", kinds: ["quote"], outputs: both },
28
+ { id: "testimonial", kinds: ["quote"], outputs: both },
29
+ { id: "code-example", kinds: ["code"], outputs: both },
30
+ { id: "specifications", kinds: ["parameters"], outputs: both },
31
+ { id: "pricing-details", kinds: ["parameters"], outputs: both },
32
+ { id: "event-details", kinds: ["parameters"], outputs: both },
33
+ { id: "recruitment-details", kinds: ["parameters"], outputs: both },
34
+ {
35
+ id: "product-demonstration",
36
+ kinds: ["image"],
37
+ outputs: both,
38
+ requiresAsset: true,
39
+ },
40
+ { id: "photograph", kinds: ["image"], outputs: both, requiresAsset: true },
41
+ ...[
42
+ "banner-hero",
43
+ "product-showcase",
44
+ "editorial-cover",
45
+ "image-campaign",
46
+ ].map((id) => ({
47
+ id,
48
+ kinds: ["image"],
49
+ outputs: both,
50
+ requiresAsset: true,
51
+ })),
20
52
  ];
21
53
  export const AUTHORED_COMPOSITIONS = [
22
- { id: 'feature-focus', kind: 'features', min: 1, max: 1 },
23
- { id: 'feature-bento', kind: 'features', min: 2, max: 6 },
24
- { id: 'feature-index', kind: 'features', min: 2, max: 6 },
25
- { id: 'steps-journey', kind: 'steps', min: 2, max: 6 },
26
- { id: 'steps-ribbon', kind: 'steps', min: 2, max: 4 },
27
- { id: 'comparison-panels', kind: 'comparison', min: 2, max: 3 },
28
- { id: 'quote-editorial', kind: 'quote', min: 0, max: 0 },
29
- { id: 'quote-poster', kind: 'quote', min: 0, max: 0 },
30
- { id: 'code-editorial', kind: 'code', min: 0, max: 0 },
31
- { id: 'parameter-ledger', kind: 'parameters', min: 1, max: 8 },
32
- { id: 'parameter-cards', kind: 'parameters', min: 1, max: 4 },
54
+ { id: "feature-focus", kind: "features", min: 1, max: 1 },
55
+ { id: "feature-bento", kind: "features", min: 2, max: 6 },
56
+ { id: "feature-index", kind: "features", min: 2, max: 6 },
57
+ { id: "steps-journey", kind: "steps", min: 2, max: 6 },
58
+ { id: "steps-ribbon", kind: "steps", min: 2, max: 4 },
59
+ { id: "comparison-panels", kind: "comparison", min: 2, max: 3 },
60
+ { id: "quote-editorial", kind: "quote", min: 0, max: 0 },
61
+ { id: "quote-poster", kind: "quote", min: 0, max: 0 },
62
+ { id: "code-editorial", kind: "code", min: 0, max: 0 },
63
+ { id: "parameter-ledger", kind: "parameters", min: 1, max: 8 },
64
+ { id: "parameter-cards", kind: "parameters", min: 1, max: 4 },
33
65
  ];
34
66
  /** Soft art-direction preferences; capacity and measured quality always remain mandatory. */
35
67
  export function sceneRecipePreference(scene, recipeId) {
36
68
  const preferred = {
37
- 'banner-hero': ['authored-image-banner'], 'product-showcase': ['authored-image-split'], 'editorial-cover': ['authored-image-editorial'], 'image-campaign': ['authored-image-campaign'],
38
- hero: ['cover', 'semantic-hero-split'], chapter: ['section', 'semantic-hero-editorial'], statement: ['statement', 'semantic-hero-stack'], closing: ['closing'],
39
- 'product-launch': ['authored-feature-bento'], 'feature-spotlight': ['authored-feature-focus'], 'feature-overview': ['authored-feature-bento', 'three-column'], benefits: ['authored-feature-index'],
40
- comparison: ['authored-comparison-panels'], 'before-after': ['before-after', 'semantic-comparison-split'], 'plan-options': ['authored-comparison-panels'],
41
- process: ['authored-steps-ribbon'], roadmap: ['authored-steps-ribbon', 'roadmap'], tutorial: ['authored-steps-journey'], checklist: ['authored-feature-index', 'checklist'],
42
- quotation: ['authored-quote-editorial'], testimonial: ['authored-quote-poster'],
43
- 'code-example': ['authored-code-editorial'], specifications: ['authored-parameter-ledger'],
44
- 'pricing-details': ['authored-parameter-cards'], 'event-details': ['authored-parameter-cards'], 'recruitment-details': ['authored-parameter-ledger'],
69
+ "banner-hero": ["authored-image-banner"],
70
+ "product-showcase": ["authored-image-split"],
71
+ "editorial-cover": ["authored-image-editorial"],
72
+ "image-campaign": ["authored-image-campaign"],
73
+ hero: ["cover", "semantic-hero-split"],
74
+ chapter: ["section", "semantic-hero-editorial"],
75
+ statement: ["statement", "semantic-hero-stack"],
76
+ closing: ["closing"],
77
+ "product-launch": ["authored-feature-bento"],
78
+ "feature-spotlight": ["authored-feature-focus"],
79
+ "feature-overview": ["authored-feature-bento", "three-column"],
80
+ benefits: ["authored-feature-index"],
81
+ comparison: ["authored-comparison-panels"],
82
+ "before-after": ["before-after", "semantic-comparison-split"],
83
+ "plan-options": ["authored-comparison-panels"],
84
+ process: ["authored-steps-ribbon"],
85
+ roadmap: ["authored-steps-ribbon", "roadmap"],
86
+ tutorial: ["authored-steps-journey"],
87
+ checklist: ["authored-feature-index", "checklist"],
88
+ quotation: ["authored-quote-editorial"],
89
+ testimonial: ["authored-quote-poster"],
90
+ "code-example": ["authored-code-editorial"],
91
+ specifications: ["authored-parameter-ledger"],
92
+ "pricing-details": ["authored-parameter-cards"],
93
+ "event-details": ["authored-parameter-cards"],
94
+ "recruitment-details": ["authored-parameter-ledger"],
45
95
  };
46
- return scene && preferred[scene]?.some(id => recipeId === id || recipeId.startsWith(`${id}--`)) ? 6 : 0;
96
+ return scene &&
97
+ preferred[scene]?.some((id) => recipeId === id || recipeId.startsWith(`${id}--`))
98
+ ? 6
99
+ : 0;
47
100
  }
@@ -0,0 +1 @@
1
+ export declare function compositionUseCase(id: string): string;