@neatico/visual-core 0.14.26 โ†’ 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 (43) 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/compositionNumbers.js +111 -0
  5. package/dist/cjs/compositionUseCases.js +145 -0
  6. package/dist/cjs/content.js +46 -10
  7. package/dist/cjs/contentBrief.js +82 -0
  8. package/dist/cjs/designSystem.js +80 -8
  9. package/dist/cjs/fontCatalog.js +7 -1
  10. package/dist/cjs/formalCompositions.js +120 -0
  11. package/dist/cjs/index.js +13 -0
  12. package/dist/cjs/nativeSocialParameters.js +145 -0
  13. package/dist/cjs/pagination.js +14 -5
  14. package/dist/cjs/parameterDefaults.js +20 -0
  15. package/dist/cjs/recipeParameters.js +304 -0
  16. package/dist/cjs/styleManifest.js +107 -0
  17. package/dist/cjs/textPresentation.js +68 -0
  18. package/dist/compositionNumbers.d.ts +4 -0
  19. package/dist/compositionNumbers.js +108 -0
  20. package/dist/compositionUseCases.d.ts +1 -0
  21. package/dist/compositionUseCases.js +142 -0
  22. package/dist/content.d.ts +10 -0
  23. package/dist/content.js +46 -10
  24. package/dist/contentBrief.d.ts +16 -0
  25. package/dist/contentBrief.js +78 -0
  26. package/dist/designSystem.js +80 -8
  27. package/dist/fontCatalog.js +7 -1
  28. package/dist/formalCompositions.d.ts +26 -0
  29. package/dist/formalCompositions.js +116 -0
  30. package/dist/index.d.ts +10 -0
  31. package/dist/index.js +10 -0
  32. package/dist/nativeSocialParameters.d.ts +38 -0
  33. package/dist/nativeSocialParameters.js +139 -0
  34. package/dist/pagination.js +14 -5
  35. package/dist/parameterDefaults.d.ts +2 -0
  36. package/dist/parameterDefaults.js +17 -0
  37. package/dist/recipeParameters.d.ts +34 -0
  38. package/dist/recipeParameters.js +300 -0
  39. package/dist/styleManifest.d.ts +20 -0
  40. package/dist/styleManifest.js +103 -0
  41. package/dist/textPresentation.d.ts +29 -0
  42. package/dist/textPresentation.js +64 -0
  43. package/package.json +1 -1
@@ -14,9 +14,81 @@ exports.DESIGN_THEMES = [
14
14
  {
15
15
  id: "neatico",
16
16
  palette: { background: "#FAF9F6", surface: "#FFFFFF", ink: "#202124", muted: "#62646B", accent: "#F4CE36", line: "#E4E1D9" },
17
- typography: { heading: "Inter", body: "Inter", code: "monospace", headingWeight: 700 },
17
+ typography: { heading: "Manrope", body: "Inter", code: "monospace", headingWeight: 700 },
18
18
  geometry: { radius: 12, spacing: 1, rule: "accent" },
19
19
  },
20
+ {
21
+ id: "archive",
22
+ palette: { background: "#F6F1E7", surface: "#FFFCF5", ink: "#19382E", muted: "#59665B", accent: "#C17A35", line: "#D7CEBC" },
23
+ typography: { heading: "Instrument Serif", body: "DM Sans", code: "JetBrains Mono", headingWeight: 400 },
24
+ geometry: { radius: 4, spacing: 1.1, rule: "hairline" },
25
+ },
26
+ {
27
+ "id": "bloom",
28
+ "palette": {
29
+ "background": "#FFF1F6",
30
+ "surface": "#FFFFFF",
31
+ "ink": "#341838",
32
+ "muted": "#70536D",
33
+ "accent": "#CC1D61",
34
+ "line": "#F3BED5"
35
+ },
36
+ "typography": {
37
+ "heading": "Montserrat",
38
+ "body": "Nunito Sans",
39
+ "code": "JetBrains Mono",
40
+ "headingWeight": 800
41
+ },
42
+ "geometry": {
43
+ "radius": 20,
44
+ "spacing": 1,
45
+ "rule": "accent"
46
+ }
47
+ },
48
+ {
49
+ "id": "splash",
50
+ "palette": {
51
+ "background": "#E9FAFF",
52
+ "surface": "#FFFFFF",
53
+ "ink": "#162E60",
54
+ "muted": "#4D637E",
55
+ "accent": "#2454E6",
56
+ "line": "#B8DEFA"
57
+ },
58
+ "typography": {
59
+ "heading": "Space Grotesk",
60
+ "body": "DM Sans",
61
+ "code": "JetBrains Mono",
62
+ "headingWeight": 700
63
+ },
64
+ "geometry": {
65
+ "radius": 20,
66
+ "spacing": 1,
67
+ "rule": "accent"
68
+ }
69
+ },
70
+ {
71
+ "id": "candy",
72
+ "palette": {
73
+ "background": "#F3EDFF",
74
+ "surface": "#FFFFFF",
75
+ "ink": "#35205C",
76
+ "muted": "#69557F",
77
+ "accent": "#7841D5",
78
+ "line": "#DCCAF6"
79
+ },
80
+ "typography": {
81
+ "heading": "Manrope",
82
+ "body": "Nunito Sans",
83
+ "code": "JetBrains Mono",
84
+ "headingWeight": 800
85
+ },
86
+ "geometry": {
87
+ "radius": 20,
88
+ "spacing": 1,
89
+ "rule": "accent"
90
+ }
91
+ },
20
92
  {
21
93
  id: "studio",
22
94
  palette: {
@@ -28,8 +100,8 @@ exports.DESIGN_THEMES = [
28
100
  line: "#D7DED3",
29
101
  },
30
102
  typography: {
31
- heading: "Inter",
32
- body: "Inter",
103
+ heading: "DM Sans",
104
+ body: "Source Sans 3",
33
105
  code: "monospace",
34
106
  headingWeight: 700,
35
107
  },
@@ -46,7 +118,7 @@ exports.DESIGN_THEMES = [
46
118
  line: "#3D526C",
47
119
  },
48
120
  typography: {
49
- heading: "Inter",
121
+ heading: "Space Grotesk",
50
122
  body: "Inter",
51
123
  code: "monospace",
52
124
  headingWeight: 700,
@@ -65,7 +137,7 @@ exports.DESIGN_THEMES = [
65
137
  },
66
138
  typography: {
67
139
  heading: "Lora",
68
- body: "Inter",
140
+ body: "Source Sans 3",
69
141
  code: "monospace",
70
142
  headingWeight: 700,
71
143
  },
@@ -82,8 +154,8 @@ exports.DESIGN_THEMES = [
82
154
  line: "#DAD5F2",
83
155
  },
84
156
  typography: {
85
- heading: "Inter",
86
- body: "Inter",
157
+ heading: "Space Grotesk",
158
+ body: "DM Sans",
87
159
  code: "monospace",
88
160
  headingWeight: 800,
89
161
  },
@@ -100,7 +172,7 @@ exports.DESIGN_THEMES = [
100
172
  line: "#B8A233",
101
173
  },
102
174
  typography: {
103
- heading: "Inter",
175
+ heading: "Oswald",
104
176
  body: "Inter",
105
177
  code: "monospace",
106
178
  headingWeight: 800,
@@ -28,6 +28,7 @@ exports.GOOGLE_FONT_CATALOG = [
28
28
  "Playfair Display",
29
29
  "PT Serif",
30
30
  ].map((f) => entry(f, "serif")),
31
+ entry("Instrument Serif", "serif", ["latin"], [400]),
31
32
  ...["Space Grotesk", "Oswald", "Bebas Neue"].map((f) => entry(f, "display", ["latin"], f === "Bebas Neue" ? [400] : [400, 700])),
32
33
  ...["JetBrains Mono", "IBM Plex Mono", "Fira Code", "Source Code Pro"].map((f) => entry(f, "mono")),
33
34
  entry("Noto Sans SC", "sans", ["zh"]),
@@ -43,6 +44,9 @@ exports.GOOGLE_FONT_CATALOG = [
43
44
  entry("Noto Serif KR", "serif", ["ko"]),
44
45
  entry("Noto Sans Arabic", "sans", ["ar"]),
45
46
  entry("Noto Sans Devanagari", "sans", ["hi"]),
47
+ entry("Noto Serif Devanagari", "serif", ["hi"]),
48
+ entry("Noto Sans Thai", "sans", ["th"]),
49
+ entry("Noto Serif Thai", "serif", ["th"]),
46
50
  ];
47
51
  function contentScript(text, locale = "en") {
48
52
  if (/[\u3040-\u30ff]/u.test(text))
@@ -53,6 +57,8 @@ function contentScript(text, locale = "en") {
53
57
  return "ar";
54
58
  if (/[\u0900-\u097f]/u.test(text))
55
59
  return "hi";
60
+ if (/[\u0e00-\u0e7f]/u.test(text))
61
+ return "th";
56
62
  if (/[\u3400-\u9fff]/u.test(text))
57
63
  return /^ja/i.test(locale)
58
64
  ? "ja"
@@ -63,7 +69,7 @@ function contentScript(text, locale = "en") {
63
69
  : "zh";
64
70
  if (/^zh/i.test(locale))
65
71
  return /TW|HK|Hant/i.test(locale) ? "zh-Hant" : "zh";
66
- return /^(ja|ko|ar|hi)/.exec(locale)?.[1] || "latin";
72
+ return /^(ja|ko|ar|hi|th)/.exec(locale)?.[1] || "latin";
67
73
  }
68
74
  function recommendedFonts(locale, text = "") {
69
75
  const script = contentScript(text, locale);
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FORMAL_COMPOSITIONS = exports.FORMAL_REFERENCE_COMPOSITIONS = void 0;
4
+ exports.applicableCompositions = applicableCompositions;
5
+ const compositionUseCases_js_1 = require("./compositionUseCases.js");
6
+ const nativeSocialParameters_js_1 = require("./nativeSocialParameters.js");
7
+ const designSystem_js_1 = require("./designSystem.js");
8
+ const socialLayouts_js_1 = require("./socialLayouts.js");
9
+ const recipeParameters_js_1 = require("./recipeParameters.js");
10
+ const compositionNumbers_js_1 = require("./compositionNumbers.js");
11
+ const both = ['presentation', 'social'];
12
+ const semantic = (id, accepts, minEntries = 0, maxEntries = 12, requiresImage = false) => ({ id, renderer: 'semantic', kind: 'social', family: id, accepts, minEntries, maxEntries, requiresImage });
13
+ 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
+ }
16
+ /** Structural reference contracts shared with the product's existing renderers.
17
+ * Colorways and historical aliases remain outside active discovery.
18
+ */
19
+ exports.FORMAL_REFERENCE_COMPOSITIONS = [
20
+ { id: 'authored-reference-photo', reference: 1, layout: 'authored-photo-editorial', accepts: ['image', 'paragraph'] },
21
+ { id: 'authored-reference-manifesto', reference: 2, layout: 'authored-type-manifesto', accepts: ['paragraph'] },
22
+ { id: 'authored-reference-journey', reference: 3, layout: 'authored-editorial-flow', accepts: ['steps'] },
23
+ { id: 'authored-reference-vision', reference: 4, layout: 'authored-editorial-features', accepts: ['features'] },
24
+ { id: 'authored-reference-matrix', reference: 5, layout: 'authored-editorial-matrix', accepts: ['comparison', 'paragraph'] },
25
+ { id: 'authored-reference-diagonal', reference: 6, layout: 'authored-diagonal-cover', accepts: ['paragraph'] },
26
+ { id: 'authored-reference-list', reference: 8, layout: 'authored-editorial-index', accepts: ['features'] },
27
+ { id: 'authored-reference-story', reference: 11, layout: 'authored-visual-poster', accepts: ['image', 'paragraph', 'features'] },
28
+ { id: 'authored-reference-sustainable', reference: 13, layout: 'authored-editorial-metrics', accepts: ['parameters'] },
29
+ { id: 'authored-reference-code', reference: 14, layout: 'authored-code-panel', accepts: ['code'] },
30
+ { id: 'authored-reference-bars', reference: 18, layout: 'authored-editorial-bars', accepts: ['chart'] },
31
+ { id: 'authored-reference-share', reference: 19, layout: 'authored-editorial-donut', accepts: ['chart'] },
32
+ { id: 'authored-reference-opinion', reference: 20, layout: 'authored-editorial-quote', accepts: ['quote'] },
33
+ { id: 'authored-reference-comparison', reference: 22, layout: 'authored-editorial-comparison', accepts: ['comparison'] },
34
+ { id: 'authored-reference-faq', reference: 23, layout: 'authored-editorial-faq', accepts: ['features'] },
35
+ { id: 'authored-reference-trend', reference: 25, layout: 'authored-editorial-trend', accepts: ['chart'] },
36
+ ];
37
+ const semanticRecipes = designSystem_js_1.DESIGN_RECIPES.filter(recipe => recipe.renderer === 'semantic');
38
+ const uniqueSemantic = semanticRecipes.filter((recipe, index) => semanticRecipes.findIndex(other => other.id === recipe.id) === index);
39
+ const campaignDefinitions = [
40
+ ['ledger-chart', ['parameters', 'chart'], 2, 6, false, true],
41
+ ['detail-sidebar', ['paragraph', 'parameters'], 1, 3, false, true],
42
+ ['technical-log', ['code', 'paragraph'], 0, 12, false, false],
43
+ ['architecture-stack', ['steps', 'paragraph'], 2, 4, false, false],
44
+ ['speaker-spotlight', ['quote', 'parameters'], 1, 6, true, false],
45
+ ['speaker-lineup', ['quote', 'parameters'], 1, 6, true, false],
46
+ ];
47
+ const founderDefinitions = [
48
+ ['authored-mechanism-flow', ['features', 'steps', 'paragraph'], 2, 4],
49
+ ['authored-reference-capability-map', ['features', 'paragraph'], 2, 4],
50
+ ['authored-founder-timeline', ['steps', 'paragraph'], 2, 4],
51
+ ['authored-founder-comparison', ['comparison', 'paragraph'], 2, 2],
52
+ ];
53
+ const articleKinds = ['features', 'process', 'comparison', 'summary', 'checklist', 'faq', 'chart'];
54
+ const articleAccepts = { features: ['features', 'paragraph'], process: ['steps', 'paragraph'], comparison: ['comparison', 'paragraph'], summary: ['paragraph', 'features'], checklist: ['features', 'paragraph'], faq: ['features', 'paragraph'], chart: ['chart', 'paragraph'] };
55
+ function article(id, kind, variant) {
56
+ const cover = kind === 'cover';
57
+ const recipe = semantic(id, cover ? ['paragraph', 'features', 'image'] : articleAccepts[kind], 0, kind === 'chart' ? 12 : 3);
58
+ const number = cover ? variant + 1 : kind === 'features' && variant >= 3 ? 22 + variant : kind === 'process' && variant === 3 ? 33 : 4 + articleKinds.indexOf(kind) * 3 + variant;
59
+ const definition = define(recipe, [cover ? 'article-cover' : 'article-illustration'], id, `A${String(number).padStart(3, '0')}`, .5, 2.4);
60
+ // A cover is a headline, a short deck and its authored hero. It is not a
61
+ // miniature article: the original source remains in the candidate pool.
62
+ const coverSchema = { type: 'object', additionalProperties: false, required: ['title', 'blocks'], properties: {
63
+ title: { type: 'string', minLength: 1, maxLength: 72, description: 'A concise editorial headline, preferably four to eight words.' },
64
+ blocks: { type: 'array', minItems: 1, maxItems: 1, items: { type: 'object', additionalProperties: false, required: ['id', 'kind', 'text', 'evidence'], properties: {
65
+ id: { type: 'string', minLength: 1, maxLength: 80 }, kind: { const: 'paragraph' }, text: { type: 'string', minLength: 1, maxLength: 140, description: 'One concise supporting sentence. Summarize the topic; do not repeat the full article or enumerate every feature.' },
66
+ evidence: { type: 'array', minItems: 1, maxItems: 32, items: { type: 'object', additionalProperties: false, required: ['sourceId'], properties: { sourceId: { type: 'string', maxLength: 160 }, quote: { type: 'string', maxLength: 2000 } } } },
67
+ } } },
68
+ } };
69
+ // The optional hero preserves compatibility with saved paragraph-only pools.
70
+ // New requests can reference a real asset; missing assets retain native artwork.
71
+ const coverWithHero = { ...coverSchema, properties: { ...coverSchema.properties, blocks: { ...coverSchema.properties.blocks, maxItems: 2,
72
+ description: 'One supporting paragraph and, when imagery helps explain the topic, one image hero. Use an available asset ID or a missing image request; never invent a photo URL.',
73
+ items: [coverSchema.properties.blocks.items, { type: 'object', additionalProperties: false, required: ['id', 'kind', 'sourceId', 'alt', 'treatment', 'status', 'evidence'], properties: {
74
+ id: { type: 'string', minLength: 1, maxLength: 80 }, kind: { const: 'image' }, sourceId: { type: 'string', maxLength: 160 },
75
+ alt: { type: 'string', minLength: 1, maxLength: 300, description: 'A concise description of the image subject, grounded in the article topic.' },
76
+ treatment: { enum: ['photograph', 'diagram', 'screenshot'] }, status: { enum: ['available', 'missing'] }, assetId: { type: 'string', maxLength: 200 },
77
+ evidence: coverSchema.properties.blocks.items.properties.evidence,
78
+ } }],
79
+ } } };
80
+ const illustrationSchema = JSON.parse(JSON.stringify(definition.parameterSchema));
81
+ if (!cover) {
82
+ const primaryKind = kind === 'summary' ? 'features' : articleAccepts[kind][0];
83
+ const blocks = illustrationSchema.properties.blocks;
84
+ const primary = blocks.items.find((block) => block.properties.kind.const === primaryKind);
85
+ blocks.items = [primary];
86
+ blocks.minItems = 1;
87
+ blocks.maxItems = 1;
88
+ if (kind === 'faq')
89
+ primary.properties.entries.items.properties.title.pattern = '^.+[?๏ผŸ]$';
90
+ }
91
+ const coverDirections = ['An editorial document sculpture with layered planes and a strong headline.', 'A connected constellation with a single focal object and an editorial headline.', 'An architectural archive silhouette balanced against expressive editorial typography.'];
92
+ return { ...definition, ...(cover ? { parameterSchema: coverWithHero, capacity: { minEntries: 0, maxEntries: 0, requiresImage: false }, semanticDescription: coverDirections[variant] } : { parameterSchema: illustrationSchema }), recipe: undefined, rendererKey: `article-${kind}:${variant}`,
93
+ semanticDescription: (0, compositionUseCases_js_1.compositionUseCase)(id) + (cover ? ' ' + coverDirections[variant] : '') };
94
+ }
95
+ /** Matches /dev/compositions/all: 161 default previews plus two landscape-only
96
+ * reference entries. Output capabilities overlap and must never be summed.
97
+ */
98
+ exports.FORMAL_COMPOSITIONS = [
99
+ ...campaignDefinitions.map(([id, accepts, min, max, image, landscape], index) => define(semantic(`authored-reference-${id}`, accepts, min, max, image), landscape ? ['presentation'] : both, `new-reference:${id}`, `N${index + 1}`, landscape ? 1.4 : .25)),
100
+ define(semantic('authored-event-poster', ['parameters', 'paragraph'], 1, 6), ['social'], 'event:provider', 'E1'),
101
+ define(semantic('authored-image-triptych', ['paragraph', 'features', 'image'], 3, 3, true), ['social'], 'campaign:triptych', 'C1', .5, 2),
102
+ ...exports.FORMAL_REFERENCE_COMPOSITIONS.map(spec => define(semantic(spec.id, spec.accepts, 0, 12, spec.accepts.includes('image')), both, `reference:${spec.id}`, `R${String(spec.reference).padStart(2, '0')}`)),
103
+ ...socialLayouts_js_1.SOCIAL_LAYOUT_CATALOG.map(preset => {
104
+ const recipe = designSystem_js_1.DESIGN_RECIPES.find(item => item.id === preset.id && item.kind === 'social');
105
+ 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
+ ...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
+ ...founderDefinitions.map(([id, accepts, min, max], index) => define(semantic(id, accepts, min, max), ['social'], `semantic:social:${id}`, `F${index + 1}`, 1.3)),
109
+ ...[0, 1, 2].flatMap(variant => [article(`article-cover-${variant}`, 'cover', variant), ...articleKinds.map(kind => article(`article-${kind}-${variant}`, kind, variant))]),
110
+ ...[3, 4, 5, 6, 7, 8, 9, 10].map(variant => article(`article-features-${variant}`, 'features', variant)),
111
+ article('article-process-3', 'process', 3),
112
+ ];
113
+ /** Only output, ratio, and explicit IDs are hard gates; semantic fit is scored. */
114
+ function applicableCompositions(kind, ratio, id) {
115
+ if (!Number.isFinite(ratio) || ratio <= 0)
116
+ throw new Error('COMPOSITION_INVALID_RATIO');
117
+ if (id && !exports.FORMAL_COMPOSITIONS.some(item => item.id === id))
118
+ throw new Error('COMPOSITION_UNKNOWN_ID');
119
+ return exports.FORMAL_COMPOSITIONS.filter(item => item.supportedKinds.includes(kind) && ratio >= item.aspectRatios.min && ratio <= item.aspectRatios.max && (!id || item.id === id));
120
+ }
package/dist/cjs/index.js CHANGED
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.SOCIAL_DISPLAY_NUMBERS = exports.compositionParameterDefaults = void 0;
17
18
  __exportStar(require("./geometry.js"), exports);
18
19
  __exportStar(require("./text.js"), exports);
19
20
  __exportStar(require("./scenes.js"), exports);
@@ -29,3 +30,15 @@ __exportStar(require("./designBlueprint.js"), exports);
29
30
  __exportStar(require("./compositionScenes.js"), exports);
30
31
  __exportStar(require("./editorial.js"), exports);
31
32
  __exportStar(require("./fontCatalog.js"), exports);
33
+ __exportStar(require("./candidateRanking.js"), exports);
34
+ __exportStar(require("./styleManifest.js"), exports);
35
+ __exportStar(require("./recipeParameters.js"), exports);
36
+ __exportStar(require("./formalCompositions.js"), exports);
37
+ __exportStar(require("./contentBrief.js"), exports);
38
+ __exportStar(require("./nativeSocialParameters.js"), exports);
39
+ __exportStar(require("./compositionUseCases.js"), exports);
40
+ var parameterDefaults_js_1 = require("./parameterDefaults.js");
41
+ Object.defineProperty(exports, "compositionParameterDefaults", { enumerable: true, get: function () { return parameterDefaults_js_1.compositionParameterDefaults; } });
42
+ var compositionNumbers_js_1 = require("./compositionNumbers.js");
43
+ Object.defineProperty(exports, "SOCIAL_DISPLAY_NUMBERS", { enumerable: true, get: function () { return compositionNumbers_js_1.SOCIAL_DISPLAY_NUMBERS; } });
44
+ __exportStar(require("./textPresentation.js"), exports);
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.COMPOSITION_ILLUSTRATIONS = void 0;
4
+ exports.nativeSocialParameterContract = nativeSocialParameterContract;
5
+ exports.validateNativeSocialParameters = validateNativeSocialParameters;
6
+ exports.nativeSocialContentPage = nativeSocialContentPage;
7
+ const parameterDefaults_js_1 = require("./parameterDefaults.js");
8
+ const textPresentation_js_1 = require("./textPresentation.js");
9
+ const socialLayouts_js_1 = require("./socialLayouts.js");
10
+ const slots = {
11
+ 'note-question': {}, 'note-marker': {}, 'note-editorial': {}, 'note-checklist': { items: 3 }, 'note-photo': { image: true }, 'note-news': { value: true },
12
+ 'chat-messenger': { items: 3 }, 'chat-imessage': { items: 3 }, 'chat-telegram': { items: 3 }, 'chat-workplace': { items: 3 },
13
+ 'time-notice': { value: true }, 'news-focus': {}, manifesto: {}, launch: { image: true }, bento: { items: 3 }, spotlight: { items: 3, value: true, image: true },
14
+ ticket: { items: 2, value: true }, webinar: { value: true, image: true }, quote: { attribution: true }, testimonial: { attribution: true }, metric: { value: true },
15
+ report: { items: 3 }, checklist: { items: 3 }, steps: { items: 3 }, comparison: { items: 2 }, myth: { items: 2 }, pricing: { items: 3, value: true }, offer: { value: true, image: true },
16
+ editorial: { image: true }, story: { image: true }, hiring: { items: 3 }, team: { items: 3, itemImages: true }, course: { items: 3 }, 'case-study': { items: 3 }, notice: {}, community: { value: true },
17
+ menu: { items: 3 }, specification: { items: 3 }, itinerary: { items: 3 }, schedule: { items: 3 }, safety: { items: 3 }, faq: { items: 3 },
18
+ 'split-bar-compare': { items: 3, quantities: 'comparison' }, 'benchmark-bars': { items: 3, quantities: 'benchmark' }, 'usage-breakdown': { items: 3, quantities: 'share' },
19
+ organization: { items: 3 }, 'editorial-quote': { attribution: true }, 'research-highlight': { items: 2 }, 'product-showcase': { items: 3, image: true },
20
+ };
21
+ const copy = (maxLength, description, minLength = 1) => ({ type: 'string', minLength, maxLength, description });
22
+ const object = (properties) => ({ type: 'object', additionalProperties: false, properties, required: Object.keys(properties) });
23
+ const asset = () => copy(160, 'An existing supplied asset ID. Use neatico-editorial-placeholder when no matching source image exists.');
24
+ exports.COMPOSITION_ILLUSTRATIONS = ['document', 'collaboration', 'review', 'feedback', 'sequence', 'comparison', 'structure'];
25
+ const illustratedSlots = new Set(['bento', 'course', 'case-study', 'report', 'product-showcase', 'comparison', 'research-highlight']);
26
+ const provenance = ['user-fact', 'derived', 'creative', 'simulated'];
27
+ // These authored slots have been measured with complete provider sentences at
28
+ // square, landscape, and portrait ratios. Compact labels keep their own limits.
29
+ const sentenceBodySlots = new Set(['note-question', 'note-marker', 'note-editorial', 'chat-imessage', 'chat-telegram', 'news-focus', 'manifesto', 'launch', 'spotlight', 'comparison', 'myth', 'editorial', 'story', 'notice']);
30
+ const supportingItemSlots = new Set(['team', 'course', 'specification', 'schedule', 'research-highlight']);
31
+ /** Every field corresponds to an authored visible slot; no scene-based eligibility filter. */
32
+ function nativeSocialParameterContract(id) {
33
+ const spec = slots[id];
34
+ if (!spec || !socialLayouts_js_1.SOCIAL_LAYOUT_CATALOG.some(item => item.id === id))
35
+ throw new Error('COMPOSITION_UNKNOWN_NATIVE_SOCIAL');
36
+ const properties = { title: copy(id === 'note-question' ? 44 : 40, 'Concise audience-facing headline.'), body: copy(id === 'organization' ? 32 : id === 'case-study' ? 64 : ['quote', 'testimonial', 'editorial-quote'].includes(id) ? 140 : sentenceBodySlots.has(id) ? 96 : 80, id === 'organization' ? 'A short label for the parent or coordinating role; never invent a real person.' : 'One supporting sentence, or the quoted passage for a quote composition.') };
37
+ if (!id.startsWith('chat-') && !['note-photo', 'note-checklist'].includes(id))
38
+ properties.eyebrow = { ...copy(20, 'Optional concise section label. Use an empty string when no label improves the message.', 0), default: '' };
39
+ if (!id.startsWith('chat-') && !['note-photo', 'note-checklist'].includes(id))
40
+ properties.cta = { ...copy(24, 'Optional short call to action appropriate to the input. Use an empty string when this is purely informational.', 0), default: '' };
41
+ if (['note-marker', 'note-editorial', 'note-question'].includes(id))
42
+ properties.titleLines = { type: 'array', minItems: 0, maxItems: 4, default: [], items: copy(40, 'One complete phrase from the title, preserving its original language.'), description: 'Preferred title lines: one item for a single line, two to four for a multiline heading, or empty for automatic fitting. Concatenating the phrases must preserve the complete title and word boundaries. The renderer may reflow only when the requested lines cannot fit legibly.' };
43
+ if (id === 'note-marker')
44
+ properties.emphasis = { type: 'array', minItems: 0, maxItems: 2, default: [], items: copy(40, 'An exact contiguous phrase from title to highlight.'), description: 'Choose one or two important exact title phrases. Empty means the renderer highlights the final title line.' };
45
+ if (spec.value)
46
+ properties.value = copy(24, 'The focal quantity, price, time, or short value statement. Label fictional quantities as simulated.');
47
+ if (spec.attribution)
48
+ properties.attribution = copy(48, 'Exact supplied attribution, or an empty string. Never invent an endorsement.', 0);
49
+ if (spec.image)
50
+ properties.imageAssetId = asset();
51
+ if (spec.items) {
52
+ const item = { title: copy(id === 'faq' ? 40 : id === 'case-study' ? 18 : 20, id === 'faq' ? 'A concise audience question ending in a question mark.' : 'A short item label.'), description: copy(id === 'faq' ? 64 : spec.quantities || id === 'case-study' ? 32 : ['comparison', 'myth'].includes(id) ? 80 : supportingItemSlots.has(id) ? 48 : 40, 'Concise supporting copy.', spec.quantities ? 0 : 1) };
53
+ if (id === 'faq')
54
+ item.title.pattern = '^.+[?๏ผŸ]$';
55
+ if (illustratedSlots.has(id))
56
+ item.illustration = { type: 'string', enum: exports.COMPOSITION_ILLUSTRATIONS, default: 'structure', description: 'An editable native illustration that explains this item. Choose document for material, review for checking, collaboration for people, feedback for revision, sequence for ordered actions, comparison for alternatives, or structure for organization. Never omit the visual slot.' };
57
+ if (spec.itemImages)
58
+ item.assetId = asset();
59
+ if (spec.quantities) {
60
+ item.primaryValue = { type: 'number', minimum: 0, maximum: 100, description: spec.quantities === 'comparison' ? 'A percentage from zero to one hundred. primaryValue plus secondaryValue must equal exactly 100 in this item.' : 'A percentage from zero to one hundred. Never encode numbers inside descriptive copy.' };
61
+ if (spec.quantities !== 'share')
62
+ item.secondaryValue = { type: 'number', minimum: 0, maximum: 100, description: 'The comparison or benchmark percentage.' };
63
+ if (spec.quantities === 'share')
64
+ item.description = { const: '' };
65
+ }
66
+ properties.items = { type: 'array', minItems: spec.items, maxItems: spec.items, items: object(item), ...(spec.quantities === 'share' ? { description: 'Exactly three nonnegative percentage shares totaling at most one hundred.' } : {}) };
67
+ }
68
+ if (spec.quantities === 'comparison' || spec.quantities === 'benchmark')
69
+ properties.comparisonLabels = { type: 'array', minItems: 2, maxItems: 2, items: copy(20, 'A supplied or explicitly illustrative series label.') };
70
+ if (['note-question', 'note-marker', 'note-editorial'].includes(id))
71
+ properties.decorationEmoji = { type: 'string', enum: ['', 'โœจ', '๐Ÿ’ก', '๐ŸŒฑ', '๐Ÿ’ฌ', '๐ŸŽฏ', '๐Ÿ“š'], default: '', description: 'Optional built-in decorative emoji for informal social notes. Choose one relevant symbol, or empty for the authored illustration. Never use it as factual evidence.' };
72
+ properties.textPresentation = (0, textPresentation_js_1.textPresentationSchema)();
73
+ const contentKeys = Object.keys(properties).filter(key => !['textPresentation', 'decorationEmoji'].includes(key));
74
+ const importance = Object.fromEntries(contentKeys.map(key => [key, key === 'title' ? 3 : key === 'items' ? 7 : ['eyebrow', 'cta'].includes(key) ? .5 : 2]));
75
+ return { parameterModel: 'native-social', parameterSchema: object(properties), importance, generationPolicy: Object.fromEntries(contentKeys.map(key => [key, provenance])) };
76
+ }
77
+ /** Apply the small closed schema vocabulary used by native slots on every local render. */
78
+ function validateNativeSocialParameters(id, raw) {
79
+ const check = (value, schema) => {
80
+ if (Object.hasOwn(schema, 'const'))
81
+ return value === schema.const;
82
+ if (Array.isArray(schema.enum))
83
+ return schema.enum.includes(value);
84
+ if (schema.type === 'string')
85
+ return typeof value === 'string' && Array.from(value).length >= Number(schema.minLength || 0) && Array.from(value).length <= Number(schema.maxLength) && (!schema.pattern || new RegExp(String(schema.pattern), 'u').test(value));
86
+ if (schema.type === 'boolean')
87
+ return typeof value === 'boolean';
88
+ if (schema.type === 'number')
89
+ return typeof value === 'number' && Number.isFinite(value) && value >= Number(schema.minimum) && value <= Number(schema.maximum);
90
+ if (schema.type === 'array')
91
+ return Array.isArray(value) && value.length >= Number(schema.minItems) && value.length <= Number(schema.maxItems) && value.every(item => check(item, schema.items));
92
+ if (schema.type === 'object') {
93
+ if (!value || typeof value !== 'object' || Array.isArray(value))
94
+ return false;
95
+ const properties = schema.properties, object = value;
96
+ return Object.keys(object).every(key => Object.hasOwn(properties, key) && check(object[key], properties[key])) && schema.required.every(key => Object.hasOwn(object, key) && check(object[key], properties[key]));
97
+ }
98
+ return false;
99
+ };
100
+ const schema = nativeSocialParameterContract(id).parameterSchema;
101
+ raw = (0, parameterDefaults_js_1.compositionParameterDefaults)(schema, raw);
102
+ if (!check(raw, schema))
103
+ throw new Error('CANDIDATE_INVALID_PARAMETERS');
104
+ const values = raw;
105
+ (0, textPresentation_js_1.validateTextPresentations)(values.textPresentation, values);
106
+ if (values.titleLines?.length) {
107
+ const normalize = (copy) => copy.normalize('NFC').replace(/\s+/gu, ' ').trim();
108
+ const title = normalize(values.title);
109
+ let cursor = 0;
110
+ for (const phrase of values.titleLines) {
111
+ const line = normalize(phrase), hadSpace = title[cursor] === ' ';
112
+ if (hadSpace)
113
+ cursor++;
114
+ // CJK phrases may meet without spaces, but Latin words cannot be split.
115
+ const brokenWord = cursor > 0 && !hadSpace && /[\p{Script=Latin}\p{Mark}\dโ€™'-]/u.test(title[cursor - 1]) && /^[\p{Script=Latin}\p{Mark}\dโ€™'-]/u.test(line);
116
+ if (!line || brokenWord || !title.startsWith(line, cursor))
117
+ throw new Error('CANDIDATE_INVALID_TITLE_LINES');
118
+ cursor += line.length;
119
+ }
120
+ if (cursor !== title.length)
121
+ throw new Error('CANDIDATE_INVALID_TITLE_LINES');
122
+ }
123
+ if (values.emphasis?.some(phrase => !values.title.includes(phrase)))
124
+ throw new Error('CANDIDATE_INVALID_EMPHASIS');
125
+ if (id === 'usage-breakdown' && values.items.reduce((sum, item) => sum + item.primaryValue, 0) > 100.0001)
126
+ throw new Error('CANDIDATE_INVALID_PERCENTAGES');
127
+ if (id === 'split-bar-compare' && values.items.some(item => Math.abs(item.primaryValue + item.secondaryValue - 100) > .001))
128
+ throw new Error('CANDIDATE_INVALID_PERCENTAGES');
129
+ return JSON.parse(JSON.stringify(values));
130
+ }
131
+ /** Numeric and image identities remain available when a native design becomes editable content. */
132
+ function nativeSocialContentPage(values, original) {
133
+ const proof = original.evidence, blocks = [{ id: 'native-body', kind: 'paragraph', text: values.body, evidence: proof }];
134
+ if (values.items?.length)
135
+ blocks.push({ id: 'native-items', kind: 'features', evidence: proof, entries: values.items.map(item => ({ title: item.title, ...(item.illustration ? { illustration: item.illustration } : {}), description: [item.description, ...[item.primaryValue, item.secondaryValue].filter(value => value !== undefined).map(value => `${value}%`)].filter(Boolean).join(' ยท ') })) });
136
+ for (const field of ['value', 'attribution', 'eyebrow', 'cta'])
137
+ if (values[field])
138
+ blocks.push({ id: `native-${field}`, kind: 'paragraph', text: values[field], evidence: proof });
139
+ if (values.comparisonLabels)
140
+ blocks.push({ id: 'native-series', kind: 'paragraph', evidence: proof, text: values.comparisonLabels.join(' / ') });
141
+ const images = [values.imageAssetId, ...(values.items || []).map(item => item.assetId)].filter((id) => !!id);
142
+ for (const [index, assetId] of images.entries())
143
+ blocks.push({ id: `native-image-${index}`, kind: 'image', evidence: [{ sourceId: assetId }], sourceId: assetId, assetId, alt: values.title, treatment: assetId === 'neatico-editorial-placeholder' ? 'diagram' : 'photograph', status: 'available' });
144
+ return { ...original, title: values.title, blocks };
145
+ }
@@ -5,12 +5,21 @@ exports.splitContent = splitContent;
5
5
  /** Split only at explicit word/sentence boundaries. Never cut identifiers,
6
6
  * decimal values, or unbroken URLs to force them onto a smaller canvas. */
7
7
  function splitCopy(copy) {
8
- const breaks = [...copy.matchAll(/\s+|[ใ€‚๏ผ๏ผŸ๏ผ›]|[.!?;](?=\s|$)/gu)]
9
- .map((match) => match.index + match[0].length)
10
- .filter((index) => index > 0 && index < copy.length);
11
- if (!breaks.length)
8
+ let at;
9
+ let distance = Infinity;
10
+ const midpoint = copy.length / 2;
11
+ for (const match of copy.matchAll(/\s+|[ใ€‚๏ผ๏ผŸ๏ผ›]|[.!?;](?=\s|$)/gu)) {
12
+ const index = match.index + match[0].length;
13
+ if (index <= 0 || index >= copy.length)
14
+ continue;
15
+ const candidateDistance = Math.abs(index - midpoint);
16
+ if (candidateDistance < distance) {
17
+ at = index;
18
+ distance = candidateDistance;
19
+ }
20
+ }
21
+ if (at === undefined)
12
22
  return;
13
- const at = breaks.sort((a, b) => Math.abs(a - copy.length / 2) - Math.abs(b - copy.length / 2))[0];
14
23
  return [copy.slice(0, at), copy.slice(at)];
15
24
  }
16
25
  /** No paraphrasing, invented continuation labels or removed constraints. The
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compositionParameterDefaults = compositionParameterDefaults;
4
+ /** Apply only renderer-authored defaults. Never rewrite supplied copy or values. */
5
+ function compositionParameterDefaults(schema, value) {
6
+ if (value === undefined && Object.hasOwn(schema, 'default'))
7
+ return JSON.parse(JSON.stringify(schema.default));
8
+ if (schema.type === 'object' && value && typeof value === 'object' && !Array.isArray(value)) {
9
+ const next = { ...value };
10
+ for (const [key, child] of Object.entries(schema.properties || {})) {
11
+ const resolved = compositionParameterDefaults(child, next[key]);
12
+ if (resolved !== undefined)
13
+ next[key] = resolved;
14
+ }
15
+ return next;
16
+ }
17
+ if (schema.type === 'array' && Array.isArray(value))
18
+ return value.map((item, index) => compositionParameterDefaults((Array.isArray(schema.items) ? schema.items[index] : schema.items) || {}, item));
19
+ return value;
20
+ }