@lemonppt/renderer 0.2.0 → 0.2.2

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 (72) hide show
  1. package/dist/client/client-render.js +65 -5401
  2. package/dist/client/editor-script.js +3355 -0
  3. package/dist/client/theme-echarts.js +65 -25
  4. package/dist/client-render.d.ts +1 -7
  5. package/dist/client-render.d.ts.map +1 -1
  6. package/dist/client-render.js +1 -11
  7. package/dist/client-render.js.map +1 -1
  8. package/dist/editor-script.d.ts +1 -1
  9. package/dist/editor-script.d.ts.map +1 -1
  10. package/dist/editor-script.js +80 -77
  11. package/dist/editor-script.js.map +1 -1
  12. package/dist/export-pdf.js +1 -1
  13. package/dist/export-pdf.js.map +1 -1
  14. package/dist/export-pptx.d.ts +1132 -2
  15. package/dist/export-pptx.d.ts.map +1 -1
  16. package/dist/export-pptx.js +1208 -19525
  17. package/dist/export-pptx.js.map +1 -1
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/render-editor-data.d.ts +21 -0
  23. package/dist/render-editor-data.d.ts.map +1 -0
  24. package/dist/render-editor-data.js +1068 -0
  25. package/dist/render-editor-data.js.map +1 -0
  26. package/dist/render.d.ts +0 -2
  27. package/dist/render.d.ts.map +1 -1
  28. package/dist/render.js +7 -323
  29. package/dist/render.js.map +1 -1
  30. package/dist/theme01-pptx.d.ts +12 -0
  31. package/dist/theme01-pptx.d.ts.map +1 -0
  32. package/dist/theme01-pptx.js +2954 -0
  33. package/dist/theme01-pptx.js.map +1 -0
  34. package/dist/theme02-pptx.d.ts +5 -0
  35. package/dist/theme02-pptx.d.ts.map +1 -0
  36. package/dist/theme02-pptx.js +1063 -0
  37. package/dist/theme02-pptx.js.map +1 -0
  38. package/dist/theme03-pptx.d.ts +5 -0
  39. package/dist/theme03-pptx.d.ts.map +1 -0
  40. package/dist/theme03-pptx.js +3279 -0
  41. package/dist/theme03-pptx.js.map +1 -0
  42. package/dist/theme04-pptx.d.ts +6 -0
  43. package/dist/theme04-pptx.d.ts.map +1 -0
  44. package/dist/theme04-pptx.js +5496 -0
  45. package/dist/theme04-pptx.js.map +1 -0
  46. package/dist/theme05-pptx.d.ts +5 -0
  47. package/dist/theme05-pptx.d.ts.map +1 -0
  48. package/dist/theme05-pptx.js +3152 -0
  49. package/dist/theme05-pptx.js.map +1 -0
  50. package/dist/theme06-pptx.d.ts +124 -0
  51. package/dist/theme06-pptx.d.ts.map +1 -0
  52. package/dist/theme06-pptx.js +8154 -0
  53. package/dist/theme06-pptx.js.map +1 -0
  54. package/dist/theme07-pptx.d.ts +49 -0
  55. package/dist/theme07-pptx.d.ts.map +1 -0
  56. package/dist/theme07-pptx.js +2579 -0
  57. package/dist/theme07-pptx.js.map +1 -0
  58. package/dist/theme08-pptx.d.ts +19 -0
  59. package/dist/theme08-pptx.d.ts.map +1 -0
  60. package/dist/theme08-pptx.js +1411 -0
  61. package/dist/theme08-pptx.js.map +1 -0
  62. package/dist/theme09-pptx.d.ts +18 -0
  63. package/dist/theme09-pptx.d.ts.map +1 -0
  64. package/dist/theme09-pptx.js +1513 -0
  65. package/dist/theme09-pptx.js.map +1 -0
  66. package/dist/theme10-pptx.d.ts +18 -0
  67. package/dist/theme10-pptx.d.ts.map +1 -0
  68. package/dist/theme10-pptx.js +2889 -0
  69. package/dist/theme10-pptx.js.map +1 -0
  70. package/package.json +7 -6
  71. package/templates/editor.html +580 -0
  72. package/templates/editor.js +222 -0
@@ -0,0 +1,1411 @@
1
+ // lemonPPT - AI-powered presentation generation
2
+ // Copyright (c) 2026 lemonforme
3
+ // SPDX-License-Identifier: AGPL-3.0-or-later
4
+ //
5
+ // theme08 黑金实验风 — PPTX 版式专属渲染器。
6
+ // 解决此前 theme08 在 PPTX 导出中 37/39 版式落到 "Unknown layout" 占位的问题。
7
+ // 所有渲染器基于 theme08 设计语言:深黑底 + 荧光金主强调(primary)/ 象牙暖白 + 古铜金(muted)。
8
+ let S;
9
+ /** 每次导出开始时由 export-pptx.ts 注入当前主题的配色/字体/深浅模式。 */
10
+ export function configureTheme08(state) {
11
+ S = state;
12
+ }
13
+ // ---- 画布常量(16:9,10 x 5.625 英寸) ----
14
+ const MX = 0.7; // 左右页边距
15
+ const CW = 10 - MX * 2; // 内容宽度 8.6
16
+ const BODY_TOP = 2.05; // 正文区起始 y
17
+ // ---- 颜色速取 ----
18
+ const ink = () => S.colors.primary;
19
+ const ink2 = () => S.colors.secondary;
20
+ const gold = () => S.colors.accent;
21
+ const surface = () => S.colors.surface;
22
+ const surfaceElev = () => S.colors.surfaceElevated;
23
+ const border = () => S.colors.border;
24
+ const isDark = () => !S.muted;
25
+ const mono = () => S.fonts.mono;
26
+ const heading = () => S.fonts.heading;
27
+ const body = () => S.fonts.body;
28
+ // ---- 基础绘制 ----
29
+ function txt(slide, text, o) {
30
+ if (text == null || text === '')
31
+ return;
32
+ slide.addText(text, o);
33
+ }
34
+ /** 设置主题背景 + 顶部金色装饰条。 */
35
+ function bg(slide) {
36
+ slide.background = { color: surface() };
37
+ slide.addShape('rect', { x: 0, y: 0, w: 10, h: 0.1, fill: { color: gold() } });
38
+ // 底部细线
39
+ slide.addShape('line', {
40
+ x: MX, y: 5.32, w: CW, h: 0,
41
+ line: { color: border(), width: 0.75 },
42
+ });
43
+ }
44
+ function kicker(slide, text, x = MX, y = 0.45) {
45
+ txt(slide, text, {
46
+ x, y, w: CW, h: 0.35, fontSize: 13, color: gold(), align: 'left',
47
+ fontFace: mono(), charSpacing: 2, bold: true,
48
+ });
49
+ }
50
+ function title(slide, text, x = MX, y = 0.82, w = CW, size = 30) {
51
+ txt(slide, text, {
52
+ x, y, w, h: 0.9, fontSize: size, color: ink(), align: 'left',
53
+ fontFace: heading(), bold: true, valign: 'top',
54
+ });
55
+ }
56
+ function subtitle(slide, text, x = MX, y = 1.62, w = CW) {
57
+ txt(slide, text, {
58
+ x, y, w, h: 0.4, fontSize: 15, color: ink2(), align: 'left', fontFace: body(),
59
+ });
60
+ }
61
+ function footer(slide, left, right) {
62
+ txt(slide, left, { x: MX, y: 5.34, w: CW / 2, h: 0.25, fontSize: 9, color: ink2(), align: 'left', fontFace: mono() });
63
+ txt(slide, right, { x: MX + CW / 2, y: 5.34, w: CW / 2, h: 0.25, fontSize: 9, color: ink2(), align: 'right', fontFace: mono() });
64
+ }
65
+ /** 圆角卡片背景。 */
66
+ function card(slide, x, y, w, h, fill, stroke) {
67
+ slide.addShape('roundRect', {
68
+ x, y, w, h, rectRadius: 0.08,
69
+ fill: { color: fill || surfaceElev() },
70
+ line: { color: stroke || border(), width: 1 },
71
+ });
72
+ }
73
+ /** 网格列宽计算。 */
74
+ function cols(n, max = 4) {
75
+ return Math.min(n, max);
76
+ }
77
+ // =====================================================================
78
+ // 1. cover
79
+ // =====================================================================
80
+ function renderTheme08Cover(slide, p) {
81
+ bg(slide);
82
+ if (p.imageUrl) {
83
+ slide.addImage({ path: p.imageUrl, x: 0, y: 0, w: 10, h: 5.625 });
84
+ slide.addShape('rect', { x: 0, y: 0, w: 10, h: 5.625, fill: { color: isDark() ? '14130D' : 'FDFBF4', transparency: 35 } });
85
+ }
86
+ if (p.tag) {
87
+ slide.addShape('roundRect', { x: MX, y: 1.0, w: 0.4 + p.tag.length * 0.16, h: 0.42, rectRadius: 0.21, fill: { color: gold() } });
88
+ txt(slide, p.tag, { x: MX, y: 1.0, w: 0.4 + p.tag.length * 0.16, h: 0.42, fontSize: 13, color: isDark() ? '14130D' : 'FFFFFF', align: 'center', valign: 'middle', bold: true, fontFace: mono() });
89
+ }
90
+ txt(slide, p.title, { x: MX, y: 1.65, w: CW, h: 1.5, fontSize: 46, color: ink(), align: 'left', fontFace: heading(), bold: true, valign: 'top' });
91
+ txt(slide, p.subtitle, { x: MX, y: 3.2, w: CW, h: 0.8, fontSize: 18, color: ink2(), align: 'left', fontFace: body() });
92
+ const metrics = (p.metrics || []).slice(0, 4);
93
+ if (metrics.length) {
94
+ const mw = CW / metrics.length;
95
+ metrics.forEach((m, i) => {
96
+ const x = MX + i * mw;
97
+ const val = [m.value, m.unit].filter(Boolean).join(' ');
98
+ txt(slide, val, { x, y: 4.25, w: mw, h: 0.5, fontSize: 26, color: m.accent ? gold() : ink(), align: 'left', bold: true, fontFace: heading() });
99
+ txt(slide, m.label, { x, y: 4.78, w: mw, h: 0.35, fontSize: 12, color: ink2(), align: 'left', fontFace: body() });
100
+ });
101
+ }
102
+ if (p.tags && p.tags.length) {
103
+ txt(slide, p.tags.join(' · '), { x: MX, y: 5.1, w: CW, h: 0.3, fontSize: 11, color: ink2(), align: 'left', fontFace: mono() });
104
+ }
105
+ footer(slide, p.footnoteLeft, p.footnoteRight);
106
+ }
107
+ // =====================================================================
108
+ // 2. chapter
109
+ // =====================================================================
110
+ function renderTheme08Chapter(slide, p) {
111
+ bg(slide);
112
+ if (p.index) {
113
+ txt(slide, p.index, { x: MX, y: 1.4, w: 3, h: 2.4, fontSize: 150, color: gold(), align: 'left', valign: 'middle', bold: true, fontFace: heading() });
114
+ }
115
+ if (p.kicker) {
116
+ txt(slide, p.kicker, { x: 4.2, y: 1.9, w: 5, h: 0.4, fontSize: 14, color: gold(), align: 'left', fontFace: mono(), bold: true, charSpacing: 2 });
117
+ }
118
+ txt(slide, p.title, { x: 4.2, y: 2.35, w: 5.1, h: 1.0, fontSize: 38, color: ink(), align: 'left', valign: 'top', bold: true, fontFace: heading() });
119
+ txt(slide, p.subtitle, { x: 4.2, y: 3.4, w: 5.1, h: 0.8, fontSize: 16, color: ink2(), align: 'left', fontFace: body() });
120
+ footer(slide, p.footnoteLeft, p.footnoteRight);
121
+ }
122
+ // =====================================================================
123
+ // 3. content
124
+ // =====================================================================
125
+ function renderTheme08Content(slide, p) {
126
+ bg(slide);
127
+ kicker(slide, p.kicker);
128
+ title(slide, p.title);
129
+ subtitle(slide, p.subtitle);
130
+ const items = (p.items || []).slice(0, 4);
131
+ const n = cols(items.length, 2);
132
+ const gap = 0.3;
133
+ const cw = (CW - gap * (n - 1)) / n;
134
+ const ch = 1.35;
135
+ items.forEach((it, i) => {
136
+ const r = Math.floor(i / n);
137
+ const c = i % n;
138
+ const x = MX + c * (cw + gap);
139
+ const y = BODY_TOP + r * (ch + 0.25);
140
+ card(slide, x, y, cw, ch);
141
+ txt(slide, it.title, { x: x + 0.25, y: y + 0.18, w: cw - 0.5, h: 0.45, fontSize: 17, color: ink(), bold: true, align: 'left', fontFace: heading() });
142
+ txt(slide, it.desc, { x: x + 0.25, y: y + 0.65, w: cw - 0.5, h: ch - 0.8, fontSize: 12.5, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
143
+ });
144
+ const stats = (p.stats || []).slice(0, 4);
145
+ if (stats.length) {
146
+ const sy = BODY_TOP + 2 * (ch + 0.25) + 0.05;
147
+ const sw = CW / stats.length;
148
+ stats.forEach((st, i) => {
149
+ const x = MX + i * sw;
150
+ txt(slide, [st.num, st.label].filter(Boolean).join(' '), { x, y: sy, w: sw, h: 0.5, fontSize: 16, color: gold(), bold: true, align: 'left', fontFace: heading() });
151
+ });
152
+ }
153
+ footer(slide, p.footnoteLeft, p.footnoteRight);
154
+ }
155
+ // =====================================================================
156
+ // 4. contents(目录)
157
+ // =====================================================================
158
+ function renderTheme08Contents(slide, p) {
159
+ bg(slide);
160
+ kicker(slide, p.kicker);
161
+ title(slide, p.title);
162
+ subtitle(slide, p.subtitle);
163
+ const items = (p.items || []).slice(0, 6);
164
+ let y = BODY_TOP;
165
+ const rowH = (5.1 - BODY_TOP) / Math.max(items.length, 1);
166
+ items.forEach((it, i) => {
167
+ const num = String(i + 1).padStart(2, '0');
168
+ txt(slide, num, { x: MX, y, w: 1.0, h: rowH, fontSize: 26, color: gold(), align: 'left', valign: 'middle', bold: true, fontFace: mono() });
169
+ txt(slide, it.title, { x: MX + 1.1, y, w: CW - 1.1, h: rowH, fontSize: 22, color: ink(), align: 'left', valign: 'middle', fontFace: body() });
170
+ slide.addShape('line', { x: MX + 1.1, y: y + rowH, w: CW - 1.1, h: 0, line: { color: border(), width: 0.5 } });
171
+ y += rowH;
172
+ });
173
+ footer(slide, p.footnoteLeft, p.footnoteRight);
174
+ }
175
+ // =====================================================================
176
+ // 5. metrics(指标卡)
177
+ // =====================================================================
178
+ function renderTheme08Metrics(slide, p) {
179
+ bg(slide);
180
+ kicker(slide, p.kicker);
181
+ title(slide, p.title);
182
+ subtitle(slide, p.subtitle);
183
+ const items = (p.items || []).slice(0, 4);
184
+ const n = cols(items.length, 4);
185
+ const gap = 0.3;
186
+ const cw = (CW - gap * (n - 1)) / n;
187
+ const ch = 2.4;
188
+ items.forEach((it, i) => {
189
+ const x = MX + i * (cw + gap);
190
+ card(slide, x, BODY_TOP, cw, ch, it.accent ? gold() : undefined);
191
+ const tcol = it.accent ? (isDark() ? '14130D' : 'FFFFFF') : ink();
192
+ txt(slide, [it.value, it.unit].filter(Boolean).join(' '), { x: x + 0.2, y: BODY_TOP + 0.4, w: cw - 0.4, h: 0.9, fontSize: 34, color: tcol, align: 'center', bold: true, fontFace: heading() });
193
+ txt(slide, it.label, { x: x + 0.2, y: BODY_TOP + 1.4, w: cw - 0.4, h: 0.8, fontSize: 13, color: tcol, align: 'center', valign: 'top', fontFace: body() });
194
+ });
195
+ footer(slide, p.footnoteLeft, p.footnoteRight);
196
+ }
197
+ // =====================================================================
198
+ // 6. metric-big
199
+ // =====================================================================
200
+ function renderTheme08MetricBig(slide, p) {
201
+ bg(slide);
202
+ kicker(slide, p.kicker, MX, 1.4);
203
+ const val = [p.value, p.unit].filter(Boolean).join(' ');
204
+ txt(slide, val, { x: MX, y: 1.9, w: CW, h: 1.8, fontSize: 100, color: gold(), align: 'left', bold: true, fontFace: heading(), valign: 'middle' });
205
+ txt(slide, p.title, { x: MX, y: 3.8, w: CW, h: 0.6, fontSize: 24, color: ink(), align: 'left', fontFace: heading(), bold: true });
206
+ txt(slide, p.desc, { x: MX, y: 4.45, w: CW, h: 0.7, fontSize: 15, color: ink2(), align: 'left', fontFace: body() });
207
+ footer(slide, p.footnoteLeft, p.footnoteRight);
208
+ }
209
+ // =====================================================================
210
+ // 7. feature
211
+ // =====================================================================
212
+ function renderTheme08Feature(slide, p) {
213
+ bg(slide);
214
+ kicker(slide, p.kicker);
215
+ title(slide, p.title);
216
+ subtitle(slide, p.subtitle);
217
+ const items = (p.items || []).slice(0, 6);
218
+ const n = cols(items.length, 3);
219
+ const gap = 0.3;
220
+ const cw = (CW - gap * (n - 1)) / n;
221
+ const ch = 2.55;
222
+ items.forEach((it, i) => {
223
+ const r = Math.floor(i / n);
224
+ const c = i % n;
225
+ const x = MX + c * (cw + gap);
226
+ const y = BODY_TOP + r * (ch + 0.25);
227
+ card(slide, x, y, cw, ch);
228
+ if (it.icon)
229
+ txt(slide, it.icon, { x: x + 0.25, y: y + 0.22, w: 0.8, h: 0.6, fontSize: 28, align: 'left', fontFace: body() });
230
+ txt(slide, it.title, { x: x + 0.25, y: y + 0.95, w: cw - 0.5, h: 0.45, fontSize: 16, color: ink(), bold: true, align: 'left', fontFace: heading() });
231
+ txt(slide, it.desc, { x: x + 0.25, y: y + 1.4, w: cw - 0.5, h: ch - 1.5, fontSize: 12, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
232
+ });
233
+ footer(slide, p.footnoteLeft, p.footnoteRight);
234
+ }
235
+ // =====================================================================
236
+ // 8. overview
237
+ // =====================================================================
238
+ function renderTheme08Overview(slide, p) {
239
+ bg(slide);
240
+ kicker(slide, p.kicker);
241
+ title(slide, p.title);
242
+ subtitle(slide, p.subtitle);
243
+ const items = (p.items || []).slice(0, 4);
244
+ const n = cols(items.length, 2);
245
+ const gap = 0.3;
246
+ const cw = (CW - gap * (n - 1)) / n;
247
+ const ch = 1.45;
248
+ items.forEach((it, i) => {
249
+ const r = Math.floor(i / n);
250
+ const c = i % n;
251
+ const x = MX + c * (cw + gap);
252
+ const y = BODY_TOP + r * (ch + 0.25);
253
+ card(slide, x, y, cw, ch);
254
+ txt(slide, it.num, { x: x + 0.25, y: y + 0.2, w: 1.2, h: 1.0, fontSize: 40, color: gold(), align: 'left', valign: 'middle', bold: true, fontFace: heading() });
255
+ txt(slide, it.label, { x: x + 1.55, y: y + 0.2, w: cw - 1.8, h: 0.5, fontSize: 16, color: ink(), bold: true, align: 'left', fontFace: heading() });
256
+ txt(slide, it.desc, { x: x + 1.55, y: y + 0.7, w: cw - 1.8, h: ch - 0.85, fontSize: 12, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
257
+ });
258
+ footer(slide, p.footnoteLeft, p.footnoteRight);
259
+ }
260
+ // =====================================================================
261
+ // 9. strategy
262
+ // =====================================================================
263
+ function renderTheme08Strategy(slide, p) {
264
+ bg(slide);
265
+ kicker(slide, p.kicker);
266
+ title(slide, p.title);
267
+ subtitle(slide, p.subtitle);
268
+ const items = (p.items || []).slice(0, 4);
269
+ const n = cols(items.length, 2);
270
+ const gap = 0.3;
271
+ const cw = (CW - gap * (n - 1)) / n;
272
+ const ch = 1.4;
273
+ items.forEach((it, i) => {
274
+ const r = Math.floor(i / n);
275
+ const c = i % n;
276
+ const x = MX + c * (cw + gap);
277
+ const y = BODY_TOP + r * (ch + 0.25);
278
+ card(slide, x, y, cw, ch);
279
+ slide.addShape('rect', { x, y, w: 0.08, h: ch, fill: { color: gold() } });
280
+ txt(slide, it.title, { x: x + 0.3, y: y + 0.2, w: cw - 0.5, h: 0.45, fontSize: 17, color: ink(), bold: true, align: 'left', fontFace: heading() });
281
+ txt(slide, it.desc, { x: x + 0.3, y: y + 0.68, w: cw - 0.5, h: ch - 0.8, fontSize: 12.5, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
282
+ });
283
+ footer(slide, p.footnoteLeft, p.footnoteRight);
284
+ }
285
+ // =====================================================================
286
+ // 10. process
287
+ // =====================================================================
288
+ function renderTheme08Process(slide, p) {
289
+ bg(slide);
290
+ kicker(slide, p.kicker);
291
+ title(slide, p.title);
292
+ subtitle(slide, p.subtitle);
293
+ const items = (p.items || []).slice(0, 5);
294
+ const n = items.length;
295
+ const gap = 0.4;
296
+ const cw = (CW - gap * (n - 1)) / n;
297
+ items.forEach((it, i) => {
298
+ const x = MX + i * (cw + gap);
299
+ card(slide, x, BODY_TOP, cw, 2.6);
300
+ txt(slide, String(i + 1).padStart(2, '0'), { x: x + 0.2, y: BODY_TOP + 0.2, w: cw - 0.4, h: 0.6, fontSize: 28, color: gold(), align: 'left', bold: true, fontFace: mono() });
301
+ txt(slide, it.title, { x: x + 0.2, y: BODY_TOP + 0.85, w: cw - 0.4, h: 0.6, fontSize: 15, color: ink(), bold: true, align: 'left', valign: 'top', fontFace: heading() });
302
+ txt(slide, it.desc, { x: x + 0.2, y: BODY_TOP + 1.5, w: cw - 0.4, h: 1.0, fontSize: 11.5, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
303
+ if (i < n - 1) {
304
+ txt(slide, '→', { x: x + cw + 0.02, y: BODY_TOP + 1.0, w: gap, h: 0.5, fontSize: 20, color: gold(), align: 'center', valign: 'middle', bold: true });
305
+ }
306
+ });
307
+ footer(slide, p.footnoteLeft, p.footnoteRight);
308
+ }
309
+ // =====================================================================
310
+ // 11. timeline
311
+ // =====================================================================
312
+ function renderTheme08Timeline(slide, p) {
313
+ bg(slide);
314
+ kicker(slide, p.kicker);
315
+ title(slide, p.title);
316
+ subtitle(slide, p.subtitle);
317
+ const items = (p.items || []).slice(0, 5);
318
+ const n = items.length;
319
+ const lineY = BODY_TOP + 0.35;
320
+ slide.addShape('line', { x: MX, y: lineY, w: CW, h: 0, line: { color: gold(), width: 1.5 } });
321
+ const step = CW / n;
322
+ items.forEach((it, i) => {
323
+ const cx = MX + step * i + step / 2;
324
+ slide.addShape('ellipse', { x: cx - 0.09, y: lineY - 0.09, w: 0.18, h: 0.18, fill: { color: gold() } });
325
+ txt(slide, it.year, { x: cx - step / 2, y: lineY + 0.15, w: step, h: 0.35, fontSize: 15, color: gold(), align: 'center', bold: true, fontFace: mono() });
326
+ txt(slide, it.title, { x: cx - step / 2 + 0.1, y: lineY + 0.55, w: step - 0.2, h: 0.45, fontSize: 14, color: ink(), bold: true, align: 'center', fontFace: heading() });
327
+ txt(slide, it.desc, { x: cx - step / 2 + 0.1, y: lineY + 1.0, w: step - 0.2, h: 1.4, fontSize: 11, color: ink2(), align: 'center', valign: 'top', fontFace: body() });
328
+ });
329
+ footer(slide, p.footnoteLeft, p.footnoteRight);
330
+ }
331
+ // =====================================================================
332
+ // 12. roadmap
333
+ // =====================================================================
334
+ function renderTheme08Roadmap(slide, p) {
335
+ bg(slide);
336
+ kicker(slide, p.kicker);
337
+ title(slide, p.title);
338
+ subtitle(slide, p.subtitle);
339
+ const items = (p.items || []).slice(0, 4);
340
+ const n = items.length;
341
+ const gap = 0.3;
342
+ const cw = (CW - gap * (n - 1)) / n;
343
+ items.forEach((it, i) => {
344
+ const x = MX + i * (cw + gap);
345
+ card(slide, x, BODY_TOP, cw, 2.8);
346
+ txt(slide, it.period, { x: x + 0.2, y: BODY_TOP + 0.2, w: cw - 0.4, h: 0.4, fontSize: 14, color: gold(), align: 'left', bold: true, fontFace: mono() });
347
+ txt(slide, it.goal, { x: x + 0.2, y: BODY_TOP + 0.65, w: cw - 0.4, h: 0.6, fontSize: 15, color: ink(), bold: true, align: 'left', valign: 'top', fontFace: heading() });
348
+ (it.points || []).slice(0, 4).forEach((pt, j) => {
349
+ txt(slide, '· ' + pt, { x: x + 0.2, y: BODY_TOP + 1.3 + j * 0.36, w: cw - 0.4, h: 0.34, fontSize: 11, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
350
+ });
351
+ });
352
+ footer(slide, p.footnoteLeft, p.footnoteRight);
353
+ }
354
+ // =====================================================================
355
+ // 13. workflow
356
+ // =====================================================================
357
+ function renderTheme08Workflow(slide, p) {
358
+ bg(slide);
359
+ kicker(slide, p.kicker);
360
+ title(slide, p.title);
361
+ subtitle(slide, p.subtitle);
362
+ const steps = (p.steps || []).slice(0, 5);
363
+ const n = steps.length;
364
+ const gap = 0.25;
365
+ const cw = (CW - gap * (n - 1)) / n;
366
+ steps.forEach((st, i) => {
367
+ const x = MX + i * (cw + gap);
368
+ card(slide, x, BODY_TOP, cw, 2.3);
369
+ slide.addShape('ellipse', { x: x + cw / 2 - 0.3, y: BODY_TOP + 0.2, w: 0.6, h: 0.6, fill: { color: gold() } });
370
+ txt(slide, st.num, { x: x + cw / 2 - 0.3, y: BODY_TOP + 0.2, w: 0.6, h: 0.6, fontSize: 20, color: isDark() ? '14130D' : 'FFFFFF', align: 'center', valign: 'middle', bold: true, fontFace: heading() });
371
+ txt(slide, st.title, { x: x + 0.15, y: BODY_TOP + 0.95, w: cw - 0.3, h: 0.5, fontSize: 13.5, color: ink(), bold: true, align: 'center', valign: 'top', fontFace: heading() });
372
+ txt(slide, st.desc, { x: x + 0.15, y: BODY_TOP + 1.45, w: cw - 0.3, h: 0.8, fontSize: 10.5, color: ink2(), align: 'center', valign: 'top', fontFace: body() });
373
+ });
374
+ const labels = p.timelineLabels || [];
375
+ if (labels.length) {
376
+ const step = CW / labels.length;
377
+ labels.forEach((lb, i) => {
378
+ txt(slide, lb, { x: MX + step * i, y: 4.85, w: step, h: 0.3, fontSize: 10, color: ink2(), align: 'center', fontFace: mono() });
379
+ });
380
+ }
381
+ footer(slide, p.footnoteLeft, p.footnoteRight);
382
+ }
383
+ // =====================================================================
384
+ // 14. team
385
+ // =====================================================================
386
+ function renderTheme08Team(slide, p) {
387
+ bg(slide);
388
+ kicker(slide, p.kicker);
389
+ title(slide, p.title);
390
+ subtitle(slide, p.subtitle);
391
+ const items = (p.items || []).slice(0, 4);
392
+ const n = cols(items.length, 4);
393
+ const gap = 0.3;
394
+ const cw = (CW - gap * (n - 1)) / n;
395
+ const ch = 2.5;
396
+ items.forEach((m, i) => {
397
+ const x = MX + i * (cw + gap);
398
+ card(slide, x, BODY_TOP, cw, ch);
399
+ const init = m.initial || (m.name ? m.name[0] : '?');
400
+ slide.addShape('ellipse', { x: x + cw / 2 - 0.45, y: BODY_TOP + 0.25, w: 0.9, h: 0.9, fill: { color: gold() } });
401
+ txt(slide, init, { x: x + cw / 2 - 0.45, y: BODY_TOP + 0.25, w: 0.9, h: 0.9, fontSize: 30, color: isDark() ? '14130D' : 'FFFFFF', align: 'center', valign: 'middle', bold: true, fontFace: heading() });
402
+ txt(slide, m.name, { x: x + 0.1, y: BODY_TOP + 1.3, w: cw - 0.2, h: 0.4, fontSize: 15, color: ink(), bold: true, align: 'center', fontFace: heading() });
403
+ txt(slide, m.role, { x: x + 0.1, y: BODY_TOP + 1.75, w: cw - 0.2, h: 0.6, fontSize: 11.5, color: ink2(), align: 'center', valign: 'top', fontFace: body() });
404
+ });
405
+ footer(slide, p.footnoteLeft, p.footnoteRight);
406
+ }
407
+ // =====================================================================
408
+ // 15. partners
409
+ // =====================================================================
410
+ function renderTheme08Partners(slide, p) {
411
+ bg(slide);
412
+ kicker(slide, p.kicker);
413
+ title(slide, p.title);
414
+ subtitle(slide, p.subtitle);
415
+ const items = (p.items || []).slice(0, 12);
416
+ const n = cols(items.length, 4);
417
+ const rows = Math.ceil(items.length / n);
418
+ const gap = 0.3;
419
+ const cw = (CW - gap * (n - 1)) / n;
420
+ const ch = Math.min(1.0, (5.0 - BODY_TOP) / rows - 0.15);
421
+ items.forEach((it, i) => {
422
+ const r = Math.floor(i / n);
423
+ const c = i % n;
424
+ const x = MX + c * (cw + gap);
425
+ const y = BODY_TOP + r * (ch + 0.15);
426
+ card(slide, x, y, cw, ch);
427
+ txt(slide, it.name, { x: x + 0.15, y, w: cw - 0.3, h: ch, fontSize: 14, color: ink(), align: 'center', valign: 'middle', bold: true, fontFace: heading() });
428
+ });
429
+ footer(slide, p.footnoteLeft, p.footnoteRight);
430
+ }
431
+ // =====================================================================
432
+ // 16. gallery
433
+ // =====================================================================
434
+ function renderTheme08Gallery(slide, p) {
435
+ bg(slide);
436
+ kicker(slide, p.kicker);
437
+ title(slide, p.title);
438
+ subtitle(slide, p.subtitle);
439
+ const items = (p.items || []).slice(0, 4);
440
+ const n = cols(items.length, 2);
441
+ const gap = 0.3;
442
+ const cw = (CW - gap * (n - 1)) / n;
443
+ const ch = 1.55;
444
+ items.forEach((it, i) => {
445
+ const r = Math.floor(i / n);
446
+ const c = i % n;
447
+ const x = MX + c * (cw + gap);
448
+ const y = BODY_TOP + r * (ch + 0.3);
449
+ const ih = ch - 0.35;
450
+ if (it.imageUrl) {
451
+ try {
452
+ slide.addImage({ path: it.imageUrl, x, y, w: cw, h: ih });
453
+ }
454
+ catch {
455
+ slide.addShape('rect', { x, y, w: cw, h: ih, fill: { color: surfaceElev() }, line: { color: border(), width: 1 } });
456
+ }
457
+ }
458
+ else {
459
+ slide.addShape('roundRect', { x, y, w: cw, h: ih, rectRadius: 0.05, fill: { color: surfaceElev() }, line: { color: gold(), width: 1 } });
460
+ txt(slide, 'IMG', { x, y, w: cw, h: ih, fontSize: 14, color: gold(), align: 'center', valign: 'middle', fontFace: mono() });
461
+ }
462
+ txt(slide, it.caption, { x, y: y + ih + 0.05, w: cw, h: 0.3, fontSize: 12, color: ink2(), align: 'left', fontFace: body() });
463
+ });
464
+ footer(slide, p.footnoteLeft, p.footnoteRight);
465
+ }
466
+ // =====================================================================
467
+ // 17. collage
468
+ // =====================================================================
469
+ function renderTheme08Collage(slide, p) {
470
+ bg(slide);
471
+ if (p.tag)
472
+ txt(slide, p.tag, { x: MX, y: 0.45, w: CW, h: 0.35, fontSize: 13, color: gold(), align: 'left', fontFace: mono(), bold: true, charSpacing: 2 });
473
+ title(slide, p.title, MX, 0.82, CW, 28);
474
+ txt(slide, p.subtitle, { x: MX, y: 1.5, w: 5.2, h: 0.8, fontSize: 14, color: ink2(), align: 'left', fontFace: body() });
475
+ txt(slide, p.desc, { x: MX, y: 2.1, w: 5.2, h: 1.6, fontSize: 13, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
476
+ const photos = (p.photos || []).slice(0, 4);
477
+ const gx = 6.1;
478
+ const gw = 3.2;
479
+ const positions = [
480
+ { x: gx, y: BODY_TOP, w: gw, h: 1.3 },
481
+ { x: gx, y: BODY_TOP + 1.45, w: gw / 2 - 0.1, h: 1.3 },
482
+ { x: gx + gw / 2 + 0.1, y: BODY_TOP + 1.45, w: gw / 2 - 0.1, h: 1.3 },
483
+ { x: gx, y: BODY_TOP + 2.9, w: gw, h: 1.3 },
484
+ ];
485
+ photos.forEach((ph, i) => {
486
+ const pos = positions[i];
487
+ if (!pos)
488
+ return;
489
+ if (ph.imageUrl) {
490
+ try {
491
+ slide.addImage({ path: ph.imageUrl, x: pos.x, y: pos.y, w: pos.w, h: pos.h });
492
+ }
493
+ catch {
494
+ slide.addShape('rect', { x: pos.x, y: pos.y, w: pos.w, h: pos.h, fill: { color: surfaceElev() }, line: { color: border(), width: 1 } });
495
+ }
496
+ }
497
+ else {
498
+ slide.addShape('roundRect', { x: pos.x, y: pos.y, w: pos.w, h: pos.h, rectRadius: 0.05, fill: { color: surfaceElev() }, line: { color: (ph.labelColor || gold()), width: 1 } });
499
+ }
500
+ if (ph.label)
501
+ txt(slide, ph.label, { x: pos.x + 0.1, y: pos.y + pos.h - 0.35, w: pos.w - 0.2, h: 0.3, fontSize: 10, color: ph.labelColor || gold(), align: 'left', bold: true, fontFace: mono() });
502
+ });
503
+ if (p.bigNumber) {
504
+ txt(slide, p.bigNumber, { x: gx, y: 5.0, w: gw, h: 0.6, fontSize: 30, color: gold(), align: 'right', bold: true, fontFace: heading() });
505
+ if (p.bigNumberLabel)
506
+ txt(slide, p.bigNumberLabel, { x: gx, y: 5.55, w: gw, h: 0.25, fontSize: 10, color: ink2(), align: 'right', fontFace: mono() });
507
+ }
508
+ footer(slide, p.footnoteLeft, p.footnoteRight);
509
+ }
510
+ // =====================================================================
511
+ // 18. bubble
512
+ // =====================================================================
513
+ function renderTheme08Bubble(slide, p) {
514
+ bg(slide);
515
+ kicker(slide, p.kicker);
516
+ title(slide, p.title);
517
+ subtitle(slide, p.subtitle);
518
+ const groups = (p.groups || []).slice(0, 3);
519
+ const n = groups.length;
520
+ const gap = 0.3;
521
+ const cw = (CW - gap * (n - 1)) / n;
522
+ groups.forEach((g, i) => {
523
+ const x = MX + i * (cw + gap);
524
+ card(slide, x, BODY_TOP, cw, 2.7);
525
+ txt(slide, g.label, { x: x + 0.2, y: BODY_TOP + 0.2, w: cw - 0.4, h: 0.4, fontSize: 15, color: ink(), bold: true, align: 'left', fontFace: heading() });
526
+ txt(slide, [g.count, g.total].filter(Boolean).join(' / '), { x: x + 0.2, y: BODY_TOP + 0.6, w: cw - 0.4, h: 0.5, fontSize: 24, color: gold(), align: 'left', bold: true, fontFace: heading() });
527
+ (g.items || []).slice(0, 4).forEach((it, j) => {
528
+ const label = typeof it === 'string' ? it : (it.label || it.name || '');
529
+ txt(slide, '· ' + label, { x: x + 0.2, y: BODY_TOP + 1.2 + j * 0.36, w: cw - 0.4, h: 0.34, fontSize: 11, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
530
+ });
531
+ });
532
+ footer(slide, p.footnoteLeft, p.footnoteRight);
533
+ }
534
+ // =====================================================================
535
+ // 19. quote
536
+ // =====================================================================
537
+ function renderTheme08Quote(slide, p) {
538
+ bg(slide);
539
+ if (p.kicker)
540
+ txt(slide, p.kicker, { x: MX, y: 1.0, w: CW, h: 0.35, fontSize: 13, color: gold(), align: 'center', fontFace: mono(), bold: true, charSpacing: 2 });
541
+ txt(slide, '“', { x: MX, y: 1.2, w: 2, h: 1.2, fontSize: 90, color: gold(), align: 'left', fontFace: heading() });
542
+ txt(slide, p.text, { x: 1.6, y: 1.7, w: CW - 1.8, h: 2.2, fontSize: 26, color: ink(), align: 'left', valign: 'top', bold: true, fontFace: heading() });
543
+ if (p.author)
544
+ txt(slide, '— ' + p.author, { x: 1.6, y: 4.0, w: CW - 1.8, h: 0.4, fontSize: 15, color: ink2(), align: 'left', fontFace: body() });
545
+ footer(slide, p.footnoteLeft, p.footnoteRight);
546
+ }
547
+ // =====================================================================
548
+ // 20. closing
549
+ // =====================================================================
550
+ function renderTheme08Closing(slide, p) {
551
+ bg(slide);
552
+ kicker(slide, p.kicker);
553
+ txt(slide, p.title, { x: MX, y: 1.6, w: CW, h: 1.2, fontSize: 44, color: ink(), align: 'left', valign: 'middle', bold: true, fontFace: heading() });
554
+ txt(slide, p.subtitle, { x: MX, y: 2.9, w: CW, h: 0.7, fontSize: 17, color: ink2(), align: 'left', fontFace: body() });
555
+ const tags = (p.tags || []).slice(0, 5);
556
+ let tx = MX;
557
+ tags.forEach((t) => {
558
+ const w = 0.4 + t.length * 0.18;
559
+ slide.addShape('roundRect', { x: tx, y: 3.8, w, h: 0.45, rectRadius: 0.22, fill: { color: surfaceElev() }, line: { color: gold(), width: 1 } });
560
+ txt(slide, t, { x: tx, y: 3.8, w, h: 0.45, fontSize: 12, color: gold(), align: 'center', valign: 'middle', fontFace: mono() });
561
+ tx += w + 0.2;
562
+ });
563
+ if (p.contact)
564
+ txt(slide, p.contact, { x: MX, y: 4.6, w: CW, h: 0.4, fontSize: 14, color: ink(), align: 'left', fontFace: body() });
565
+ footer(slide, p.footnoteLeft, p.footnoteRight);
566
+ }
567
+ // =====================================================================
568
+ // 21. hero-split
569
+ // =====================================================================
570
+ function renderTheme08HeroSplit(slide, p) {
571
+ bg(slide);
572
+ const halfW = CW / 2 - 0.2;
573
+ slide.addShape('line', { x: 5, y: BODY_TOP - 0.2, w: 0, h: 2.9, line: { color: gold(), width: 1.5 } });
574
+ // left
575
+ if (p.leftKicker)
576
+ txt(slide, p.leftKicker, { x: MX, y: BODY_TOP, w: halfW, h: 0.35, fontSize: 13, color: gold(), align: 'left', fontFace: mono(), bold: true });
577
+ txt(slide, p.leftTitle, { x: MX, y: BODY_TOP + 0.45, w: halfW, h: 1.0, fontSize: 26, color: ink(), align: 'left', valign: 'top', bold: true, fontFace: heading() });
578
+ txt(slide, p.leftDesc, { x: MX, y: BODY_TOP + 1.5, w: halfW, h: 1.5, fontSize: 14, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
579
+ // right
580
+ const rx = 5.2;
581
+ if (p.rightKicker)
582
+ txt(slide, p.rightKicker, { x: rx, y: BODY_TOP, w: halfW, h: 0.35, fontSize: 13, color: gold(), align: 'left', fontFace: mono(), bold: true });
583
+ txt(slide, p.rightTitle, { x: rx, y: BODY_TOP + 0.45, w: halfW, h: 1.0, fontSize: 26, color: ink(), align: 'left', valign: 'top', bold: true, fontFace: heading() });
584
+ txt(slide, p.rightDesc, { x: rx, y: BODY_TOP + 1.5, w: halfW, h: 1.5, fontSize: 14, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
585
+ if (p.watermarkNumber)
586
+ txt(slide, p.watermarkNumber, { x: MX, y: 4.7, w: CW, h: 0.6, fontSize: 40, color: gold(), align: 'center', bold: true, fontFace: heading() });
587
+ footer(slide, p.footnoteLeft, p.footnoteRight);
588
+ }
589
+ // =====================================================================
590
+ // 22. ecosystem
591
+ // =====================================================================
592
+ function renderTheme08Ecosystem(slide, p) {
593
+ bg(slide);
594
+ kicker(slide, p.kicker);
595
+ title(slide, p.title, MX, 0.82, 5, 26);
596
+ // hub
597
+ const hx = 3.6, hy = 3.0, hr = 1.3;
598
+ slide.addShape('ellipse', { x: hx, y: hy - hr / 2, w: hr, h: hr, fill: { color: gold() } });
599
+ txt(slide, p.hubName, { x: hx, y: hy - hr / 2 + 0.15, w: hr, h: 0.4, fontSize: 14, color: isDark() ? '14130D' : 'FFFFFF', align: 'center', bold: true, fontFace: heading() });
600
+ txt(slide, [p.hubValue, p.hubUnit].filter(Boolean).join(' '), { x: hx, y: hy - hr / 2 + 0.5, w: hr, h: 0.5, fontSize: 20, color: isDark() ? '14130D' : 'FFFFFF', align: 'center', bold: true, fontFace: heading() });
601
+ if (p.hubSub)
602
+ txt(slide, p.hubSub, { x: hx, y: hy - hr / 2 + 0.95, w: hr, h: 0.3, fontSize: 9, color: isDark() ? '14130D' : 'FFFFFF', align: 'center', fontFace: mono() });
603
+ const nodes = (p.nodes || []).slice(0, 6);
604
+ const posMap = {
605
+ top: [4.0, 1.7], right: [7.3, 2.8], bottom: [4.0, 4.3], left: [0.9, 2.8],
606
+ };
607
+ const order = { top: 0, right: 1, bottom: 2, left: 3 };
608
+ nodes.sort((a, b) => (order[a.position] ?? 9) - (order[b.position] ?? 9));
609
+ nodes.forEach((nd) => {
610
+ const base = posMap[nd.position] || [4.0, 3.0];
611
+ const nw = 2.2, nh = 0.9;
612
+ card(slide, base[0], base[1], nw, nh);
613
+ txt(slide, nd.name, { x: base[0] + 0.15, y: base[1] + 0.1, w: nw - 0.3, h: 0.35, fontSize: 13, color: ink(), bold: true, align: 'left', fontFace: heading() });
614
+ txt(slide, [nd.value, nd.unit].filter(Boolean).join(' '), { x: base[0] + 0.15, y: base[1] + 0.45, w: nw - 0.3, h: 0.35, fontSize: 15, color: gold(), align: 'left', bold: true, fontFace: heading() });
615
+ });
616
+ footer(slide, p.footnoteLeft, p.footnoteRight);
617
+ }
618
+ // =====================================================================
619
+ // 23. case
620
+ // =====================================================================
621
+ function renderTheme08Case(slide, p) {
622
+ bg(slide);
623
+ kicker(slide, p.kicker);
624
+ title(slide, p.name, MX, 0.95, 6, 30);
625
+ if (p.logoText) {
626
+ slide.addShape('roundRect', { x: 7.2, y: 0.95, w: 1.6, h: 0.7, rectRadius: 0.1, fill: { color: gold() } });
627
+ txt(slide, p.logoText, { x: 7.2, y: 0.95, w: 1.6, h: 0.7, fontSize: 16, color: isDark() ? '14130D' : 'FFFFFF', align: 'center', valign: 'middle', bold: true, fontFace: heading() });
628
+ }
629
+ if (p.tag)
630
+ txt(slide, p.tag, { x: MX, y: 1.6, w: CW, h: 0.35, fontSize: 13, color: gold(), align: 'left', fontFace: mono(), bold: true });
631
+ txt(slide, p.desc, { x: MX, y: 2.0, w: CW, h: 1.2, fontSize: 14, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
632
+ const metrics = (p.metrics || []).slice(0, 3);
633
+ if (metrics.length) {
634
+ const mw = CW / metrics.length;
635
+ metrics.forEach((m, i) => {
636
+ const x = MX + i * mw;
637
+ txt(slide, [m.num].filter(Boolean).join(''), { x, y: 3.6, w: mw, h: 0.6, fontSize: 28, color: gold(), align: 'left', bold: true, fontFace: heading() });
638
+ txt(slide, m.label, { x, y: 4.2, w: mw, h: 0.4, fontSize: 12, color: ink2(), align: 'left', fontFace: body() });
639
+ });
640
+ }
641
+ footer(slide, p.footnoteLeft, p.footnoteRight);
642
+ }
643
+ // =====================================================================
644
+ // 24. funding
645
+ // =====================================================================
646
+ function renderTheme08Funding(slide, p) {
647
+ bg(slide);
648
+ kicker(slide, p.kicker);
649
+ title(slide, p.title);
650
+ subtitle(slide, p.subtitle);
651
+ const rows = (p.rows || []).slice(0, 5);
652
+ let y = BODY_TOP;
653
+ const rowH = (5.0 - BODY_TOP) / Math.max(rows.length, 1);
654
+ rows.forEach((r) => {
655
+ card(slide, MX, y, CW, rowH - 0.12, r.alt ? surfaceElev() : undefined);
656
+ txt(slide, r.name, { x: MX + 0.2, y: y + 0.05, w: 2.4, h: rowH - 0.2, fontSize: 15, color: ink(), bold: true, align: 'left', valign: 'middle', fontFace: heading() });
657
+ txt(slide, r.nameSub, { x: MX + 0.2, y: y + rowH / 2, w: 2.4, h: rowH / 2, fontSize: 10, color: ink2(), align: 'left', fontFace: body() });
658
+ // dots
659
+ const dotN = Math.min(r.dotCount || 0, 10);
660
+ let dx = MX + 2.7;
661
+ for (let k = 0; k < dotN; k++) {
662
+ slide.addShape('ellipse', { x: dx, y: y + rowH / 2 - 0.05, w: 0.1, h: 0.1, fill: { color: gold() } });
663
+ dx += 0.16;
664
+ }
665
+ txt(slide, r.count, { x: MX + 4.6, y, w: 1.2, h: rowH - 0.12, fontSize: 18, color: gold(), align: 'left', valign: 'middle', bold: true, fontFace: heading() });
666
+ txt(slide, '均值 ' + (r.avg || ''), { x: MX + 5.8, y, w: 1.6, h: rowH - 0.12, fontSize: 11, color: ink2(), align: 'left', valign: 'middle', fontFace: body() });
667
+ txt(slide, '信号 ' + (r.signalCount || 0), { x: MX + 7.4, y, w: 1.4, h: rowH - 0.12, fontSize: 11, color: ink2(), align: 'right', valign: 'middle', fontFace: body() });
668
+ y += rowH;
669
+ });
670
+ if (p.summaryPct) {
671
+ txt(slide, [p.summaryLabel, p.summaryPct].filter(Boolean).join(' '), { x: MX, y: 5.05, w: CW, h: 0.3, fontSize: 12, color: gold(), align: 'left', bold: true, fontFace: heading() });
672
+ }
673
+ footer(slide, p.footnoteLeft, p.footnoteRight);
674
+ }
675
+ // =====================================================================
676
+ // 25. matrix(2x2)
677
+ // =====================================================================
678
+ function renderTheme08Matrix(slide, p) {
679
+ bg(slide);
680
+ kicker(slide, p.kicker);
681
+ title(slide, p.title);
682
+ subtitle(slide, p.subtitle);
683
+ const cells = (p.cells || []).slice(0, 4);
684
+ const gap = 0.25;
685
+ const cw = (CW - gap) / 2;
686
+ const ch = 1.45;
687
+ const positions = [
688
+ { x: MX, y: BODY_TOP },
689
+ { x: MX + cw + gap, y: BODY_TOP },
690
+ { x: MX, y: BODY_TOP + ch + gap },
691
+ { x: MX + cw + gap, y: BODY_TOP + ch + gap },
692
+ ];
693
+ cells.forEach((c, i) => {
694
+ const pos = positions[i];
695
+ const fill = c.dark ? gold() : surfaceElev();
696
+ const tcol = c.dark ? (isDark() ? '14130D' : 'FFFFFF') : ink();
697
+ card(slide, pos.x, pos.y, cw, ch, fill);
698
+ txt(slide, c.title, { x: pos.x + 0.2, y: pos.y + 0.15, w: cw - 0.4, h: 0.4, fontSize: 15, color: tcol, bold: true, align: 'left', fontFace: heading() });
699
+ txt(slide, c.desc, { x: pos.x + 0.2, y: pos.y + 0.55, w: cw - 0.4, h: ch - 0.7, fontSize: 11, color: tcol, align: 'left', valign: 'top', fontFace: body() });
700
+ if (c.cornerBadge)
701
+ txt(slide, c.cornerBadge, { x: pos.x + cw - 1.0, y: pos.y + 0.1, w: 0.9, h: 0.3, fontSize: 10, color: tcol, align: 'right', bold: true, fontFace: mono() });
702
+ });
703
+ if (p.xAxisLabel || p.yAxisLabel) {
704
+ txt(slide, [p.xAxisLabel, p.yAxisLabel].filter(Boolean).join(' · '), { x: MX, y: 5.05, w: CW, h: 0.3, fontSize: 10, color: ink2(), align: 'left', fontFace: mono() });
705
+ }
706
+ footer(slide, p.footnoteLeft, p.footnoteRight);
707
+ }
708
+ // =====================================================================
709
+ // 26. quadrant(2x2)
710
+ // =====================================================================
711
+ function renderTheme08Quadrant(slide, p) {
712
+ bg(slide);
713
+ kicker(slide, p.kicker);
714
+ title(slide, p.title);
715
+ subtitle(slide, p.subtitle);
716
+ const items = (p.items || []).slice(0, 4);
717
+ const gap = 0.25;
718
+ const cw = (CW - gap) / 2;
719
+ const ch = 1.45;
720
+ const positions = [
721
+ { x: MX, y: BODY_TOP },
722
+ { x: MX + cw + gap, y: BODY_TOP },
723
+ { x: MX, y: BODY_TOP + ch + gap },
724
+ { x: MX + cw + gap, y: BODY_TOP + ch + gap },
725
+ ];
726
+ items.forEach((it, i) => {
727
+ const pos = positions[i];
728
+ card(slide, pos.x, pos.y, cw, ch);
729
+ if (it.label)
730
+ txt(slide, it.label, { x: pos.x + 0.2, y: pos.y + 0.12, w: cw - 0.4, h: 0.3, fontSize: 11, color: gold(), align: 'left', bold: true, fontFace: mono() });
731
+ txt(slide, it.title, { x: pos.x + 0.2, y: pos.y + 0.42, w: cw - 0.4, h: 0.4, fontSize: 15, color: ink(), bold: true, align: 'left', fontFace: heading() });
732
+ txt(slide, it.desc, { x: pos.x + 0.2, y: pos.y + 0.82, w: cw - 0.4, h: ch - 0.95, fontSize: 11, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
733
+ });
734
+ footer(slide, p.footnoteLeft, p.footnoteRight);
735
+ }
736
+ // =====================================================================
737
+ // 27. scorecard
738
+ // =====================================================================
739
+ function renderTheme08Scorecard(slide, p) {
740
+ bg(slide);
741
+ kicker(slide, p.kicker);
742
+ title(slide, p.title);
743
+ subtitle(slide, p.subtitle);
744
+ const cards = (p.cards || []).slice(0, 4);
745
+ const n = cards.length;
746
+ const gap = 0.3;
747
+ const cw = (CW - gap * (n - 1)) / n;
748
+ cards.forEach((c, i) => {
749
+ const x = MX + i * (cw + gap);
750
+ card(slide, x, BODY_TOP, cw, 2.7, c.highlight ? gold() : undefined);
751
+ const tcol = c.highlight ? (isDark() ? '14130D' : 'FFFFFF') : ink();
752
+ txt(slide, c.label, { x: x + 0.2, y: BODY_TOP + 0.2, w: cw - 0.4, h: 0.35, fontSize: 13, color: tcol, align: 'left', fontFace: body() });
753
+ txt(slide, c.value, { x: x + 0.2, y: BODY_TOP + 0.55, w: cw - 0.4, h: 0.7, fontSize: 30, color: tcol, align: 'left', bold: true, fontFace: heading() });
754
+ if (c.change) {
755
+ const up = c.changeDir !== 'down';
756
+ slide.addShape('roundRect', { x: x + 0.2, y: BODY_TOP + 1.35, w: 1.0, h: 0.35, rectRadius: 0.17, fill: { color: up ? (isDark() ? '1A1812' : 'E8F5E9') : (isDark() ? '2C1414' : 'FDECEA') } });
757
+ txt(slide, (up ? '▲ ' : '▼ ') + c.change, { x: x + 0.2, y: BODY_TOP + 1.35, w: 1.0, h: 0.35, fontSize: 11, color: up ? (isDark() ? '3DDC84' : '1B7A3D') : (isDark() ? 'FF6B6B' : 'C0392B'), align: 'center', valign: 'middle', bold: true, fontFace: mono() });
758
+ }
759
+ if (c.desc)
760
+ txt(slide, c.desc, { x: x + 0.2, y: BODY_TOP + 1.85, w: cw - 0.4, h: 0.8, fontSize: 11, color: tcol, align: 'left', valign: 'top', fontFace: body() });
761
+ });
762
+ footer(slide, p.footnoteLeft, p.footnoteRight);
763
+ }
764
+ // =====================================================================
765
+ // 28. region
766
+ // =====================================================================
767
+ function renderTheme08Region(slide, p) {
768
+ bg(slide);
769
+ kicker(slide, p.kicker);
770
+ title(slide, p.title);
771
+ subtitle(slide, p.subtitle);
772
+ const regions = (p.regions || []).slice(0, 7);
773
+ let y = BODY_TOP;
774
+ const rowH = (5.0 - BODY_TOP) / Math.max(regions.length, 1);
775
+ // find max for bar scaling
776
+ const maxV = Math.max(...regions.map((r) => parseFloat(r.value) || 0), 1);
777
+ regions.forEach((r) => {
778
+ txt(slide, r.city, { x: MX, y, w: 2.2, h: rowH - 0.1, fontSize: 14, color: ink(), bold: true, align: 'left', valign: 'middle', fontFace: heading() });
779
+ const v = parseFloat(r.value) || 0;
780
+ const bw = (CW - 2.4) * (v / maxV);
781
+ slide.addShape('roundRect', { x: MX + 2.3, y: y + (rowH - 0.1) / 2 - 0.12, w: Math.max(bw, 0.1), h: 0.24, rectRadius: 0.12, fill: { color: gold() } });
782
+ txt(slide, r.value, { x: MX + 2.3 + Math.max(bw, 0.1) + 0.1, y, w: 1.5, h: rowH - 0.1, fontSize: 13, color: ink2(), align: 'left', valign: 'middle', fontFace: mono() });
783
+ y += rowH;
784
+ });
785
+ footer(slide, p.footnoteLeft, p.footnoteRight);
786
+ }
787
+ // =====================================================================
788
+ // 29. range
789
+ // =====================================================================
790
+ function renderTheme08Range(slide, p) {
791
+ bg(slide);
792
+ kicker(slide, p.kicker);
793
+ title(slide, p.title);
794
+ subtitle(slide, p.subtitle);
795
+ const rows = (p.rows || []).slice(0, 5);
796
+ let y = BODY_TOP;
797
+ const rowH = (5.0 - BODY_TOP) / Math.max(rows.length, 1);
798
+ const maxHigh = Math.max(...rows.map((r) => r.high || 0), 1);
799
+ rows.forEach((r) => {
800
+ txt(slide, r.name, { x: MX, y, w: 2.0, h: rowH - 0.1, fontSize: 13, color: ink(), bold: true, align: 'left', valign: 'middle', fontFace: heading() });
801
+ txt(slide, r.nameSub, { x: MX, y: y + rowH / 2, w: 2.0, h: rowH / 2, fontSize: 9, color: ink2(), align: 'left', fontFace: body() });
802
+ const trackX = MX + 2.2;
803
+ const trackW = CW - 2.2 - 1.6;
804
+ slide.addShape('roundRect', { x: trackX, y: y + (rowH - 0.1) / 2 - 0.09, w: trackW, h: 0.18, rectRadius: 0.09, fill: { color: surfaceElev() } });
805
+ const lowX = trackX + trackW * (r.low / maxHigh);
806
+ const highX = trackX + trackW * (r.high / maxHigh);
807
+ slide.addShape('roundRect', { x: lowX, y: y + (rowH - 0.1) / 2 - 0.11, w: Math.max(highX - lowX, 0.1), h: 0.22, rectRadius: 0.11, fill: { color: r.highlight ? gold() : (isDark() ? 'B8AE98' : '524C3D') } });
808
+ txt(slide, [r.lowVal, r.highVal].filter(Boolean).join(' – '), { x: MX + CW - 1.5, y, w: 1.5, h: rowH - 0.1, fontSize: 11, color: ink2(), align: 'right', valign: 'middle', fontFace: mono() });
809
+ y += rowH;
810
+ });
811
+ if (p.midLabel)
812
+ txt(slide, p.midLabel, { x: MX, y: 5.05, w: CW, h: 0.3, fontSize: 10, color: ink2(), align: 'left', fontFace: mono() });
813
+ footer(slide, p.footnoteLeft, p.footnoteRight);
814
+ }
815
+ // =====================================================================
816
+ // 30. heatmap
817
+ // =====================================================================
818
+ function renderTheme08Heatmap(slide, p) {
819
+ bg(slide);
820
+ kicker(slide, p.kicker);
821
+ title(slide, p.title);
822
+ subtitle(slide, p.subtitle);
823
+ const cells = (p.cells || []).slice(0, 12);
824
+ const n = cols(cells.length, 6);
825
+ const rowsN = Math.ceil(cells.length / n);
826
+ const gap = 0.15;
827
+ const cw = (CW - gap * (n - 1)) / n;
828
+ const ch = Math.min(0.95, (5.0 - BODY_TOP) / rowsN - gap);
829
+ cells.forEach((c, i) => {
830
+ const r = Math.floor(i / n);
831
+ const col = i % n;
832
+ const x = MX + col * (cw + gap);
833
+ const y = BODY_TOP + r * (ch + gap);
834
+ slide.addShape('roundRect', { x, y, w: cw, h: ch, rectRadius: 0.05, fill: { color: c.color || gold() } });
835
+ txt(slide, c.month, { x: x + 0.05, y: y + 0.02, w: cw - 0.1, h: 0.3, fontSize: 9, color: isDark() ? '14130D' : '1A1812', align: 'left', bold: true, fontFace: mono() });
836
+ txt(slide, c.value + (c.unit || ''), { x: x + 0.05, y: y + ch - 0.4, w: cw - 0.1, h: 0.35, fontSize: 12, color: isDark() ? '14130D' : '1A1812', align: 'left', bold: true, fontFace: heading() });
837
+ });
838
+ if (p.scaleMin || p.scaleMax) {
839
+ txt(slide, [p.scaleMin, p.scaleMax, p.scaleUnit].filter(Boolean).join(' '), { x: MX, y: 5.05, w: CW, h: 0.3, fontSize: 10, color: ink2(), align: 'left', fontFace: mono() });
840
+ }
841
+ footer(slide, p.footnoteLeft, p.footnoteRight);
842
+ }
843
+ // =====================================================================
844
+ // 31. waterfall
845
+ // =====================================================================
846
+ function renderTheme08Waterfall(slide, p) {
847
+ bg(slide);
848
+ kicker(slide, p.kicker);
849
+ title(slide, p.title);
850
+ subtitle(slide, p.subtitle);
851
+ const bars = (p.bars || []).slice(0, 7);
852
+ const n = bars.length;
853
+ const gap = 0.25;
854
+ const cw = (CW - gap * (n - 1)) / n;
855
+ const baseY = 4.6;
856
+ const maxV = Math.max(...bars.map((b) => Math.abs(b.value)), 1);
857
+ const scale = (baseY - BODY_TOP - 0.3) / maxV;
858
+ let cum = 0;
859
+ bars.forEach((b, i) => {
860
+ const x = MX + i * (cw + gap);
861
+ const startY = baseY - cum * scale;
862
+ const h = Math.abs(b.value) * scale;
863
+ const top = b.value >= 0 ? startY - h : startY;
864
+ slide.addShape('roundRect', { x, y: top, w: cw, h: Math.max(h, 0.05), rectRadius: 0.03, fill: { color: b.color || (b.value >= 0 ? gold() : (isDark() ? 'FF6B6B' : 'C0392B')) } });
865
+ txt(slide, b.value + (b.pct || ''), { x, y: top - 0.35, w: cw, h: 0.3, fontSize: 12, color: ink(), align: 'center', bold: true, fontFace: heading() });
866
+ txt(slide, b.label, { x, y: baseY + 0.05, w: cw, h: 0.3, fontSize: 10, color: ink2(), align: 'center', fontFace: body() });
867
+ if (b.sub)
868
+ txt(slide, b.sub, { x, y: baseY + 0.33, w: cw, h: 0.25, fontSize: 8.5, color: ink2(), align: 'center', fontFace: mono() });
869
+ cum += b.value;
870
+ });
871
+ if (p.totalValue) {
872
+ txt(slide, [p.totalLabel, p.totalValue].filter(Boolean).join(':'), { x: MX, y: 5.05, w: CW, h: 0.3, fontSize: 12, color: gold(), align: 'left', bold: true, fontFace: heading() });
873
+ }
874
+ footer(slide, p.footnoteLeft, p.footnoteRight);
875
+ }
876
+ // =====================================================================
877
+ // 32. chart-bar
878
+ // =====================================================================
879
+ function renderTheme08ChartBar(slide, p) {
880
+ bg(slide);
881
+ kicker(slide, p.kicker);
882
+ title(slide, p.title);
883
+ subtitle(slide, p.subtitle);
884
+ const bars = (p.bars || []).slice(0, 8);
885
+ if (bars.length) {
886
+ slide.addChart('bar', [{ name: p.title || '数据', labels: bars.map((b) => b.label), values: bars.map((b) => parseFloat(b.value) || 0) }], {
887
+ x: MX, y: BODY_TOP, w: CW, h: 2.9,
888
+ chartColors: [gold()],
889
+ barDir: 'col',
890
+ showValue: true,
891
+ dataLabelColor: ink(),
892
+ dataLabelFontSize: 10,
893
+ catAxisLabelColor: ink2(),
894
+ valAxisLabelColor: ink2(),
895
+ catAxisLabelFontFace: body(),
896
+ valAxisLabelFontFace: body(),
897
+ });
898
+ }
899
+ footer(slide, p.footnoteLeft, p.footnoteRight);
900
+ }
901
+ // =====================================================================
902
+ // 33. chart-donut
903
+ // =====================================================================
904
+ function renderTheme08ChartDonut(slide, p) {
905
+ bg(slide);
906
+ kicker(slide, p.kicker);
907
+ title(slide, p.title);
908
+ subtitle(slide, p.subtitle);
909
+ const segs = (p.segments || []).slice(0, 8);
910
+ if (segs.length) {
911
+ slide.addChart('doughnut', [{ name: p.title || '占比', labels: segs.map((s) => s.name), values: segs.map((s) => parseFloat(s.value) || 0) }], {
912
+ x: 0.8, y: BODY_TOP, w: 5.2, h: 2.9,
913
+ chartColors: S.chartColors,
914
+ holeSize: 60,
915
+ showValue: false,
916
+ showLegend: true,
917
+ legendColor: ink2(),
918
+ legendFontFace: body(),
919
+ legendFontSize: 10,
920
+ });
921
+ if (p.centerNum) {
922
+ txt(slide, p.centerNum, { x: 2.5, y: BODY_TOP + 1.0, w: 1.6, h: 0.6, fontSize: 24, color: gold(), align: 'center', valign: 'middle', bold: true, fontFace: heading() });
923
+ if (p.centerLabel)
924
+ txt(slide, p.centerLabel, { x: 2.5, y: BODY_TOP + 1.55, w: 1.6, h: 0.3, fontSize: 10, color: ink2(), align: 'center', fontFace: body() });
925
+ }
926
+ // legend values on right
927
+ let ly = BODY_TOP + 0.2;
928
+ segs.forEach((s) => {
929
+ txt(slide, [s.name, s.value].filter(Boolean).join(' '), { x: 6.4, y: ly, w: 3, h: 0.35, fontSize: 12, color: ink(), align: 'left', fontFace: body() });
930
+ ly += 0.45;
931
+ });
932
+ }
933
+ footer(slide, p.footnoteLeft, p.footnoteRight);
934
+ }
935
+ // =====================================================================
936
+ // 34. gauge
937
+ // =====================================================================
938
+ function renderTheme08Gauge(slide, p) {
939
+ bg(slide);
940
+ kicker(slide, p.kicker);
941
+ title(slide, p.title);
942
+ if (p.year)
943
+ txt(slide, p.year, { x: MX, y: 1.5, w: CW, h: 0.35, fontSize: 13, color: gold(), align: 'left', fontFace: mono(), bold: true });
944
+ // big value
945
+ txt(slide, [p.value, p.unit].filter(Boolean).join(''), { x: MX, y: 2.0, w: 4.5, h: 1.4, fontSize: 80, color: gold(), align: 'left', valign: 'middle', bold: true, fontFace: heading() });
946
+ if (p.desc)
947
+ txt(slide, p.desc, { x: MX, y: 3.6, w: 4.5, h: 1.0, fontSize: 14, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
948
+ // progress bar (value as percentage of 100)
949
+ const pct = Math.max(0, Math.min(100, parseFloat(p.value) || 0));
950
+ slide.addShape('roundRect', { x: 5.4, y: 2.4, w: 3.8, h: 0.3, rectRadius: 0.15, fill: { color: surfaceElev() } });
951
+ slide.addShape('roundRect', { x: 5.4, y: 2.4, w: 3.8 * (pct / 100), h: 0.3, rectRadius: 0.15, fill: { color: gold() } });
952
+ txt(slide, pct.toFixed(0) + '%', { x: 5.4, y: 2.75, w: 3.8, h: 0.3, fontSize: 12, color: ink2(), align: 'left', fontFace: mono() });
953
+ const breakdown = (p.breakdown || []).slice(0, 5);
954
+ let by = 3.3;
955
+ breakdown.forEach((b) => {
956
+ txt(slide, [b.label, b.value].filter(Boolean).join(' '), { x: 5.4, y: by, w: 3.8, h: 0.35, fontSize: 12, color: ink(), align: 'left', fontFace: body() });
957
+ by += 0.42;
958
+ });
959
+ footer(slide, p.footnoteLeft, p.footnoteRight);
960
+ }
961
+ // =====================================================================
962
+ // 35. radar
963
+ // =====================================================================
964
+ function renderTheme08Radar(slide, p) {
965
+ bg(slide);
966
+ kicker(slide, p.kicker);
967
+ title(slide, p.title);
968
+ subtitle(slide, p.subtitle);
969
+ const axes = (p.axes || []).slice(0, 8);
970
+ if (axes.length) {
971
+ slide.addChart('radar', [{ name: p.title || '能力', labels: axes.map((a) => a.label), values: axes.map((a) => a.value || 0) }], {
972
+ x: MX, y: BODY_TOP, w: 5.4, h: 2.9,
973
+ chartColors: [gold()],
974
+ showValue: false,
975
+ catAxisLabelColor: ink2(),
976
+ catAxisLabelFontFace: body(),
977
+ catAxisLabelFontSize: 10,
978
+ });
979
+ }
980
+ const metrics = (p.metrics || []).slice(0, 6);
981
+ let my = BODY_TOP + 0.1;
982
+ metrics.forEach((m) => {
983
+ txt(slide, m.label, { x: 6.4, y: my, w: 3, h: 0.3, fontSize: 12, color: ink(), bold: true, align: 'left', fontFace: heading() });
984
+ txt(slide, String(m.value), { x: 6.4, y: my + 0.3, w: 1.5, h: 0.3, fontSize: 14, color: gold(), align: 'left', bold: true, fontFace: heading() });
985
+ if (m.delta)
986
+ txt(slide, (m.delta > 0 ? '▲' : '▼') + ' ' + m.delta, { x: 8.0, y: my + 0.3, w: 1.4, h: 0.3, fontSize: 11, color: m.delta > 0 ? (isDark() ? '3DDC84' : '1B7A3D') : (isDark() ? 'FF6B6B' : 'C0392B'), align: 'left', fontFace: mono() });
987
+ my += 0.45;
988
+ });
989
+ footer(slide, p.footnoteLeft, p.footnoteRight);
990
+ }
991
+ // =====================================================================
992
+ // 36. ranking
993
+ // =====================================================================
994
+ function renderTheme08Ranking(slide, p) {
995
+ bg(slide);
996
+ kicker(slide, p.kicker);
997
+ title(slide, p.title);
998
+ subtitle(slide, p.subtitle);
999
+ const items = (p.items || []).slice(0, 6);
1000
+ const maxV = Math.max(...items.map((it) => parseFloat(it.value) || 0), 1);
1001
+ let y = BODY_TOP;
1002
+ const rowH = (5.0 - BODY_TOP) / Math.max(items.length, 1);
1003
+ items.forEach((it, i) => {
1004
+ const rank = String(i + 1).padStart(2, '0');
1005
+ txt(slide, rank, { x: MX, y, w: 0.7, h: rowH - 0.1, fontSize: 18, color: it.accent ? gold() : ink2(), align: 'left', valign: 'middle', bold: true, fontFace: mono() });
1006
+ txt(slide, it.name, { x: MX + 0.8, y, w: 3.2, h: rowH - 0.1, fontSize: 14, color: ink(), bold: true, align: 'left', valign: 'middle', fontFace: heading() });
1007
+ const trackX = MX + 4.1;
1008
+ const trackW = CW - 4.1 - 1.3;
1009
+ const v = parseFloat(it.value) || 0;
1010
+ slide.addShape('roundRect', { x: trackX, y: y + (rowH - 0.1) / 2 - 0.1, w: trackW, h: 0.2, rectRadius: 0.1, fill: { color: surfaceElev() } });
1011
+ slide.addShape('roundRect', { x: trackX, y: y + (rowH - 0.1) / 2 - 0.1, w: Math.max(trackW * (v / maxV), 0.1), h: 0.2, rectRadius: 0.1, fill: { color: it.accent ? gold() : (isDark() ? 'B8AE98' : '524C3D') } });
1012
+ txt(slide, (p.unit ? it.value + ' ' + p.unit : it.value), { x: MX + CW - 1.2, y, w: 1.2, h: rowH - 0.1, fontSize: 13, color: ink2(), align: 'right', valign: 'middle', fontFace: mono() });
1013
+ y += rowH;
1014
+ });
1015
+ footer(slide, p.footnoteLeft, p.footnoteRight);
1016
+ }
1017
+ // =====================================================================
1018
+ // 37. compare
1019
+ // =====================================================================
1020
+ function renderTheme08Compare(slide, p) {
1021
+ bg(slide);
1022
+ kicker(slide, p.kicker);
1023
+ title(slide, p.title);
1024
+ subtitle(slide, p.subtitle);
1025
+ const items = (p.items || []).slice(0, 6);
1026
+ const left = items.filter((it) => it.side !== 'right').slice(0, 3);
1027
+ const right = items.filter((it) => it.side === 'right').slice(0, 3);
1028
+ const halfW = CW / 2 - 0.2;
1029
+ slide.addShape('line', { x: 5, y: BODY_TOP - 0.1, w: 0, h: 2.9, line: { color: border(), width: 1 } });
1030
+ const drawCol = (arr, x) => {
1031
+ let y = BODY_TOP;
1032
+ const rh = 2.9 / Math.max(arr.length, 1);
1033
+ arr.forEach((it) => {
1034
+ txt(slide, it.title, { x, y, w: halfW - 0.2, h: 0.4, fontSize: 15, color: gold(), bold: true, align: 'left', fontFace: heading() });
1035
+ txt(slide, it.desc, { x, y: y + 0.4, w: halfW - 0.2, h: rh - 0.45, fontSize: 12, color: ink2(), align: 'left', valign: 'top', fontFace: body() });
1036
+ y += rh;
1037
+ });
1038
+ };
1039
+ drawCol(left, MX);
1040
+ drawCol(right, 5.2);
1041
+ footer(slide, p.footnoteLeft, p.footnoteRight);
1042
+ }
1043
+ // =====================================================================
1044
+ // 38. table
1045
+ // =====================================================================
1046
+ function renderTheme08Table(slide, p) {
1047
+ bg(slide);
1048
+ kicker(slide, p.kicker);
1049
+ title(slide, p.title);
1050
+ subtitle(slide, p.subtitle);
1051
+ const headers = (p.headers || []).slice(0, 4);
1052
+ const rows = (p.rows || []).slice(0, 8);
1053
+ const colsN = Math.max(headers.length, 1);
1054
+ const gap = 0.1;
1055
+ const cw = (CW - gap * (colsN - 1)) / colsN;
1056
+ const headY = BODY_TOP;
1057
+ // header
1058
+ headers.forEach((h, i) => {
1059
+ const x = MX + i * (cw + gap);
1060
+ slide.addShape('rect', { x, y: headY, w: cw, h: 0.45, fill: { color: gold() } });
1061
+ txt(slide, h, { x: x + 0.15, y: headY, w: cw - 0.3, h: 0.45, fontSize: 13, color: isDark() ? '14130D' : 'FFFFFF', align: 'left', valign: 'middle', bold: true, fontFace: heading() });
1062
+ });
1063
+ const rowH = (5.0 - BODY_TOP - 0.55) / Math.max(rows.length, 1);
1064
+ rows.forEach((r, ri) => {
1065
+ const y = headY + 0.55 + ri * rowH;
1066
+ if (ri % 2 === 1)
1067
+ slide.addShape('rect', { x: MX, y, w: CW, h: rowH, fill: { color: surfaceElev() } });
1068
+ const cellsArr = [r.c1, r.c2, r.c3, r.c4].slice(0, colsN);
1069
+ cellsArr.forEach((c, i) => {
1070
+ const x = MX + i * (cw + gap);
1071
+ txt(slide, c, { x: x + 0.15, y, w: cw - 0.3, h: rowH, fontSize: 12, color: ink(), align: 'left', valign: 'middle', fontFace: body() });
1072
+ });
1073
+ });
1074
+ footer(slide, p.footnoteLeft, p.footnoteRight);
1075
+ }
1076
+ // =====================================================================
1077
+ // 39. chain
1078
+ // =====================================================================
1079
+ function renderTheme08Chain(slide, p) {
1080
+ bg(slide);
1081
+ kicker(slide, p.kicker);
1082
+ title(slide, p.title);
1083
+ subtitle(slide, p.subtitle);
1084
+ const layers = (p.layers || []).slice(0, 5);
1085
+ const n = layers.length;
1086
+ const gap = 0.3;
1087
+ const cw = (CW - gap * (n - 1)) / n;
1088
+ layers.forEach((l, i) => {
1089
+ const x = MX + i * (cw + gap);
1090
+ const fill = l.dark ? gold() : surfaceElev();
1091
+ const tcol = l.dark ? (isDark() ? '14130D' : 'FFFFFF') : ink();
1092
+ card(slide, x, BODY_TOP, cw, 2.6, fill);
1093
+ txt(slide, l.title, { x: x + 0.2, y: BODY_TOP + 0.25, w: cw - 0.4, h: 0.7, fontSize: 17, color: tcol, bold: true, align: 'left', valign: 'top', fontFace: heading() });
1094
+ txt(slide, l.subtitle, { x: x + 0.2, y: BODY_TOP + 1.0, w: cw - 0.4, h: 1.4, fontSize: 12, color: tcol, align: 'left', valign: 'top', fontFace: body() });
1095
+ if (i < n - 1) {
1096
+ txt(slide, '→', { x: x + cw + 0.02, y: BODY_TOP + 1.0, w: gap, h: 0.5, fontSize: 18, color: gold(), align: 'center', valign: 'middle', bold: true });
1097
+ }
1098
+ });
1099
+ footer(slide, p.footnoteLeft, p.footnoteRight);
1100
+ }
1101
+ // =====================================================================
1102
+ // v2 变体版式家族渲染器(cover/chapter/quote/cards/bars/line/dumbbell/panels/statwall/arch/capital)
1103
+ // =====================================================================
1104
+ function foot(p) {
1105
+ return [p.footnoteLeft || p.footerLeft, p.footnoteRight || p.footerRight];
1106
+ }
1107
+ function gridGeom(n, perRow, top = BODY_TOP, bottom = 5.0) {
1108
+ const rows = Math.max(1, Math.ceil(n / perRow));
1109
+ const gap = 0.18;
1110
+ const cw = (CW - gap * (perRow - 1)) / perRow;
1111
+ const ch = (bottom - top - gap * (rows - 1)) / rows;
1112
+ return { rows, perRow, gap, cw, ch, top, bottom };
1113
+ }
1114
+ function renderT08Cover2(slide, p) {
1115
+ bg(slide);
1116
+ if (p.tag)
1117
+ kicker(slide, p.tag);
1118
+ title(slide, p.title, MX, 1.5, CW, 40);
1119
+ subtitle(slide, p.subtitle);
1120
+ const stats = (p.stats || []).slice(0, 4);
1121
+ if (stats.length) {
1122
+ const mw = CW / stats.length;
1123
+ stats.forEach((m, i) => {
1124
+ const x = MX + i * mw;
1125
+ txt(slide, [m.value, m.unit].filter(Boolean).join(' '), { x, y: 3.7, w: mw, h: 0.5, fontSize: 24, color: m.accent ? gold() : ink(), bold: true, align: 'left', fontFace: heading() });
1126
+ txt(slide, m.label, { x, y: 4.22, w: mw, h: 0.35, fontSize: 12, color: ink2(), align: 'left' });
1127
+ });
1128
+ }
1129
+ footer(slide, ...foot(p));
1130
+ }
1131
+ function renderT08Chapter2(slide, p) {
1132
+ bg(slide);
1133
+ if (p.chapterNo)
1134
+ txt(slide, p.chapterNo, { x: MX, y: 1.3, w: CW, h: 1.7, fontSize: 100, color: gold(), bold: true, align: 'left', fontFace: heading() });
1135
+ title(slide, p.title, MX, 3.1, CW, 36);
1136
+ subtitle(slide, p.subtitle);
1137
+ footer(slide, ...foot(p));
1138
+ }
1139
+ function renderT08Quote2(slide, p) {
1140
+ bg(slide);
1141
+ kicker(slide, p.kicker);
1142
+ txt(slide, p.quote, { x: MX, y: 1.5, w: CW, h: 2.4, fontSize: 28, color: ink(), bold: true, align: 'left', valign: 'top', fontFace: heading() });
1143
+ if (p.fieldA)
1144
+ txt(slide, p.fieldA, { x: MX, y: 3.9, w: CW, h: 0.6, fontSize: 16, color: ink2(), valign: 'top' });
1145
+ if (p.cite)
1146
+ txt(slide, p.cite, { x: MX, y: 4.7, w: CW, h: 0.4, fontSize: 13, color: ink2(), italic: true });
1147
+ footer(slide, ...foot(p));
1148
+ }
1149
+ function renderT08GridCards(slide, p, opts) {
1150
+ bg(slide);
1151
+ kicker(slide, p.kicker);
1152
+ title(slide, p.title);
1153
+ subtitle(slide, p.subtitle);
1154
+ const items = (p.items || []).slice(0, opts.list ? 6 : 12);
1155
+ const perRow = opts.list ? 1 : Math.min(opts.cols, items.length || 1);
1156
+ const g = gridGeom(items.length || 1, perRow);
1157
+ items.forEach((it, i) => {
1158
+ const r = Math.floor(i / perRow), c = i % perRow;
1159
+ const x = MX + c * (g.cw + g.gap);
1160
+ const y = g.top + r * (g.ch + g.gap);
1161
+ card(slide, x, y, g.cw, g.ch);
1162
+ let yy = y + 0.18;
1163
+ if (it.num != null && it.num !== '') {
1164
+ txt(slide, [it.num, it.unit].filter(Boolean).join(' '), { x: x + 0.2, y: yy, w: g.cw - 0.4, h: 0.4, fontSize: 22, color: gold(), bold: true, fontFace: heading() });
1165
+ yy += 0.5;
1166
+ }
1167
+ if (it.title) {
1168
+ txt(slide, it.title, { x: x + 0.2, y: yy, w: g.cw - 0.4, h: 0.4, fontSize: 15, color: ink(), bold: true, fontFace: heading() });
1169
+ yy += 0.45;
1170
+ }
1171
+ if (it.desc) {
1172
+ txt(slide, it.desc, { x: x + 0.2, y: yy, w: g.cw - 0.4, h: g.ch - (yy - y) - 0.2, fontSize: 11, color: ink2(), align: 'left', valign: 'top' });
1173
+ }
1174
+ });
1175
+ footer(slide, ...foot(p));
1176
+ }
1177
+ function renderT08Bars(slide, p) {
1178
+ bg(slide);
1179
+ kicker(slide, p.kicker);
1180
+ title(slide, p.title);
1181
+ subtitle(slide, p.subtitle);
1182
+ const items = (p.items || []).slice(0, 7);
1183
+ const top = BODY_TOP + 0.1;
1184
+ const rowH = (4.95 - top) / Math.max(items.length, 1);
1185
+ const maxV = Math.max(1, ...items.map((d) => Number(d.value) || 0));
1186
+ items.forEach((d, i) => {
1187
+ const y = top + i * rowH;
1188
+ txt(slide, d.label, { x: MX, y, w: 2.0, h: rowH * 0.7, fontSize: 13, color: ink(), align: 'left', valign: 'middle', fontFace: body() });
1189
+ const trackX = MX + 2.1, trackW = CW - 2.1 - 1.0;
1190
+ slide.addShape('roundRect', { x: trackX, y: y + rowH * 0.18, w: trackW, h: rowH * 0.5, rectRadius: 0.04, fill: { color: surfaceElev() } });
1191
+ const fw = trackW * (Number(d.value) || 0) / maxV;
1192
+ slide.addShape('roundRect', { x: trackX, y: y + rowH * 0.18, w: Math.max(0.05, fw), h: rowH * 0.5, rectRadius: 0.04, fill: { color: i % 2 ? gold() : 'C9962E' } });
1193
+ txt(slide, String(d.value), { x: trackX + trackW + 0.05, y, w: 1.0, h: rowH * 0.7, fontSize: 14, color: ink(), bold: true, align: 'right', valign: 'middle', fontFace: heading() });
1194
+ });
1195
+ footer(slide, ...foot(p));
1196
+ }
1197
+ function renderT08Line(slide, p) {
1198
+ bg(slide);
1199
+ kicker(slide, p.kicker);
1200
+ title(slide, p.title);
1201
+ subtitle(slide, p.subtitle);
1202
+ const items = (p.items || []).slice(0, 10);
1203
+ const maxV = Math.max(1, ...items.map((d) => Number(d.value) || 0));
1204
+ const baseY = 4.85;
1205
+ const top = BODY_TOP + 0.2;
1206
+ const avail = baseY - top;
1207
+ const gap = 0.2;
1208
+ const cw = (CW - gap * (items.length - 1)) / items.length;
1209
+ items.forEach((d, i) => {
1210
+ const x = MX + i * (cw + gap);
1211
+ const h = avail * (Number(d.value) || 0) / maxV;
1212
+ slide.addShape('rect', { x, y: baseY - h, w: cw, h, fill: { color: i % 2 ? gold() : 'C9962E' } });
1213
+ txt(slide, String(d.value), { x, y: baseY - h - 0.3, w: cw, h: 0.3, fontSize: 11, color: ink(), align: 'center', bold: true, fontFace: heading() });
1214
+ txt(slide, d.label, { x, y: baseY + 0.05, w: cw, h: 0.3, fontSize: 10, color: ink2(), align: 'center' });
1215
+ });
1216
+ footer(slide, ...foot(p));
1217
+ }
1218
+ function renderT08Dumbbell(slide, p) {
1219
+ bg(slide);
1220
+ kicker(slide, p.kicker);
1221
+ title(slide, p.title);
1222
+ subtitle(slide, p.subtitle);
1223
+ const items = (p.items || []).slice(0, 7);
1224
+ const top = BODY_TOP + 0.2;
1225
+ const rowH = (4.85 - top) / Math.max(items.length, 1);
1226
+ const maxV = Math.max(1, ...items.flatMap((d) => [Number(d.a) || 0, Number(d.b) || 0]));
1227
+ items.forEach((d, i) => {
1228
+ const y = top + i * rowH + rowH * 0.4;
1229
+ txt(slide, d.label, { x: MX, y: y - 0.2, w: 1.8, h: 0.4, fontSize: 12, color: ink(), align: 'left', valign: 'middle' });
1230
+ const trackX = MX + 1.9, trackW = CW - 1.9 - 0.9;
1231
+ slide.addShape('roundRect', { x: trackX, y: y - 0.04, w: trackW, h: 0.08, rectRadius: 0.04, fill: { color: surfaceElev() } });
1232
+ const ax = trackX + trackW * (Number(d.a) || 0) / maxV;
1233
+ const bx = trackX + trackW * (Number(d.b) || 0) / maxV;
1234
+ slide.addShape('oval', { x: ax - 0.12, y: y - 0.12, w: 0.24, h: 0.24, fill: { color: gold() } });
1235
+ slide.addShape('oval', { x: bx - 0.12, y: y - 0.12, w: 0.24, h: 0.24, fill: { color: 'C9962E' } });
1236
+ txt(slide, [d.a, d.b].filter(Boolean).join('→'), { x: trackX + trackW + 0.05, y: y - 0.2, w: 0.9, h: 0.4, fontSize: 11, color: ink2(), align: 'left' });
1237
+ });
1238
+ footer(slide, ...foot(p));
1239
+ }
1240
+ function renderT08Panels(slide, p) {
1241
+ bg(slide);
1242
+ kicker(slide, p.kicker);
1243
+ title(slide, p.title);
1244
+ subtitle(slide, p.subtitle);
1245
+ const items = (p.items || []).slice(0, 5);
1246
+ const top = BODY_TOP + 0.1;
1247
+ const rowH = (4.9 - top) / Math.max(items.length, 1);
1248
+ items.forEach((it, i) => {
1249
+ const y = top + i * rowH;
1250
+ card(slide, MX, y, CW, rowH - 0.15);
1251
+ slide.addShape('rect', { x: MX, y, w: 0.08, h: rowH - 0.15, fill: { color: gold() } });
1252
+ if (it.name)
1253
+ txt(slide, it.name, { x: MX + 0.25, y: y + 0.12, w: 3.0, h: rowH - 0.3, fontSize: 16, color: ink(), bold: true, valign: 'middle', fontFace: heading() });
1254
+ if (it.note)
1255
+ txt(slide, it.note, { x: MX + 3.4, y: y + 0.12, w: CW - 3.6, h: rowH - 0.3, fontSize: 12, color: ink2(), valign: 'middle' });
1256
+ });
1257
+ footer(slide, ...foot(p));
1258
+ }
1259
+ function renderT08Statwall(slide, p, opts) {
1260
+ bg(slide);
1261
+ kicker(slide, p.kicker);
1262
+ title(slide, p.title);
1263
+ subtitle(slide, p.subtitle);
1264
+ const items = (p.items || []).slice(0, 8);
1265
+ const perRow = Math.min(opts.cols, items.length || 1);
1266
+ const g = gridGeom(items.length || 1, perRow, BODY_TOP + 0.2);
1267
+ items.forEach((it, i) => {
1268
+ const r = Math.floor(i / perRow), c = i % perRow;
1269
+ const x = MX + c * (g.cw + g.gap), y = g.top + r * (g.ch + g.gap);
1270
+ card(slide, x, y, g.cw, g.ch);
1271
+ txt(slide, [it.value, it.unit].filter(Boolean).join(' '), { x: x + 0.2, y: y + 0.2, w: g.cw - 0.4, h: 0.6, fontSize: 30, color: gold(), bold: true, fontFace: heading() });
1272
+ txt(slide, it.label, { x: x + 0.2, y: y + 0.85, w: g.cw - 0.4, h: g.ch - 1.0, fontSize: 12, color: ink2(), valign: 'top' });
1273
+ });
1274
+ footer(slide, ...foot(p));
1275
+ }
1276
+ function renderT08ArchFlow(slide, p) {
1277
+ bg(slide);
1278
+ kicker(slide, p.kicker);
1279
+ title(slide, p.title);
1280
+ subtitle(slide, p.subtitle);
1281
+ const nodes = (p.nodes || p.items || []).slice(0, 6);
1282
+ const n = nodes.length || 1;
1283
+ const gap = 0.25;
1284
+ const cw = (CW - gap * (n - 1)) / n;
1285
+ nodes.forEach((nd, i) => {
1286
+ const x = MX + i * (cw + gap);
1287
+ card(slide, x, BODY_TOP + 0.2, cw, 2.6);
1288
+ if (nd.name)
1289
+ txt(slide, nd.name, { x: x + 0.2, y: BODY_TOP + 0.45, w: cw - 0.4, h: 0.5, fontSize: 15, color: ink(), bold: true, fontFace: heading() });
1290
+ if (nd.desc)
1291
+ txt(slide, nd.desc, { x: x + 0.2, y: BODY_TOP + 1.0, w: cw - 0.4, h: 1.6, fontSize: 11, color: ink2(), valign: 'top' });
1292
+ if (i < n - 1)
1293
+ txt(slide, '→', { x: x + cw + 0.01, y: BODY_TOP + 1.3, w: gap, h: 0.5, fontSize: 18, color: gold(), align: 'center', bold: true });
1294
+ });
1295
+ footer(slide, ...foot(p));
1296
+ }
1297
+ function renderT08CapitalLoop(slide, p) {
1298
+ bg(slide);
1299
+ kicker(slide, p.kicker);
1300
+ title(slide, p.title);
1301
+ subtitle(slide, p.subtitle);
1302
+ const steps = (p.steps || p.items || []).slice(0, 5);
1303
+ const n = steps.length || 1;
1304
+ const gap = 0.3;
1305
+ const cw = (CW - gap * (n - 1)) / n;
1306
+ steps.forEach((s, i) => {
1307
+ const x = MX + i * (cw + gap);
1308
+ card(slide, x, BODY_TOP + 0.4, cw, 2.2);
1309
+ if (s.title)
1310
+ txt(slide, s.title, { x: x + 0.15, y: BODY_TOP + 0.7, w: cw - 0.3, h: 0.5, fontSize: 14, color: ink(), bold: true, fontFace: heading() });
1311
+ if (s.note)
1312
+ txt(slide, s.note, { x: x + 0.15, y: BODY_TOP + 1.25, w: cw - 0.3, h: 1.2, fontSize: 11, color: ink2(), valign: 'top' });
1313
+ if (i < n - 1)
1314
+ txt(slide, '→', { x: x + cw + 0.01, y: BODY_TOP + 1.4, w: gap, h: 0.5, fontSize: 18, color: gold(), align: 'center', bold: true });
1315
+ });
1316
+ footer(slide, ...foot(p));
1317
+ }
1318
+ const RENDERERS = {
1319
+ theme08_cover_v1: renderTheme08Cover,
1320
+ theme08_chapter_v1: renderTheme08Chapter,
1321
+ theme08_content_v1: renderTheme08Content,
1322
+ theme08_contents_v1: renderTheme08Contents,
1323
+ theme08_metrics_v1: renderTheme08Metrics,
1324
+ theme08_metric_big_v1: renderTheme08MetricBig,
1325
+ theme08_feature_v1: renderTheme08Feature,
1326
+ theme08_overview_v1: renderTheme08Overview,
1327
+ theme08_strategy_v1: renderTheme08Strategy,
1328
+ theme08_process_v1: renderTheme08Process,
1329
+ theme08_timeline_v1: renderTheme08Timeline,
1330
+ theme08_roadmap_v1: renderTheme08Roadmap,
1331
+ theme08_workflow_v1: renderTheme08Workflow,
1332
+ theme08_team_v1: renderTheme08Team,
1333
+ theme08_partners_v1: renderTheme08Partners,
1334
+ theme08_gallery_v1: renderTheme08Gallery,
1335
+ theme08_collage_v1: renderTheme08Collage,
1336
+ theme08_bubble_v1: renderTheme08Bubble,
1337
+ theme08_quote_v1: renderTheme08Quote,
1338
+ theme08_closing_v1: renderTheme08Closing,
1339
+ theme08_hero_split_v1: renderTheme08HeroSplit,
1340
+ theme08_ecosystem_v1: renderTheme08Ecosystem,
1341
+ theme08_case_v1: renderTheme08Case,
1342
+ theme08_funding_v1: renderTheme08Funding,
1343
+ theme08_matrix_v1: renderTheme08Matrix,
1344
+ theme08_quadrant_v1: renderTheme08Quadrant,
1345
+ theme08_scorecard_v1: renderTheme08Scorecard,
1346
+ theme08_region_v1: renderTheme08Region,
1347
+ theme08_range_v1: renderTheme08Range,
1348
+ theme08_heatmap_v1: renderTheme08Heatmap,
1349
+ theme08_waterfall_v1: renderTheme08Waterfall,
1350
+ theme08_chart_bar_v1: renderTheme08ChartBar,
1351
+ theme08_chart_donut_v1: renderTheme08ChartDonut,
1352
+ theme08_gauge_v1: renderTheme08Gauge,
1353
+ theme08_radar_v1: renderTheme08Radar,
1354
+ theme08_ranking_v1: renderTheme08Ranking,
1355
+ theme08_compare_v1: renderTheme08Compare,
1356
+ theme08_table_v1: renderTheme08Table,
1357
+ theme08_chain_v1: renderTheme08Chain,
1358
+ // ---- v2 变体版式(对齐参考 theme08 共 84 组件页) ----
1359
+ theme08_cover_v2: renderT08Cover2,
1360
+ theme08_cover_v3: renderT08Cover2,
1361
+ theme08_cover_v4: renderT08Cover2,
1362
+ theme08_cover_v5: renderT08Cover2,
1363
+ theme08_chapter_v2: renderT08Chapter2,
1364
+ theme08_chapter_v3: renderT08Chapter2,
1365
+ theme08_chapter_v4: renderT08Chapter2,
1366
+ theme08_quote_statement_v1: renderT08Quote2,
1367
+ theme08_quote_resources_v1: renderT08Quote2,
1368
+ theme08_quote_verdict_v1: renderT08Quote2,
1369
+ theme08_quote_twofield_v1: renderT08Quote2,
1370
+ theme08_quote_manifesto_v1: renderT08Quote2,
1371
+ theme08_case_card_v1: (s, p) => renderT08GridCards(s, p, { cols: 3 }),
1372
+ theme08_case_card_v2: (s, p) => renderT08GridCards(s, p, { cols: 2 }),
1373
+ theme08_case_table_v1: (s, p) => renderT08GridCards(s, p, { cols: 1, list: true }),
1374
+ theme08_case_study_v1: (s, p) => renderT08GridCards(s, p, { cols: 2 }),
1375
+ theme08_case_grid_v1: (s, p) => renderT08GridCards(s, p, { cols: 4 }),
1376
+ theme08_case_list_v1: (s, p) => renderT08GridCards(s, p, { cols: 1, list: true }),
1377
+ theme08_region_anchor_v1: (s, p) => renderT08Statwall(s, p, { cols: 1 }),
1378
+ theme08_region_card_ny_v1: (s, p) => renderT08GridCards(s, p, { cols: 3 }),
1379
+ theme08_region_dotmap_v1: (s, p) => renderT08GridCards(s, p, { cols: 4 }),
1380
+ theme08_segment_v1: renderT08ArchFlow,
1381
+ theme08_pipeline_v1: renderT08ArchFlow,
1382
+ theme08_architecture_v1: renderT08ArchFlow,
1383
+ theme08_supply_v1: renderT08ArchFlow,
1384
+ theme08_compute_v1: renderT08ArchFlow,
1385
+ theme08_trend_v1: renderT08Bars,
1386
+ theme08_cross_v1: renderT08Bars,
1387
+ theme08_peak_v1: renderT08Line,
1388
+ theme08_pullback_v1: renderT08Dumbbell,
1389
+ theme08_peak_trough_v1: renderT08Dumbbell,
1390
+ theme08_capital_curve_v1: renderT08Line,
1391
+ theme08_revenue_v1: renderT08Panels,
1392
+ theme08_regulation_v1: renderT08Panels,
1393
+ theme08_squeeze_v1: renderT08Panels,
1394
+ theme08_early_stage_v1: (s, p) => renderT08Statwall(s, p, { cols: 4 }),
1395
+ theme08_investor_mix_v1: (s, p) => renderT08GridCards(s, p, { cols: 3 }),
1396
+ theme08_resource_map_v1: renderT08ArchFlow,
1397
+ theme08_closed_loop_v1: renderT08CapitalLoop,
1398
+ theme08_triptych_v1: (s, p) => renderT08GridCards(s, p, { cols: 3 }),
1399
+ theme08_scene_split_v1: (s, p) => renderT08GridCards(s, p, { cols: 2 }),
1400
+ theme08_budget_card_v1: (s, p) => renderT08GridCards(s, p, { cols: 3 }),
1401
+ theme08_mainlines_v1: (s, p) => renderT08GridCards(s, p, { cols: 3 }),
1402
+ theme08_migration_v1: renderT08CapitalLoop,
1403
+ theme08_size_split_v1: renderT08Dumbbell,
1404
+ };
1405
+ /** 将全部 theme08 渲染器注册到导出管线的按 layoutId 索引注册表。 */
1406
+ export function registerTheme08Renderers(register) {
1407
+ for (const [id, fn] of Object.entries(RENDERERS)) {
1408
+ register(id, fn);
1409
+ }
1410
+ }
1411
+ //# sourceMappingURL=theme08-pptx.js.map