@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.
- package/dist/candidateRanking.d.ts +31 -0
- package/dist/candidateRanking.js +55 -0
- package/dist/cjs/candidateRanking.js +61 -0
- package/dist/cjs/composition.js +62 -36
- package/dist/cjs/compositionNumbers.js +111 -0
- package/dist/cjs/compositionScenes.js +91 -38
- package/dist/cjs/compositionUseCases.js +145 -0
- package/dist/cjs/content.js +574 -120
- package/dist/cjs/contentBrief.js +82 -0
- package/dist/cjs/designBlueprint.js +94 -19
- package/dist/cjs/designSystem.js +536 -77
- package/dist/cjs/editorial.js +76 -20
- package/dist/cjs/fontCatalog.js +81 -23
- package/dist/cjs/formalCompositions.js +120 -0
- package/dist/cjs/index.js +13 -0
- package/dist/cjs/layoutContracts.js +57 -11
- package/dist/cjs/layouts.js +5 -92
- package/dist/cjs/nativeSocialParameters.js +145 -0
- package/dist/cjs/pagination.js +27 -7
- package/dist/cjs/parameterDefaults.js +20 -0
- package/dist/cjs/recipeParameters.js +304 -0
- package/dist/cjs/socialLayouts.js +127 -387
- package/dist/cjs/styleManifest.js +107 -0
- package/dist/cjs/text.js +12 -2
- package/dist/cjs/textPresentation.js +68 -0
- package/dist/composition.d.ts +4 -4
- package/dist/composition.js +62 -36
- package/dist/compositionNumbers.d.ts +4 -0
- package/dist/compositionNumbers.js +108 -0
- package/dist/compositionScenes.d.ts +2 -2
- package/dist/compositionScenes.js +91 -38
- package/dist/compositionUseCases.d.ts +1 -0
- package/dist/compositionUseCases.js +142 -0
- package/dist/content.d.ts +65 -20
- package/dist/content.js +575 -121
- package/dist/contentBrief.d.ts +16 -0
- package/dist/contentBrief.js +78 -0
- package/dist/designBlueprint.d.ts +4 -4
- package/dist/designBlueprint.js +95 -20
- package/dist/designSystem.d.ts +11 -7
- package/dist/designSystem.js +537 -79
- package/dist/editorial.d.ts +1 -1
- package/dist/editorial.js +77 -21
- package/dist/fontCatalog.d.ts +1 -1
- package/dist/fontCatalog.js +81 -23
- package/dist/formalCompositions.d.ts +26 -0
- package/dist/formalCompositions.js +116 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/layoutContracts.d.ts +3 -3
- package/dist/layoutContracts.js +58 -12
- package/dist/layouts.d.ts +2 -2
- package/dist/layouts.js +5 -92
- package/dist/nativeSocialParameters.d.ts +38 -0
- package/dist/nativeSocialParameters.js +139 -0
- package/dist/pagination.js +27 -7
- package/dist/parameterDefaults.d.ts +2 -0
- package/dist/parameterDefaults.js +17 -0
- package/dist/recipeParameters.d.ts +34 -0
- package/dist/recipeParameters.js +300 -0
- package/dist/socialLayouts.d.ts +40 -232
- package/dist/socialLayouts.js +127 -387
- package/dist/styleManifest.d.ts +20 -0
- package/dist/styleManifest.js +103 -0
- package/dist/text.js +12 -2
- package/dist/textPresentation.d.ts +29 -0
- package/dist/textPresentation.js +64 -0
- package/package.json +1 -1
package/dist/editorial.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ContentPage, type ContentPlan } from
|
|
1
|
+
import { type ContentPage, type ContentPlan } from "./content.js";
|
|
2
2
|
/** Reading density, not just geometric fit. CJK occupies roughly twice Latin's width. */
|
|
3
3
|
export declare const readingUnits: (text: string) => number;
|
|
4
4
|
export declare function needsEditorialPagination(page: ContentPage): boolean;
|
package/dist/editorial.js
CHANGED
|
@@ -1,35 +1,91 @@
|
|
|
1
|
-
import { contentBlockText } from
|
|
1
|
+
import { contentBlockText, } from "./content.js";
|
|
2
2
|
/** Reading density, not just geometric fit. CJK occupies roughly twice Latin's width. */
|
|
3
|
-
export const readingUnits = (text) => Array.from(text).reduce((n, c) => n +
|
|
3
|
+
export const readingUnits = (text) => Array.from(text).reduce((n, c) => n +
|
|
4
|
+
(/\s/.test(c)
|
|
5
|
+
? 0.2
|
|
6
|
+
: /[\u2e80-\u9fff\uac00-\ud7af\u3040-\u30ff]/u.test(c)
|
|
7
|
+
? 1
|
|
8
|
+
: 0.52), 0);
|
|
4
9
|
export function needsEditorialPagination(page) {
|
|
5
|
-
if (page.scene ===
|
|
10
|
+
if (page.scene === "brand-collection")
|
|
6
11
|
return false;
|
|
7
|
-
const entries = page.blocks.flatMap(b => b.kind ===
|
|
8
|
-
if (entries.length >= 3 &&
|
|
12
|
+
const entries = page.blocks.flatMap((b) => b.kind === "features" || b.kind === "steps" ? b.entries : []);
|
|
13
|
+
if (entries.length >= 3 &&
|
|
14
|
+
entries.some((e) => readingUnits(e.description) > 65))
|
|
9
15
|
return true;
|
|
10
16
|
const total = page.blocks.reduce((n, b) => n + contentBlockText(b).reduce((m, s) => m + readingUnits(s), 0), 0);
|
|
11
|
-
return total > 250 ||
|
|
17
|
+
return (total > 250 ||
|
|
18
|
+
page.blocks.some((b) => b.kind === "paragraph"
|
|
19
|
+
? readingUnits(b.text) > 165
|
|
20
|
+
: (b.kind === "features" || b.kind === "steps") &&
|
|
21
|
+
(b.entries.some((e) => readingUnits(e.description) > 135) ||
|
|
22
|
+
(b.entries.length >= 3 &&
|
|
23
|
+
b.entries.some((e) => readingUnits(e.description) > 65)))));
|
|
12
24
|
}
|
|
13
|
-
const endings = {
|
|
25
|
+
const endings = {
|
|
26
|
+
zh: "感谢聆听",
|
|
27
|
+
en: "Thank you",
|
|
28
|
+
de: "Vielen Dank",
|
|
29
|
+
es: "Gracias",
|
|
30
|
+
fr: "Merci",
|
|
31
|
+
ja: "ご清聴ありがとうございました",
|
|
32
|
+
pt: "Obrigado",
|
|
33
|
+
};
|
|
14
34
|
/** Structural bookends contain no invented customer claims or calls to action. */
|
|
15
35
|
export function ensurePresentationBookends(plan) {
|
|
16
|
-
if (plan.kind !==
|
|
36
|
+
if (plan.kind !== "presentation" || !plan.pages.length)
|
|
17
37
|
return plan;
|
|
18
|
-
const text = [plan.title, ...plan.pages.map(p => p.title)].join(
|
|
19
|
-
const language = /[\u3040-\u30ff]/.test(text)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
38
|
+
const text = [plan.title, ...plan.pages.map((p) => p.title)].join(" ");
|
|
39
|
+
const language = /[\u3040-\u30ff]/.test(text)
|
|
40
|
+
? "ja"
|
|
41
|
+
: /[\u3400-\u9fff]/.test(text)
|
|
42
|
+
? "zh"
|
|
43
|
+
: /\b(und|für|Einführung)\b/i.test(text)
|
|
44
|
+
? "de"
|
|
45
|
+
: /\b(pour|présentation|avec)\b/i.test(text)
|
|
46
|
+
? "fr"
|
|
47
|
+
: /\b(para|apresentação|com)\b/i.test(text)
|
|
48
|
+
? "pt"
|
|
49
|
+
: /\b(presentación|con|gracias)\b/i.test(text)
|
|
50
|
+
? "es"
|
|
51
|
+
: "en";
|
|
52
|
+
const pages = plan.pages.map((p) => ({ ...p, blocks: [...p.blocks] }));
|
|
53
|
+
const used = new Set(pages.flatMap((p) => [p.id, ...p.blocks.map((b) => b.id)]));
|
|
54
|
+
const id = (prefix) => {
|
|
55
|
+
let s = prefix, i = 0;
|
|
56
|
+
while (used.has(s))
|
|
57
|
+
s = `${prefix}-${++i}`;
|
|
58
|
+
used.add(s);
|
|
59
|
+
return s;
|
|
60
|
+
};
|
|
61
|
+
const bookend = (purpose) => ({
|
|
62
|
+
id: id(`deck-${purpose}`),
|
|
63
|
+
title: purpose === "cover" ? plan.title : endings[language],
|
|
64
|
+
purpose,
|
|
65
|
+
scene: purpose === "cover" ? "hero" : "closing",
|
|
66
|
+
evidence: pages[0].evidence,
|
|
67
|
+
blocks: [
|
|
68
|
+
{
|
|
69
|
+
id: id(`deck-${purpose}-caption`),
|
|
70
|
+
kind: "paragraph",
|
|
71
|
+
text: plan.title,
|
|
72
|
+
evidence: pages[0].evidence,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
if (pages[0].purpose !== "cover")
|
|
77
|
+
pages.unshift(bookend("cover"));
|
|
27
78
|
const last = pages[pages.length - 1];
|
|
28
79
|
// Evidence and long explanations remain content slides, never masquerade as a closing.
|
|
29
|
-
if (last.purpose !==
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
80
|
+
if (last.purpose !== "conclude" ||
|
|
81
|
+
last.scene === "brand-collection" ||
|
|
82
|
+
last.blocks.some((b) => b.kind !== "paragraph") ||
|
|
83
|
+
last.blocks.reduce((n, b) => n + contentBlockText(b).join("").length, 0) >
|
|
84
|
+
120 ||
|
|
85
|
+
/行业认可|客户|customers|trusted by/i.test(last.title)) {
|
|
86
|
+
if (last.purpose === "conclude")
|
|
87
|
+
last.purpose = "evidence";
|
|
88
|
+
pages.push(bookend("conclude"));
|
|
33
89
|
}
|
|
34
90
|
return { ...plan, pages };
|
|
35
91
|
}
|
package/dist/fontCatalog.d.ts
CHANGED
package/dist/fontCatalog.js
CHANGED
|
@@ -1,36 +1,94 @@
|
|
|
1
|
-
const entry = (family, category, scripts = [
|
|
1
|
+
const entry = (family, category, scripts = ["latin"], weights = [400, 700]) => ({ family, category, scripts, weights });
|
|
2
2
|
/** Curated Google Fonts families; request only their declared, supported weights. */
|
|
3
3
|
export const GOOGLE_FONT_CATALOG = [
|
|
4
|
-
...[
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
...[
|
|
5
|
+
"Inter",
|
|
6
|
+
"Roboto",
|
|
7
|
+
"Montserrat",
|
|
8
|
+
"Manrope",
|
|
9
|
+
"DM Sans",
|
|
10
|
+
"Lato",
|
|
11
|
+
"Open Sans",
|
|
12
|
+
"Source Sans 3",
|
|
13
|
+
"Work Sans",
|
|
14
|
+
"Nunito Sans",
|
|
15
|
+
"IBM Plex Sans",
|
|
16
|
+
].map((f) => entry(f, "sans")),
|
|
17
|
+
...[
|
|
18
|
+
"Lora",
|
|
19
|
+
"Merriweather",
|
|
20
|
+
"Libre Baskerville",
|
|
21
|
+
"Source Serif 4",
|
|
22
|
+
"Playfair Display",
|
|
23
|
+
"PT Serif",
|
|
24
|
+
].map((f) => entry(f, "serif")),
|
|
25
|
+
entry("Instrument Serif", "serif", ["latin"], [400]),
|
|
26
|
+
...["Space Grotesk", "Oswald", "Bebas Neue"].map((f) => entry(f, "display", ["latin"], f === "Bebas Neue" ? [400] : [400, 700])),
|
|
27
|
+
...["JetBrains Mono", "IBM Plex Mono", "Fira Code", "Source Code Pro"].map((f) => entry(f, "mono")),
|
|
28
|
+
entry("Noto Sans SC", "sans", ["zh"]),
|
|
29
|
+
entry("Noto Serif SC", "serif", ["zh"]),
|
|
30
|
+
entry("ZCOOL XiaoWei", "display", ["zh"], [400]),
|
|
31
|
+
entry("Ma Shan Zheng", "display", ["zh"], [400]),
|
|
32
|
+
entry("Noto Sans TC", "sans", ["zh-Hant"]),
|
|
33
|
+
entry("Noto Serif TC", "serif", ["zh-Hant"]),
|
|
34
|
+
entry("Noto Sans JP", "sans", ["ja"]),
|
|
35
|
+
entry("Noto Serif JP", "serif", ["ja"]),
|
|
36
|
+
entry("M PLUS Rounded 1c", "sans", ["ja"]),
|
|
37
|
+
entry("Noto Sans KR", "sans", ["ko"]),
|
|
38
|
+
entry("Noto Serif KR", "serif", ["ko"]),
|
|
39
|
+
entry("Noto Sans Arabic", "sans", ["ar"]),
|
|
40
|
+
entry("Noto Sans Devanagari", "sans", ["hi"]),
|
|
41
|
+
entry("Noto Serif Devanagari", "serif", ["hi"]),
|
|
42
|
+
entry("Noto Sans Thai", "sans", ["th"]),
|
|
43
|
+
entry("Noto Serif Thai", "serif", ["th"]),
|
|
13
44
|
];
|
|
14
|
-
export function contentScript(text, locale =
|
|
45
|
+
export function contentScript(text, locale = "en") {
|
|
15
46
|
if (/[\u3040-\u30ff]/u.test(text))
|
|
16
|
-
return
|
|
47
|
+
return "ja";
|
|
17
48
|
if (/[\uac00-\ud7af]/u.test(text))
|
|
18
|
-
return
|
|
49
|
+
return "ko";
|
|
19
50
|
if (/[\u0600-\u06ff]/u.test(text))
|
|
20
|
-
return
|
|
51
|
+
return "ar";
|
|
21
52
|
if (/[\u0900-\u097f]/u.test(text))
|
|
22
|
-
return
|
|
53
|
+
return "hi";
|
|
54
|
+
if (/[\u0e00-\u0e7f]/u.test(text))
|
|
55
|
+
return "th";
|
|
23
56
|
if (/[\u3400-\u9fff]/u.test(text))
|
|
24
|
-
return /
|
|
57
|
+
return /^ja/i.test(locale)
|
|
58
|
+
? "ja"
|
|
59
|
+
: /^ko/i.test(locale)
|
|
60
|
+
? "ko"
|
|
61
|
+
: /TW|HK|Hant/i.test(locale)
|
|
62
|
+
? "zh-Hant"
|
|
63
|
+
: "zh";
|
|
25
64
|
if (/^zh/i.test(locale))
|
|
26
|
-
return /TW|HK|Hant/i.test(locale) ?
|
|
27
|
-
return /^(ja|ko|ar|hi)/.exec(locale)?.[1] ||
|
|
65
|
+
return /TW|HK|Hant/i.test(locale) ? "zh-Hant" : "zh";
|
|
66
|
+
return /^(ja|ko|ar|hi|th)/.exec(locale)?.[1] || "latin";
|
|
28
67
|
}
|
|
29
|
-
export function recommendedFonts(locale, text =
|
|
30
|
-
export function languageFont(text, preferred, locale = 'en') {
|
|
68
|
+
export function recommendedFonts(locale, text = "") {
|
|
31
69
|
const script = contentScript(text, locale);
|
|
32
|
-
|
|
70
|
+
return [...GOOGLE_FONT_CATALOG].sort((a, b) => Number(b.scripts.includes(script)) - Number(a.scripts.includes(script)));
|
|
71
|
+
}
|
|
72
|
+
export function languageFont(text, preferred, locale = "en") {
|
|
73
|
+
const script = contentScript(text, locale);
|
|
74
|
+
if (script === "latin")
|
|
75
|
+
return preferred;
|
|
76
|
+
const selected = GOOGLE_FONT_CATALOG.find((font) => font.family === preferred);
|
|
77
|
+
if (selected?.scripts.includes(script))
|
|
78
|
+
return preferred;
|
|
79
|
+
if (!selected &&
|
|
80
|
+
![
|
|
81
|
+
"Arial",
|
|
82
|
+
"Helvetica",
|
|
83
|
+
"Georgia",
|
|
84
|
+
"Times New Roman",
|
|
85
|
+
"Courier New",
|
|
86
|
+
"sans-serif",
|
|
87
|
+
"serif",
|
|
88
|
+
"monospace",
|
|
89
|
+
].includes(preferred))
|
|
33
90
|
return preferred;
|
|
34
|
-
const serif = /serif|Georgia|Lora|Baskerville|Merriweather|Playfair/i.test(preferred) &&
|
|
35
|
-
|
|
91
|
+
const serif = /serif|Georgia|Lora|Baskerville|Merriweather|Playfair/i.test(preferred) &&
|
|
92
|
+
!/sans/i.test(preferred);
|
|
93
|
+
return (GOOGLE_FONT_CATALOG.find((f) => f.scripts.includes(script) && f.category === (serif ? "serif" : "sans"))?.family || preferred);
|
|
36
94
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type DesignRecipe } from './designSystem.js';
|
|
2
|
+
import { type RecipeParameterContract, type CompositionKind } from './recipeParameters.js';
|
|
3
|
+
import type { SemanticKind } from './content.js';
|
|
4
|
+
export interface FormalComposition extends RecipeParameterContract {
|
|
5
|
+
displayNumber: string;
|
|
6
|
+
aspectRatios: {
|
|
7
|
+
min: number;
|
|
8
|
+
max: number;
|
|
9
|
+
};
|
|
10
|
+
recipe?: DesignRecipe;
|
|
11
|
+
}
|
|
12
|
+
/** Structural reference contracts shared with the product's existing renderers.
|
|
13
|
+
* Colorways and historical aliases remain outside active discovery.
|
|
14
|
+
*/
|
|
15
|
+
export declare const FORMAL_REFERENCE_COMPOSITIONS: readonly {
|
|
16
|
+
id: string;
|
|
17
|
+
reference: number;
|
|
18
|
+
layout: string;
|
|
19
|
+
accepts: readonly SemanticKind[];
|
|
20
|
+
}[];
|
|
21
|
+
/** Matches /dev/compositions/all: 161 default previews plus two landscape-only
|
|
22
|
+
* reference entries. Output capabilities overlap and must never be summed.
|
|
23
|
+
*/
|
|
24
|
+
export declare const FORMAL_COMPOSITIONS: readonly FormalComposition[];
|
|
25
|
+
/** Only output, ratio, and explicit IDs are hard gates; semantic fit is scored. */
|
|
26
|
+
export declare function applicableCompositions(kind: CompositionKind, ratio: number, id?: string): FormalComposition[];
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { compositionUseCase } from './compositionUseCases.js';
|
|
2
|
+
import { nativeSocialParameterContract } from './nativeSocialParameters.js';
|
|
3
|
+
import { DESIGN_RECIPES } from './designSystem.js';
|
|
4
|
+
import { SOCIAL_LAYOUT_CATALOG } from './socialLayouts.js';
|
|
5
|
+
import { recipeParameterContract } from './recipeParameters.js';
|
|
6
|
+
import { SEMANTIC_DISPLAY_NUMBERS, SOCIAL_DISPLAY_NUMBERS } from './compositionNumbers.js';
|
|
7
|
+
const both = ['presentation', 'social'];
|
|
8
|
+
const semantic = (id, accepts, minEntries = 0, maxEntries = 12, requiresImage = false) => ({ id, renderer: 'semantic', kind: 'social', family: id, accepts, minEntries, maxEntries, requiresImage });
|
|
9
|
+
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
|
+
}
|
|
12
|
+
/** Structural reference contracts shared with the product's existing renderers.
|
|
13
|
+
* Colorways and historical aliases remain outside active discovery.
|
|
14
|
+
*/
|
|
15
|
+
export const FORMAL_REFERENCE_COMPOSITIONS = [
|
|
16
|
+
{ id: 'authored-reference-photo', reference: 1, layout: 'authored-photo-editorial', accepts: ['image', 'paragraph'] },
|
|
17
|
+
{ id: 'authored-reference-manifesto', reference: 2, layout: 'authored-type-manifesto', accepts: ['paragraph'] },
|
|
18
|
+
{ id: 'authored-reference-journey', reference: 3, layout: 'authored-editorial-flow', accepts: ['steps'] },
|
|
19
|
+
{ id: 'authored-reference-vision', reference: 4, layout: 'authored-editorial-features', accepts: ['features'] },
|
|
20
|
+
{ id: 'authored-reference-matrix', reference: 5, layout: 'authored-editorial-matrix', accepts: ['comparison', 'paragraph'] },
|
|
21
|
+
{ id: 'authored-reference-diagonal', reference: 6, layout: 'authored-diagonal-cover', accepts: ['paragraph'] },
|
|
22
|
+
{ id: 'authored-reference-list', reference: 8, layout: 'authored-editorial-index', accepts: ['features'] },
|
|
23
|
+
{ id: 'authored-reference-story', reference: 11, layout: 'authored-visual-poster', accepts: ['image', 'paragraph', 'features'] },
|
|
24
|
+
{ id: 'authored-reference-sustainable', reference: 13, layout: 'authored-editorial-metrics', accepts: ['parameters'] },
|
|
25
|
+
{ id: 'authored-reference-code', reference: 14, layout: 'authored-code-panel', accepts: ['code'] },
|
|
26
|
+
{ id: 'authored-reference-bars', reference: 18, layout: 'authored-editorial-bars', accepts: ['chart'] },
|
|
27
|
+
{ id: 'authored-reference-share', reference: 19, layout: 'authored-editorial-donut', accepts: ['chart'] },
|
|
28
|
+
{ id: 'authored-reference-opinion', reference: 20, layout: 'authored-editorial-quote', accepts: ['quote'] },
|
|
29
|
+
{ id: 'authored-reference-comparison', reference: 22, layout: 'authored-editorial-comparison', accepts: ['comparison'] },
|
|
30
|
+
{ id: 'authored-reference-faq', reference: 23, layout: 'authored-editorial-faq', accepts: ['features'] },
|
|
31
|
+
{ id: 'authored-reference-trend', reference: 25, layout: 'authored-editorial-trend', accepts: ['chart'] },
|
|
32
|
+
];
|
|
33
|
+
const semanticRecipes = DESIGN_RECIPES.filter(recipe => recipe.renderer === 'semantic');
|
|
34
|
+
const uniqueSemantic = semanticRecipes.filter((recipe, index) => semanticRecipes.findIndex(other => other.id === recipe.id) === index);
|
|
35
|
+
const campaignDefinitions = [
|
|
36
|
+
['ledger-chart', ['parameters', 'chart'], 2, 6, false, true],
|
|
37
|
+
['detail-sidebar', ['paragraph', 'parameters'], 1, 3, false, true],
|
|
38
|
+
['technical-log', ['code', 'paragraph'], 0, 12, false, false],
|
|
39
|
+
['architecture-stack', ['steps', 'paragraph'], 2, 4, false, false],
|
|
40
|
+
['speaker-spotlight', ['quote', 'parameters'], 1, 6, true, false],
|
|
41
|
+
['speaker-lineup', ['quote', 'parameters'], 1, 6, true, false],
|
|
42
|
+
];
|
|
43
|
+
const founderDefinitions = [
|
|
44
|
+
['authored-mechanism-flow', ['features', 'steps', 'paragraph'], 2, 4],
|
|
45
|
+
['authored-reference-capability-map', ['features', 'paragraph'], 2, 4],
|
|
46
|
+
['authored-founder-timeline', ['steps', 'paragraph'], 2, 4],
|
|
47
|
+
['authored-founder-comparison', ['comparison', 'paragraph'], 2, 2],
|
|
48
|
+
];
|
|
49
|
+
const articleKinds = ['features', 'process', 'comparison', 'summary', 'checklist', 'faq', 'chart'];
|
|
50
|
+
const articleAccepts = { features: ['features', 'paragraph'], process: ['steps', 'paragraph'], comparison: ['comparison', 'paragraph'], summary: ['paragraph', 'features'], checklist: ['features', 'paragraph'], faq: ['features', 'paragraph'], chart: ['chart', 'paragraph'] };
|
|
51
|
+
function article(id, kind, variant) {
|
|
52
|
+
const cover = kind === 'cover';
|
|
53
|
+
const recipe = semantic(id, cover ? ['paragraph', 'features', 'image'] : articleAccepts[kind], 0, kind === 'chart' ? 12 : 3);
|
|
54
|
+
const number = cover ? variant + 1 : kind === 'features' && variant >= 3 ? 22 + variant : kind === 'process' && variant === 3 ? 33 : 4 + articleKinds.indexOf(kind) * 3 + variant;
|
|
55
|
+
const definition = define(recipe, [cover ? 'article-cover' : 'article-illustration'], id, `A${String(number).padStart(3, '0')}`, .5, 2.4);
|
|
56
|
+
// A cover is a headline, a short deck and its authored hero. It is not a
|
|
57
|
+
// miniature article: the original source remains in the candidate pool.
|
|
58
|
+
const coverSchema = { type: 'object', additionalProperties: false, required: ['title', 'blocks'], properties: {
|
|
59
|
+
title: { type: 'string', minLength: 1, maxLength: 72, description: 'A concise editorial headline, preferably four to eight words.' },
|
|
60
|
+
blocks: { type: 'array', minItems: 1, maxItems: 1, items: { type: 'object', additionalProperties: false, required: ['id', 'kind', 'text', 'evidence'], properties: {
|
|
61
|
+
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.' },
|
|
62
|
+
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 } } } },
|
|
63
|
+
} } },
|
|
64
|
+
} };
|
|
65
|
+
// The optional hero preserves compatibility with saved paragraph-only pools.
|
|
66
|
+
// New requests can reference a real asset; missing assets retain native artwork.
|
|
67
|
+
const coverWithHero = { ...coverSchema, properties: { ...coverSchema.properties, blocks: { ...coverSchema.properties.blocks, maxItems: 2,
|
|
68
|
+
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.',
|
|
69
|
+
items: [coverSchema.properties.blocks.items, { type: 'object', additionalProperties: false, required: ['id', 'kind', 'sourceId', 'alt', 'treatment', 'status', 'evidence'], properties: {
|
|
70
|
+
id: { type: 'string', minLength: 1, maxLength: 80 }, kind: { const: 'image' }, sourceId: { type: 'string', maxLength: 160 },
|
|
71
|
+
alt: { type: 'string', minLength: 1, maxLength: 300, description: 'A concise description of the image subject, grounded in the article topic.' },
|
|
72
|
+
treatment: { enum: ['photograph', 'diagram', 'screenshot'] }, status: { enum: ['available', 'missing'] }, assetId: { type: 'string', maxLength: 200 },
|
|
73
|
+
evidence: coverSchema.properties.blocks.items.properties.evidence,
|
|
74
|
+
} }],
|
|
75
|
+
} } };
|
|
76
|
+
const illustrationSchema = JSON.parse(JSON.stringify(definition.parameterSchema));
|
|
77
|
+
if (!cover) {
|
|
78
|
+
const primaryKind = kind === 'summary' ? 'features' : articleAccepts[kind][0];
|
|
79
|
+
const blocks = illustrationSchema.properties.blocks;
|
|
80
|
+
const primary = blocks.items.find((block) => block.properties.kind.const === primaryKind);
|
|
81
|
+
blocks.items = [primary];
|
|
82
|
+
blocks.minItems = 1;
|
|
83
|
+
blocks.maxItems = 1;
|
|
84
|
+
if (kind === 'faq')
|
|
85
|
+
primary.properties.entries.items.properties.title.pattern = '^.+[??]$';
|
|
86
|
+
}
|
|
87
|
+
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.'];
|
|
88
|
+
return { ...definition, ...(cover ? { parameterSchema: coverWithHero, capacity: { minEntries: 0, maxEntries: 0, requiresImage: false }, semanticDescription: coverDirections[variant] } : { parameterSchema: illustrationSchema }), recipe: undefined, rendererKey: `article-${kind}:${variant}`,
|
|
89
|
+
semanticDescription: compositionUseCase(id) + (cover ? ' ' + coverDirections[variant] : '') };
|
|
90
|
+
}
|
|
91
|
+
/** Matches /dev/compositions/all: 161 default previews plus two landscape-only
|
|
92
|
+
* reference entries. Output capabilities overlap and must never be summed.
|
|
93
|
+
*/
|
|
94
|
+
export const FORMAL_COMPOSITIONS = [
|
|
95
|
+
...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)),
|
|
96
|
+
define(semantic('authored-event-poster', ['parameters', 'paragraph'], 1, 6), ['social'], 'event:provider', 'E1'),
|
|
97
|
+
define(semantic('authored-image-triptych', ['paragraph', 'features', 'image'], 3, 3, true), ['social'], 'campaign:triptych', 'C1', .5, 2),
|
|
98
|
+
...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')}`)),
|
|
99
|
+
...SOCIAL_LAYOUT_CATALOG.map(preset => {
|
|
100
|
+
const recipe = DESIGN_RECIPES.find(item => item.id === preset.id && item.kind === 'social');
|
|
101
|
+
return { ...define({ ...recipe, renderer: 'catalog' }, ['social'], `social:${preset.id}`, SOCIAL_DISPLAY_NUMBERS[preset.id], .5, 2.4), ...nativeSocialParameterContract(preset.id) };
|
|
102
|
+
}),
|
|
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
|
+
...founderDefinitions.map(([id, accepts, min, max], index) => define(semantic(id, accepts, min, max), ['social'], `semantic:social:${id}`, `F${index + 1}`, 1.3)),
|
|
105
|
+
...[0, 1, 2].flatMap(variant => [article(`article-cover-${variant}`, 'cover', variant), ...articleKinds.map(kind => article(`article-${kind}-${variant}`, kind, variant))]),
|
|
106
|
+
...[3, 4, 5, 6, 7, 8, 9, 10].map(variant => article(`article-features-${variant}`, 'features', variant)),
|
|
107
|
+
article('article-process-3', 'process', 3),
|
|
108
|
+
];
|
|
109
|
+
/** Only output, ratio, and explicit IDs are hard gates; semantic fit is scored. */
|
|
110
|
+
export function applicableCompositions(kind, ratio, id) {
|
|
111
|
+
if (!Number.isFinite(ratio) || ratio <= 0)
|
|
112
|
+
throw new Error('COMPOSITION_INVALID_RATIO');
|
|
113
|
+
if (id && !FORMAL_COMPOSITIONS.some(item => item.id === id))
|
|
114
|
+
throw new Error('COMPOSITION_UNKNOWN_ID');
|
|
115
|
+
return FORMAL_COMPOSITIONS.filter(item => item.supportedKinds.includes(kind) && ratio >= item.aspectRatios.min && ratio <= item.aspectRatios.max && (!id || item.id === id));
|
|
116
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,3 +13,13 @@ export * from "./designBlueprint.js";
|
|
|
13
13
|
export * from './compositionScenes.js';
|
|
14
14
|
export * from './editorial.js';
|
|
15
15
|
export * from './fontCatalog.js';
|
|
16
|
+
export * from './candidateRanking.js';
|
|
17
|
+
export * from './styleManifest.js';
|
|
18
|
+
export * from './recipeParameters.js';
|
|
19
|
+
export * from './formalCompositions.js';
|
|
20
|
+
export * from './contentBrief.js';
|
|
21
|
+
export * from "./nativeSocialParameters.js";
|
|
22
|
+
export * from "./compositionUseCases.js";
|
|
23
|
+
export { compositionParameterDefaults } from './parameterDefaults.js';
|
|
24
|
+
export { SOCIAL_DISPLAY_NUMBERS } from './compositionNumbers.js';
|
|
25
|
+
export * from './textPresentation.js';
|
package/dist/index.js
CHANGED
|
@@ -13,3 +13,13 @@ export * from "./designBlueprint.js";
|
|
|
13
13
|
export * from './compositionScenes.js';
|
|
14
14
|
export * from './editorial.js';
|
|
15
15
|
export * from './fontCatalog.js';
|
|
16
|
+
export * from './candidateRanking.js';
|
|
17
|
+
export * from './styleManifest.js';
|
|
18
|
+
export * from './recipeParameters.js';
|
|
19
|
+
export * from './formalCompositions.js';
|
|
20
|
+
export * from './contentBrief.js';
|
|
21
|
+
export * from "./nativeSocialParameters.js";
|
|
22
|
+
export * from "./compositionUseCases.js";
|
|
23
|
+
export { compositionParameterDefaults } from './parameterDefaults.js';
|
|
24
|
+
export { SOCIAL_DISPLAY_NUMBERS } from './compositionNumbers.js';
|
|
25
|
+
export * from './textPresentation.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface LayoutContract {
|
|
2
2
|
id: string;
|
|
3
|
-
kind:
|
|
3
|
+
kind: "presentation" | "social";
|
|
4
4
|
semantic: string;
|
|
5
5
|
minItems: number;
|
|
6
6
|
maxItems: number;
|
|
@@ -10,6 +10,6 @@ export interface LayoutContract {
|
|
|
10
10
|
}
|
|
11
11
|
/** Eligibility is a first filter, never a substitute for measured fit. */
|
|
12
12
|
export declare const LAYOUT_CONTRACTS: readonly LayoutContract[];
|
|
13
|
-
export declare function eligibleLayouts(kind: LayoutContract[
|
|
13
|
+
export declare function eligibleLayouts(kind: LayoutContract["kind"], content: Record<string, unknown>, ratio: number): LayoutContract[];
|
|
14
14
|
/** Requested intent, same semantic scene, then general-purpose structures. */
|
|
15
|
-
export declare function layoutCandidates(kind: LayoutContract[
|
|
15
|
+
export declare function layoutCandidates(kind: LayoutContract["kind"], requested: string, content: Record<string, unknown>, ratio: number, limit?: number): string[];
|
package/dist/layoutContracts.js
CHANGED
|
@@ -1,22 +1,68 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { SOCIAL_LAYOUT_CATALOG } from "./socialLayouts.js";
|
|
2
|
+
const textOnly = new Set([
|
|
3
|
+
"cover",
|
|
4
|
+
"section",
|
|
5
|
+
"statement",
|
|
6
|
+
"quote",
|
|
7
|
+
"closing",
|
|
8
|
+
"image-full",
|
|
9
|
+
"og-cover",
|
|
10
|
+
"gradient-cover",
|
|
11
|
+
"bold-diagonal-cover",
|
|
12
|
+
"split-cover",
|
|
13
|
+
"block-banner",
|
|
14
|
+
"video-cover",
|
|
15
|
+
"magazine-pull-quote",
|
|
16
|
+
]);
|
|
17
|
+
const pair = new Set([
|
|
18
|
+
"comparison",
|
|
19
|
+
"two-column",
|
|
20
|
+
"myth",
|
|
21
|
+
"problem-solution",
|
|
22
|
+
"before-after",
|
|
23
|
+
"split-bar-compare",
|
|
24
|
+
]);
|
|
5
25
|
/** Eligibility is a first filter, never a substitute for measured fit. */
|
|
6
26
|
export const LAYOUT_CONTRACTS = [
|
|
7
|
-
...
|
|
8
|
-
|
|
27
|
+
...SOCIAL_LAYOUT_CATALOG.map(({ id, scene }) => ({
|
|
28
|
+
id,
|
|
29
|
+
kind: "social",
|
|
30
|
+
semantic: scene,
|
|
31
|
+
minItems: pair.has(id) ? 2 : 0,
|
|
32
|
+
maxItems: textOnly.has(id) ? 0 : pair.has(id) ? 2 : 6,
|
|
33
|
+
minRatio: 0.25,
|
|
34
|
+
maxRatio: 4,
|
|
35
|
+
required: [
|
|
36
|
+
"headline",
|
|
37
|
+
...(["pricing", "metric"].includes(scene) ? ["value"] : []),
|
|
38
|
+
],
|
|
39
|
+
})),
|
|
9
40
|
];
|
|
10
41
|
export function eligibleLayouts(kind, content, ratio) {
|
|
11
42
|
const count = Array.isArray(content.items) ? content.items.length : 0;
|
|
12
|
-
return LAYOUT_CONTRACTS.filter(layout => layout.kind === kind &&
|
|
43
|
+
return LAYOUT_CONTRACTS.filter((layout) => layout.kind === kind &&
|
|
44
|
+
ratio >= layout.minRatio - 1e-6 &&
|
|
45
|
+
ratio <= layout.maxRatio + 1e-6 &&
|
|
46
|
+
count >= layout.minItems &&
|
|
47
|
+
count <= layout.maxItems &&
|
|
48
|
+
layout.required.every((key) => typeof content[key] === "string" && String(content[key]).trim()));
|
|
13
49
|
}
|
|
14
50
|
/** Requested intent, same semantic scene, then general-purpose structures. */
|
|
15
51
|
export function layoutCandidates(kind, requested, content, ratio, limit = 8) {
|
|
16
52
|
const eligible = eligibleLayouts(kind, content, ratio);
|
|
17
|
-
const selected = LAYOUT_CONTRACTS.find(layout => layout.kind === kind && layout.id === requested);
|
|
18
|
-
const fallback = kind ===
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
53
|
+
const selected = LAYOUT_CONTRACTS.find((layout) => layout.kind === kind && layout.id === requested);
|
|
54
|
+
const fallback = kind === "presentation"
|
|
55
|
+
? ["cards", "two-column", "bullets", "table", "agenda", "statement"]
|
|
56
|
+
: ["report", "checklist", "steps", "bento", "editorial", "story"];
|
|
57
|
+
const order = [
|
|
58
|
+
requested,
|
|
59
|
+
...eligible
|
|
60
|
+
.filter((layout) => layout.semantic === (content.scene || selected?.semantic))
|
|
61
|
+
.map((layout) => layout.id),
|
|
62
|
+
...fallback,
|
|
63
|
+
];
|
|
64
|
+
const allowed = new Set(eligible.map((layout) => layout.id));
|
|
65
|
+
return [...new Set(order)]
|
|
66
|
+
.filter((id) => allowed.has(id))
|
|
67
|
+
.slice(0, Math.max(1, Math.min(16, limit)));
|
|
22
68
|
}
|
package/dist/layouts.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
export declare const PRESENTATION_LAYOUT_IDS: readonly ["cover", "
|
|
1
|
+
/** Content intents for structured presentations; not a separate renderer catalog. */
|
|
2
|
+
export declare const PRESENTATION_LAYOUT_IDS: readonly ["cover", "section", "statement", "cards", "process", "comparison", "bar-chart", "quote", "table", "closing"];
|