@lutrin/core 1.0.0
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/LICENSE +21 -0
- package/README.md +154 -0
- package/design/layouts/before-after.json +6 -0
- package/design/layouts/funnel.json +6 -0
- package/design/layouts/journey.json +5 -0
- package/design/layouts/key-message.json +5 -0
- package/design/layouts/portfolio.json +8 -0
- package/design/layouts/priority-matrix.json +7 -0
- package/design/layouts/pros-cons.json +6 -0
- package/design/layouts/pyramid.json +6 -0
- package/design/layouts/risk-map.json +8 -0
- package/design/layouts/roadmap.json +6 -0
- package/design/themes/default.json +159 -0
- package/package.json +57 -0
- package/src/cli.mjs +1114 -0
- package/src/deck/assets.mjs +910 -0
- package/src/deck/chart.mjs +424 -0
- package/src/deck/context.mjs +62 -0
- package/src/deck/highlight.mjs +206 -0
- package/src/deck/kit.mjs +342 -0
- package/src/deck/layout.mjs +1599 -0
- package/src/deck/parse.mjs +776 -0
- package/src/deck/suggest.mjs +33 -0
- package/src/deck/theme.mjs +1135 -0
- package/src/deck/tokens.mjs +268 -0
- package/src/deck/validate.mjs +737 -0
- package/src/html/render.mjs +1586 -0
- package/src/kit/archive.mjs +448 -0
- package/src/pptx/anim.mjs +196 -0
- package/src/pptx/fonts.mjs +204 -0
- package/src/pptx/morph.mjs +103 -0
- package/src/pptx/render.mjs +1379 -0
- package/src/vendor.mjs +281 -0
- package/src/worker/protocol.d.ts +82 -0
- package/src/worker/worker.mjs +145 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design tokens of the compiler's DEFAULT theme: a generic, neutral
|
|
3
|
+
* system ("Slate") — no branding. The complete JSON mirror lives in
|
|
4
|
+
* design/themes/default.json (a template to copy when theming an
|
|
5
|
+
* organization); an anti-drift test guarantees it stays identical to
|
|
6
|
+
* these values. Organization brands are KITS (kit.json + theme.json
|
|
7
|
+
* + layouts/ + fonts), resolved by resolveTheme (theme.mjs).
|
|
8
|
+
*
|
|
9
|
+
* Units: the 16:9 slide is 13.333 × 7.5 in, that is exactly
|
|
10
|
+
* 1280 × 720 px at 96 dpi. Everything is therefore composed in pixels on
|
|
11
|
+
* the system's 8 px grid, and converted to inches at render time (px / 96).
|
|
12
|
+
*
|
|
13
|
+
* Themability: each group is an object mutated IN PLACE by
|
|
14
|
+
* `applyTheme()` (theme.mjs) — consumers must read the tokens at call
|
|
15
|
+
* time, never copy them when the module loads. The derived groups
|
|
16
|
+
* (PAGE.margin/gutter/footerHeight, LAYER_SHADES, TREND_INK,
|
|
17
|
+
* SEMANTIC) are recomputed by `deriveTokens()` so they follow a theme's
|
|
18
|
+
* palette.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export const COLORS = {
|
|
22
|
+
primary: '1D4ED8',
|
|
23
|
+
primaryLighter: '60A5FA',
|
|
24
|
+
primaryDarker: '1E3A8A',
|
|
25
|
+
brand: '1D4ED8',
|
|
26
|
+
brandBlack: '111827',
|
|
27
|
+
neutralPrimary: '212529',
|
|
28
|
+
neutralSecondary: '637381',
|
|
29
|
+
neutralTertiary: 'ADB2BD',
|
|
30
|
+
neutralStroke: 'CED4DA',
|
|
31
|
+
underground1: 'F8F9FA',
|
|
32
|
+
underground2: 'DEE2E6',
|
|
33
|
+
ground: 'FFFFFF',
|
|
34
|
+
highlightLight: 'EFF6FF',
|
|
35
|
+
highlightStrong: 'DBEAFE',
|
|
36
|
+
informative: '0079C4',
|
|
37
|
+
informativeDark: '004B7B',
|
|
38
|
+
informativeLight: 'E6F5F9',
|
|
39
|
+
positive: '0DA566',
|
|
40
|
+
positiveDark: '025D29',
|
|
41
|
+
positiveLight: 'E7F6F0',
|
|
42
|
+
warning: 'FFB833',
|
|
43
|
+
warningDark: '6C4600',
|
|
44
|
+
warningLight: 'FEFAE6',
|
|
45
|
+
negative: 'D3310A',
|
|
46
|
+
negativeDark: '851A00',
|
|
47
|
+
negativeLight: 'FFEBE6',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Default font: Arial — installed everywhere, identical rendering in
|
|
52
|
+
* PowerPoint and in browsers without embedding anything. A theme supplies
|
|
53
|
+
* its family through `fonts.body` and its files through `fonts.files`.
|
|
54
|
+
*/
|
|
55
|
+
export const FONTS = {
|
|
56
|
+
body: 'Arial',
|
|
57
|
+
mono: 'Courier New',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Files of the embedded font, themable: `regular`/`bold`/`italic` are
|
|
62
|
+
* the .ttf files embedded in the .pptx; the HTML renderer inlines the
|
|
63
|
+
* same names as .woff2 (same file name, .woff2 extension — supply both
|
|
64
|
+
* formats side by side). A theme's paths are resolved relative to the
|
|
65
|
+
* theme file by resolveTheme(). The default embeds nothing (Arial is
|
|
66
|
+
* already on the machine).
|
|
67
|
+
*/
|
|
68
|
+
export const FONT_FILES = {
|
|
69
|
+
regular: null,
|
|
70
|
+
bold: null,
|
|
71
|
+
italic: null,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Chrome signatures (logos), themable: `cover` is placed at the top of the
|
|
76
|
+
* cover slide, `section` (a reversed variant, meant for the primary
|
|
77
|
+
* background) at the bottom of section slides. `cover`/`section` feed the
|
|
78
|
+
* PPTX (bitmap required: png/jpg); `coverSvg`/`sectionSvg` the HTML output
|
|
79
|
+
* (svg accepted, falling back to the bitmap). Absolute paths after
|
|
80
|
+
* resolveTheme(). The default has no signature: both renderers simply omit
|
|
81
|
+
* the logo when the path is null or not found.
|
|
82
|
+
*/
|
|
83
|
+
export const LOGOS = {
|
|
84
|
+
cover: null,
|
|
85
|
+
coverSvg: null,
|
|
86
|
+
section: null,
|
|
87
|
+
sectionSvg: null,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** Sizes in points, derived from the DESIGN.md scale (px × 0.75 = pt),
|
|
91
|
+
* bumped up one notch for projection. */
|
|
92
|
+
export const TYPE = {
|
|
93
|
+
coverTitle: 40,
|
|
94
|
+
coverSubtitle: 20,
|
|
95
|
+
sectionTitle: 32,
|
|
96
|
+
slideTitle: 26,
|
|
97
|
+
sectionHeading: 16, // h2 inside a slide (slot title)
|
|
98
|
+
lead: 16,
|
|
99
|
+
body: 14,
|
|
100
|
+
bullet: 14,
|
|
101
|
+
bulletNested: 13,
|
|
102
|
+
small: 11,
|
|
103
|
+
caption: 9,
|
|
104
|
+
code: 11,
|
|
105
|
+
metricValue: 44,
|
|
106
|
+
metricLabel: 12,
|
|
107
|
+
quote: 22,
|
|
108
|
+
tableBody: 12,
|
|
109
|
+
tableHeader: 12,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const LINE_HEIGHT = 1.4; // structural, not themable (scalar export)
|
|
113
|
+
|
|
114
|
+
/** 8 px grid (DESIGN.md: xs 8 · sm 16 · md 24 · lg 32 · xl 40 · xxl 48). */
|
|
115
|
+
export const SPACE = { xs: 8, sm: 16, md: 24, lg: 32, xl: 40, xxl: 48 };
|
|
116
|
+
|
|
117
|
+
/** Slide geometry, in pixels (1280 × 720 @96 dpi).
|
|
118
|
+
* width/height are the physical 16:9 frame — never themable;
|
|
119
|
+
* margin/gutter/footerHeight are derived from SPACE (deriveTokens). */
|
|
120
|
+
export const PAGE = {
|
|
121
|
+
width: 1280,
|
|
122
|
+
height: 720,
|
|
123
|
+
margin: SPACE.xxl, // 48 px
|
|
124
|
+
gutter: SPACE.md, // 24 px
|
|
125
|
+
titleHeight: 96, // 2 × 48: title zone of content slides
|
|
126
|
+
footerHeight: SPACE.lg, // 32 px
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/** Utility radii (px). PPTX rectangles take a 0–1 ratio. */
|
|
130
|
+
export const ROUNDED = { sm: 2, md: 4, lg: 8, pill: 64 };
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Chrome of the cover/section layouts and of the content masters — geometry
|
|
134
|
+
* shared by BOTH renderers (pptx and html) so that parity is structural
|
|
135
|
+
* rather than two sets of literals to keep in sync.
|
|
136
|
+
* All values in px on the 1280 × 720 grid.
|
|
137
|
+
*/
|
|
138
|
+
export const CHROME = {
|
|
139
|
+
cover: {
|
|
140
|
+
barY: 280, // accent bar above the title
|
|
141
|
+
barW: 96,
|
|
142
|
+
barH: 6,
|
|
143
|
+
titleY: 304,
|
|
144
|
+
titleH: 120,
|
|
145
|
+
subtitleY: 424,
|
|
146
|
+
subtitleH: 72,
|
|
147
|
+
bylineBottom: 80, // distance byline → bottom of page
|
|
148
|
+
bylineH: 32,
|
|
149
|
+
logoH: 44,
|
|
150
|
+
},
|
|
151
|
+
section: {
|
|
152
|
+
titleY: 288,
|
|
153
|
+
titleH: 144,
|
|
154
|
+
logoH: 32,
|
|
155
|
+
},
|
|
156
|
+
title: {
|
|
157
|
+
accentW: 64, // accent segment of the title rule
|
|
158
|
+
accentH: 4,
|
|
159
|
+
},
|
|
160
|
+
footer: {
|
|
161
|
+
textW: 600,
|
|
162
|
+
h: 24,
|
|
163
|
+
numW: 64, // page number, right-aligned
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const px = (v) => v / 96; // px → inches
|
|
168
|
+
|
|
169
|
+
/** Usable content area of a content slide (below the title, above the footer). */
|
|
170
|
+
export function contentArea() {
|
|
171
|
+
const x = PAGE.margin;
|
|
172
|
+
const y = PAGE.titleHeight + SPACE.sm;
|
|
173
|
+
return {
|
|
174
|
+
x,
|
|
175
|
+
y,
|
|
176
|
+
w: PAGE.width - 2 * PAGE.margin,
|
|
177
|
+
h: PAGE.height - y - PAGE.footerHeight - SPACE.sm,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Categorical chart palette — six neutral hues (teal, ochre, blue, red,
|
|
183
|
+
* dark blue, brown), lightness and chroma tuned to pass the six dataviz
|
|
184
|
+
* accessibility checks (OKLCH band 0.43–0.77, chroma ≥ 0.10, adjacent CVD
|
|
185
|
+
* ΔE ≥ 12, contrast ≥ 3:1 on white). Fixed order — never assign in a
|
|
186
|
+
* loop: past six series, group the rest under "Other". Palette independent
|
|
187
|
+
* of COLORS: a theme that changes `primary` must supply its own
|
|
188
|
+
* `chartColors`.
|
|
189
|
+
*/
|
|
190
|
+
export const CHART_COLORS = ['0A8A76', 'B87F00', '0079C4', 'D3310A', '005E99', '8A5C00'];
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Shades of the `layers` layout (architecture layers), from the base (dark)
|
|
194
|
+
* up to the surface (light) — only hues of the primary token, the system's
|
|
195
|
+
* only accent. `ink` = the ink legible on the shade (contrast ≥ 4.5:1);
|
|
196
|
+
* white on the two dark shades only.
|
|
197
|
+
* Derived from COLORS — recipe in deriveTokens().
|
|
198
|
+
*/
|
|
199
|
+
export const LAYER_SHADES = [];
|
|
200
|
+
|
|
201
|
+
/** Trend inks of the `:::metric` cards (dark: small body text).
|
|
202
|
+
* Derived from COLORS — recipe in deriveTokens(). */
|
|
203
|
+
export const TREND_INK = {};
|
|
204
|
+
|
|
205
|
+
/** Tints of the :::info/success/warning/danger callouts.
|
|
206
|
+
* fill/text derived from COLORS (deriveTokens); label localizable. */
|
|
207
|
+
export const SEMANTIC = {};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Recipes for the groups derived from COLORS/SPACE: run when the module
|
|
211
|
+
* loads, then re-run by applyTheme() AFTER a theme is merged so that
|
|
212
|
+
* LAYER_SHADES, SEMANTIC, TREND_INK and PAGE's margins follow the
|
|
213
|
+
* palette — an explicit theme override on a derived group is then
|
|
214
|
+
* re-merged on top (theme.mjs).
|
|
215
|
+
*/
|
|
216
|
+
export function deriveTokens() {
|
|
217
|
+
PAGE.margin = SPACE.xxl;
|
|
218
|
+
PAGE.gutter = SPACE.md;
|
|
219
|
+
PAGE.footerHeight = SPACE.lg;
|
|
220
|
+
|
|
221
|
+
LAYER_SHADES.splice(
|
|
222
|
+
0,
|
|
223
|
+
LAYER_SHADES.length,
|
|
224
|
+
{ fill: COLORS.primaryDarker, ink: COLORS.ground },
|
|
225
|
+
{ fill: COLORS.primary, ink: COLORS.ground },
|
|
226
|
+
{ fill: COLORS.primaryLighter, ink: COLORS.neutralPrimary },
|
|
227
|
+
{ fill: COLORS.highlightStrong, ink: COLORS.primaryDarker },
|
|
228
|
+
{ fill: COLORS.highlightLight, ink: COLORS.primaryDarker },
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
Object.assign(TREND_INK, {
|
|
232
|
+
positive: COLORS.positiveDark,
|
|
233
|
+
negative: COLORS.negativeDark,
|
|
234
|
+
neutral: COLORS.neutralSecondary,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
Object.assign(SEMANTIC, {
|
|
238
|
+
info: { fill: COLORS.informativeLight, text: COLORS.informativeDark, label: 'Info' },
|
|
239
|
+
success: { fill: COLORS.positiveLight, text: COLORS.positiveDark, label: 'Key point' },
|
|
240
|
+
warning: { fill: COLORS.warningLight, text: COLORS.warningDark, label: 'Caution' },
|
|
241
|
+
danger: { fill: COLORS.negativeLight, text: COLORS.negativeDark, label: 'Important' },
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
deriveTokens();
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Panel styles of the structured layouts (comparison, pillars, layers,
|
|
248
|
+
* swot): fill + rule per variant — a flat system, no shadow. Shared by
|
|
249
|
+
* both renderers to guarantee identical rendering.
|
|
250
|
+
*/
|
|
251
|
+
export function panelStyle(block) {
|
|
252
|
+
switch (block.variant) {
|
|
253
|
+
case 'accent':
|
|
254
|
+
// solid accent bar (focus layout) — same ink as the title rule
|
|
255
|
+
return { fill: COLORS.primary, line: null };
|
|
256
|
+
case 'highlight':
|
|
257
|
+
return { fill: COLORS.highlightLight, line: { color: COLORS.primary, width: 1.25 } };
|
|
258
|
+
case 'pillar':
|
|
259
|
+
return { fill: COLORS.ground, line: { color: COLORS.neutralStroke, width: 1 } };
|
|
260
|
+
case 'semantic':
|
|
261
|
+
return { fill: (SEMANTIC[block.kind] ?? SEMANTIC.info).fill, line: null };
|
|
262
|
+
case 'layer':
|
|
263
|
+
return { fill: (LAYER_SHADES[block.shade] ?? LAYER_SHADES[0]).fill, line: null };
|
|
264
|
+
case 'muted':
|
|
265
|
+
default:
|
|
266
|
+
return { fill: COLORS.underground1, line: { color: COLORS.neutralStroke, width: 1 } };
|
|
267
|
+
}
|
|
268
|
+
}
|