@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,3279 @@
1
+ // lemonPPT - AI-powered presentation generation
2
+ // Copyright (c) 2026 lemonforme
3
+ // SPDX-License-Identifier: AGPL-3.0-or-later
4
+ //
5
+ // theme03 PPTX 版式专属渲染器(从 export-pptx.ts 抽出)。
6
+ // 共享基础设施(全局 helper + 通用渲染器 + 类型 + theme03 局部 helper/const)从 export-pptx.ts 导入;
7
+ // theme01 通用渲染器从 theme01-pptx.ts 复用;其他已抽出主题的渲染器从各自 theme<K>-pptx.ts 复用。
8
+ // 注册通过 registerTheme03Renderers 回调注入。
9
+ import { addTheme03Topbar, theme03TitleText, FONTS, COLORS, addTheme03Footer, addTheme02Card, CHART_COLORS, addImageMaybe, getTheme03PricingFeatureValue, getTheme03ComparisonValue } from './export-pptx.js';
10
+ function renderTheme03CoverV1(slide, props) {
11
+ addTheme03Topbar(slide, props);
12
+ slide.addText(theme03TitleText(props.title ?? ''), {
13
+ x: 0.84375, y: 1.5, w: 7.0, h: 1.4,
14
+ fontSize: 54, fontFace: FONTS.heading, valign: 'top',
15
+ });
16
+ if (props.subtitle) {
17
+ slide.addText(props.subtitle, {
18
+ x: 0.84375, y: 2.95, w: 6.5, h: 0.8,
19
+ fontSize: 16, color: COLORS.secondary, fontFace: FONTS.body,
20
+ valign: 'top',
21
+ });
22
+ }
23
+ const metricX = 7.2;
24
+ slide.addText(props.metricValue ?? '', {
25
+ x: metricX, y: 2.2, w: 2.5, h: 0.8,
26
+ fontSize: 72, color: COLORS.primary, bold: true, fontFace: FONTS.heading,
27
+ valign: 'bottom',
28
+ });
29
+ slide.addText(props.metricUnit ?? '', {
30
+ x: metricX + 1.4, y: 2.6, w: 1.0, h: 0.3,
31
+ fontSize: 20, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
32
+ valign: 'bottom',
33
+ });
34
+ if (props.metricLabel) {
35
+ slide.addText(props.metricLabel, {
36
+ x: metricX, y: 3.05, w: 2.5, h: 0.3,
37
+ fontSize: 14, color: COLORS.primary, bold: true, fontFace: FONTS.body,
38
+ });
39
+ }
40
+ if (props.stats && props.stats.length > 0) {
41
+ const statY = 4.1;
42
+ const statW = 2.6;
43
+ props.stats.slice(0, 3).forEach((stat, idx) => {
44
+ const x = 0.84375 + idx * (statW + 0.3);
45
+ slide.addText(`${stat.value}${stat.unit ?? ''}`, {
46
+ x, y: statY, w: statW, h: 0.35,
47
+ fontSize: 24, color: COLORS.primary, bold: true, fontFace: FONTS.heading,
48
+ });
49
+ slide.addText(stat.label ?? '', {
50
+ x, y: statY + 0.38, w: statW, h: 0.25,
51
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.mono,
52
+ });
53
+ });
54
+ }
55
+ addTheme03Footer(slide, props);
56
+ }
57
+ function renderTheme03ChapterV1(slide, props) {
58
+ addTheme03Topbar(slide, props);
59
+ slide.addText(props.number ?? '', {
60
+ x: 0.84375, y: 1.6, w: 2.2, h: 1.4,
61
+ fontSize: 110, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
62
+ valign: 'top',
63
+ });
64
+ if (props.numberEnglish) {
65
+ slide.addText(props.numberEnglish, {
66
+ x: 2.6, y: 1.8, w: 0.3, h: 2.0,
67
+ fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono,
68
+ valign: 'top',
69
+ });
70
+ }
71
+ slide.addText(theme03TitleText(props.title ?? ''), {
72
+ x: 3.1, y: 1.7, w: 5.8, h: 0.9,
73
+ fontSize: 42, fontFace: FONTS.heading, valign: 'top',
74
+ });
75
+ if (props.description) {
76
+ slide.addText(props.description, {
77
+ x: 3.1, y: 2.65, w: 5.0, h: 0.8,
78
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
79
+ });
80
+ }
81
+ if (props.items && props.items.length > 0) {
82
+ const itemY = 3.8;
83
+ props.items.slice(0, 4).forEach((item, idx) => {
84
+ slide.addText(`${item.number ?? idx + 1} ${item.title ?? ''}`, {
85
+ x: 3.1 + idx * 2.1, y: itemY, w: 2.0, h: 0.3,
86
+ fontSize: 12, color: COLORS.primary, fontFace: FONTS.body,
87
+ });
88
+ });
89
+ }
90
+ addTheme03Footer(slide, props);
91
+ }
92
+ function renderTheme03ContentV1(slide, props) {
93
+ addTheme03Topbar(slide, props);
94
+ slide.addText(theme03TitleText(props.title ?? ''), {
95
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
96
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
97
+ });
98
+ if (props.subtitle) {
99
+ slide.addText(props.subtitle, {
100
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
101
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
102
+ });
103
+ }
104
+ if (props.columns && props.columns.length > 0) {
105
+ const colW = 8.3 / Math.min(props.columns.length, 3);
106
+ const startY = 2.9;
107
+ props.columns.slice(0, 3).forEach((col, idx) => {
108
+ const x = 0.84375 + idx * colW;
109
+ addTheme02Card(slide, x, startY, colW - 0.25, 2.1, 0.08);
110
+ slide.addText(col.title ?? '', {
111
+ x: x + 0.15, y: startY + 0.15, w: colW - 0.55, h: 0.3,
112
+ fontSize: 12, color: COLORS.accent, bold: true, fontFace: FONTS.mono,
113
+ });
114
+ if (col.points && col.points.length > 0) {
115
+ const points = Array.isArray(col.points[0]) ? col.points : col.points.map((p) => typeof p === 'string' ? p : p.value);
116
+ slide.addText(points.map((p) => `• ${p}`).join('\n'), {
117
+ x: x + 0.15, y: startY + 0.55, w: colW - 0.55, h: 1.4,
118
+ fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
119
+ });
120
+ }
121
+ });
122
+ }
123
+ addTheme03Footer(slide, props);
124
+ }
125
+ function renderTheme03MetricBig(slide, props) {
126
+ addTheme03Topbar(slide, props);
127
+ slide.addText(`${props.value ?? ''}${props.unit ?? ''}`, {
128
+ x: 0.84375, y: 1.7, w: 4.5, h: 1.1,
129
+ fontSize: 88, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
130
+ valign: 'top',
131
+ });
132
+ if (props.label) {
133
+ slide.addText(props.label, {
134
+ x: 0.84375, y: 2.8, w: 4.5, h: 0.5,
135
+ fontSize: 18, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
136
+ });
137
+ }
138
+ if (props.description) {
139
+ slide.addText(props.description, {
140
+ x: 0.84375, y: 3.3, w: 4.5, h: 0.6,
141
+ fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
142
+ });
143
+ }
144
+ if (props.showInsight && props.insight) {
145
+ addTheme02Card(slide, 5.6, 1.7, 3.4, 2.3, 0.08);
146
+ slide.addShape('line', {
147
+ x1: 5.6, y1: 1.7, x2: 5.6, y2: 4.0,
148
+ line: { color: COLORS.accent, width: 3 },
149
+ });
150
+ slide.addText(props.insight.label ?? '', {
151
+ x: 5.8, y: 1.9, w: 3.0, h: 0.25,
152
+ fontSize: 11, color: COLORS.accent, fontFace: FONTS.mono,
153
+ });
154
+ slide.addText(props.insight.value ?? '', {
155
+ x: 5.8, y: 2.2, w: 3.0, h: 0.5,
156
+ fontSize: 28, color: COLORS.primary, bold: true, fontFace: FONTS.heading,
157
+ });
158
+ slide.addText(props.insight.description ?? '', {
159
+ x: 5.8, y: 2.75, w: 3.0, h: 1.0,
160
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
161
+ });
162
+ }
163
+ if (props.metrics && props.metrics.length > 0) {
164
+ const statY = 4.35;
165
+ const statW = 8.3 / Math.min(props.metrics.length, 3);
166
+ props.metrics.slice(0, 3).forEach((metric, idx) => {
167
+ const x = 0.84375 + idx * statW;
168
+ slide.addShape('line', {
169
+ x1: x, y1: statY - 0.05, x2: x + statW - 0.3, y2: statY - 0.05,
170
+ line: { color: COLORS.border, width: 1 },
171
+ });
172
+ slide.addText(`${metric.value}${metric.unit ?? ''}`, {
173
+ x, y: statY + 0.05, w: statW - 0.3, h: 0.35,
174
+ fontSize: 24, color: COLORS.primary, bold: true, fontFace: FONTS.heading,
175
+ });
176
+ slide.addText(metric.label ?? '', {
177
+ x, y: statY + 0.42, w: statW - 0.3, h: 0.25,
178
+ fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono,
179
+ });
180
+ });
181
+ }
182
+ addTheme03Footer(slide, props);
183
+ }
184
+ function renderTheme03RankingV1(slide, props) {
185
+ addTheme03Topbar(slide, props);
186
+ slide.addText(theme03TitleText(props.title ?? ''), {
187
+ x: 0.84375, y: 1.35, w: 8.3, h: 0.7,
188
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
189
+ });
190
+ if (props.subtitle) {
191
+ slide.addText(props.subtitle, {
192
+ x: 0.84375, y: 2.05, w: 7.5, h: 0.35,
193
+ fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
194
+ });
195
+ }
196
+ if (props.items && props.items.length > 0) {
197
+ const max = Math.max(...props.items.map((i) => Number(i.maxValue || i.value) || 0));
198
+ const rowH = 0.38;
199
+ const startY = 2.55;
200
+ props.items.slice(0, 6).forEach((item, idx) => {
201
+ const y = startY + idx * rowH;
202
+ const isTop = idx === 0;
203
+ const value = Number(item.value) || 0;
204
+ const pct = max ? value / max : 0;
205
+ slide.addText(item.rank ?? `${idx + 1}`, {
206
+ x: 0.84375, y, w: 0.4, h: 0.3,
207
+ fontSize: 11, color: isTop ? COLORS.accent : COLORS.secondary, fontFace: FONTS.mono,
208
+ });
209
+ slide.addText(item.name ?? '', {
210
+ x: 1.3, y, w: 1.8, h: 0.3,
211
+ fontSize: 13, color: isTop ? COLORS.accent : COLORS.primary, bold: true, fontFace: FONTS.body,
212
+ });
213
+ slide.addText(item.category ?? '', {
214
+ x: 3.2, y, w: 1.2, h: 0.3,
215
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body,
216
+ });
217
+ slide.addShape('roundRect', {
218
+ x: 4.5, y: y + 0.1, w: 3.2, h: 0.12,
219
+ fill: { color: COLORS.surfaceElevated },
220
+ rectRadius: 0.06,
221
+ });
222
+ if (pct > 0) {
223
+ slide.addShape('roundRect', {
224
+ x: 4.5, y: y + 0.1, w: 3.2 * pct, h: 0.12,
225
+ fill: { color: isTop ? COLORS.accent : COLORS.secondary },
226
+ rectRadius: 0.06,
227
+ });
228
+ }
229
+ slide.addText(`${item.value}${props.unit ?? ''}`, {
230
+ x: 7.9, y, w: 0.8, h: 0.3,
231
+ fontSize: 12, color: isTop ? COLORS.accent : COLORS.primary, bold: true, fontFace: FONTS.mono,
232
+ align: 'right',
233
+ });
234
+ });
235
+ }
236
+ if (props.insightLabel || props.insightText) {
237
+ const insightY = 4.85;
238
+ slide.addText(props.insightLabel ?? '', {
239
+ x: 0.84375, y: insightY, w: 1.0, h: 0.25,
240
+ fontSize: 10, color: COLORS.accent, fontFace: FONTS.mono,
241
+ });
242
+ slide.addText(props.insightText ?? '', {
243
+ x: 1.8, y: insightY, w: 7.0, h: 0.25,
244
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body,
245
+ });
246
+ }
247
+ addTheme03Footer(slide, props);
248
+ }
249
+ function renderTheme03QuoteV1(slide, props) {
250
+ addTheme03Topbar(slide, props);
251
+ slide.addText('“', {
252
+ x: 0.84375, y: 1.5, w: 1.0, h: 0.8,
253
+ fontSize: 90, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
254
+ });
255
+ slide.addText(theme03TitleText(props.quote ?? ''), {
256
+ x: 1.4, y: 2.0, w: 7.5, h: 1.4,
257
+ fontSize: 38, fontFace: FONTS.heading, valign: 'top',
258
+ });
259
+ const attrParts = [props.author, props.title, props.source].filter(Boolean).join(' / ');
260
+ if (attrParts) {
261
+ slide.addText(attrParts, {
262
+ x: 1.4, y: 3.55, w: 7.5, h: 0.3,
263
+ fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body,
264
+ });
265
+ }
266
+ addTheme03Footer(slide, props);
267
+ }
268
+ function renderTheme03CaseV1(slide, props) {
269
+ addTheme03Topbar(slide, props);
270
+ slide.addText(theme03TitleText(props.title ?? ''), {
271
+ x: 4.5, y: 1.4, w: 4.9, h: 0.7,
272
+ fontSize: 34, fontFace: FONTS.heading, valign: 'top',
273
+ });
274
+ if (props.subtitle) {
275
+ slide.addText(props.subtitle, {
276
+ x: 4.5, y: 2.1, w: 4.9, h: 0.3,
277
+ fontSize: 14, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
278
+ });
279
+ }
280
+ if (props.description) {
281
+ slide.addText(props.description, {
282
+ x: 4.5, y: 2.45, w: 4.9, h: 0.6,
283
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
284
+ });
285
+ }
286
+ addTheme02Card(slide, 0.84375, 1.4, 3.3, 3.6, 0.08);
287
+ slide.addText('点击上传案例配图', {
288
+ x: 0.84375, y: 2.9, w: 3.3, h: 0.3,
289
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center',
290
+ });
291
+ if (props.milestones && props.milestones.length > 0) {
292
+ const startY = 3.2;
293
+ props.milestones.slice(0, 3).forEach((m, idx) => {
294
+ const y = startY + idx * 0.55;
295
+ slide.addShape('ellipse', {
296
+ x: 4.5, y: y + 0.05, w: 0.12, h: 0.12,
297
+ fill: { color: COLORS.accent },
298
+ });
299
+ slide.addText(m.date ?? '', {
300
+ x: 4.75, y, w: 1.0, h: 0.18,
301
+ fontSize: 10, color: COLORS.accent, fontFace: FONTS.mono,
302
+ });
303
+ slide.addText(m.title ?? '', {
304
+ x: 4.75, y: y + 0.2, w: 2.5, h: 0.18,
305
+ fontSize: 12, color: COLORS.primary, bold: true, fontFace: FONTS.body,
306
+ });
307
+ });
308
+ }
309
+ if (props.footnote) {
310
+ slide.addText(props.footnote, {
311
+ x: 4.5, y: 4.55, w: 4.9, h: 0.4,
312
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
313
+ });
314
+ }
315
+ addTheme03Footer(slide, props);
316
+ }
317
+ function renderTheme03ClosingV1(slide, props) {
318
+ addTheme03Topbar(slide, props);
319
+ slide.addText(props.title ?? '感谢阅读', {
320
+ x: 0.84375, y: 1.7, w: 4.5, h: 1.0,
321
+ fontSize: 54, color: COLORS.primary, bold: true, fontFace: FONTS.heading,
322
+ valign: 'top',
323
+ });
324
+ if (props.subtitle) {
325
+ slide.addText(props.subtitle, {
326
+ x: 0.84375, y: 2.7, w: 4.0, h: 0.4,
327
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
328
+ });
329
+ }
330
+ if (props.contact) {
331
+ slide.addText(props.contact, {
332
+ x: 0.84375, y: 3.3, w: 4.0, h: 0.25,
333
+ fontSize: 12, color: COLORS.accent, fontFace: FONTS.mono,
334
+ });
335
+ }
336
+ const columns = [
337
+ { title: props.leftColumnTitle, items: props.leftColumnItems },
338
+ { title: props.rightColumnTitle, items: props.rightColumnItems },
339
+ ];
340
+ const colW = 2.0;
341
+ columns.forEach((col, idx) => {
342
+ if (!col.title && (!col.items || col.items.length === 0))
343
+ return;
344
+ const x = 5.4 + idx * (colW + 0.4);
345
+ slide.addText(col.title ?? '', {
346
+ x, y: 1.7, w: colW, h: 0.25,
347
+ fontSize: 11, color: COLORS.accent, bold: true, fontFace: FONTS.mono,
348
+ });
349
+ if (col.items && col.items.length > 0) {
350
+ slide.addText(col.items.join('\n'), {
351
+ x, y: 2.05, w: colW, h: 1.8,
352
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
353
+ });
354
+ }
355
+ });
356
+ addTheme03Footer(slide, props);
357
+ }
358
+ function renderTheme03TableOfContentsV1(slide, props) {
359
+ addTheme03Topbar(slide, props);
360
+ slide.addText(theme03TitleText(props.title ?? ''), {
361
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
362
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
363
+ });
364
+ if (props.subtitle) {
365
+ slide.addText(props.subtitle, {
366
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
367
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
368
+ });
369
+ }
370
+ if (props.items && props.items.length > 0) {
371
+ const startY = 2.85;
372
+ const rowH = 0.52;
373
+ props.items.slice(0, 8).forEach((item, idx) => {
374
+ const y = startY + idx * rowH;
375
+ addTheme02Card(slide, 0.84375, y, 8.3125, 0.42, 0.06);
376
+ slide.addText(item.title ?? '', {
377
+ x: 1.05, y: y + 0.09, w: 3.8, h: 0.24,
378
+ fontSize: 14, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'middle',
379
+ });
380
+ slide.addText(item.page ?? '', {
381
+ x: 8.0, y: y + 0.09, w: 0.9, h: 0.24,
382
+ fontSize: 13, color: COLORS.accent, bold: true, fontFace: FONTS.mono, align: 'right', valign: 'middle',
383
+ });
384
+ });
385
+ }
386
+ addTheme03Footer(slide, props);
387
+ }
388
+ function renderTheme03MetricsV1(slide, props) {
389
+ addTheme03Topbar(slide, props);
390
+ slide.addText(theme03TitleText(props.title ?? ''), {
391
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
392
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
393
+ });
394
+ if (props.subtitle) {
395
+ slide.addText(props.subtitle, {
396
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
397
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
398
+ });
399
+ }
400
+ if (props.stats && props.stats.length > 0) {
401
+ const items = props.stats.slice(0, 8);
402
+ const cols = items.length <= 4 ? Math.min(items.length, 4) : 4;
403
+ const rows = Math.ceil(items.length / cols);
404
+ const cardW = (8.3125 - (cols - 1) * 0.25) / cols;
405
+ const cardH = rows === 1 ? 1.8 : 1.25;
406
+ const startY = 2.9;
407
+ items.forEach((stat, idx) => {
408
+ const col = idx % cols;
409
+ const row = Math.floor(idx / cols);
410
+ const x = 0.84375 + col * (cardW + 0.25);
411
+ const y = startY + row * (cardH + 0.25);
412
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
413
+ slide.addShape('roundRect', {
414
+ x, y, w: cardW, h: 0.08,
415
+ fill: { color: COLORS.accent }, rectRadius: 0.04,
416
+ });
417
+ const valueText = `${stat.value ?? ''}${stat.unit ?? ''}`;
418
+ slide.addText(valueText, {
419
+ x: x + 0.15, y: y + 0.25, w: cardW - 0.3, h: 0.4,
420
+ fontSize: 28, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
421
+ });
422
+ if (stat.change) {
423
+ slide.addText(stat.change, {
424
+ x: x + cardW - 1.1, y: y + 0.28, w: 0.95, h: 0.3,
425
+ fontSize: 12, color: COLORS.accent, bold: true, fontFace: FONTS.mono, align: 'right', valign: 'top',
426
+ });
427
+ }
428
+ slide.addText(stat.label ?? '', {
429
+ x: x + 0.15, y: y + 0.72, w: cardW - 0.3, h: 0.35,
430
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
431
+ });
432
+ });
433
+ }
434
+ addTheme03Footer(slide, props);
435
+ }
436
+ function renderTheme03FeatureV1(slide, props) {
437
+ addTheme03Topbar(slide, props);
438
+ slide.addText(theme03TitleText(props.title ?? ''), {
439
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
440
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
441
+ });
442
+ if (props.subtitle) {
443
+ slide.addText(props.subtitle, {
444
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
445
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
446
+ });
447
+ }
448
+ if (props.features && props.features.length > 0) {
449
+ const items = props.features.slice(0, 6);
450
+ const cols = Math.min(items.length, 3);
451
+ const rows = Math.ceil(items.length / cols);
452
+ const cardW = (8.3125 - (cols - 1) * 0.28) / cols;
453
+ const cardH = rows === 1 ? 2.2 : 1.55;
454
+ const startY = 2.85;
455
+ items.forEach((feature, idx) => {
456
+ const col = idx % cols;
457
+ const row = Math.floor(idx / cols);
458
+ const x = 0.84375 + col * (cardW + 0.28);
459
+ const y = startY + row * (cardH + 0.28);
460
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
461
+ slide.addText(feature.number ?? String(idx + 1).padStart(2, '0'), {
462
+ x: x + 0.15, y: y + 0.15, w: 0.8, h: 0.35,
463
+ fontSize: 24, color: COLORS.accent, bold: true, fontFace: FONTS.heading, valign: 'top',
464
+ });
465
+ slide.addText(feature.title ?? '', {
466
+ x: x + 0.15, y: y + 0.58, w: cardW - 0.3, h: 0.32,
467
+ fontSize: 16, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
468
+ });
469
+ slide.addText(feature.description ?? '', {
470
+ x: x + 0.15, y: y + 0.95, w: cardW - 0.3, h: cardH - 1.1,
471
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
472
+ });
473
+ });
474
+ }
475
+ addTheme03Footer(slide, props);
476
+ }
477
+ function renderTheme03ImageV1(slide, props) {
478
+ addTheme03Topbar(slide, props);
479
+ slide.addText(theme03TitleText(props.title ?? ''), {
480
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
481
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
482
+ });
483
+ if (props.subtitle) {
484
+ slide.addText(props.subtitle, {
485
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
486
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
487
+ });
488
+ }
489
+ const imgY = 2.85;
490
+ const imgH = 2.6;
491
+ addTheme02Card(slide, 0.84375, imgY, 8.3125, imgH, 0.1);
492
+ if (props.image) {
493
+ try {
494
+ slide.addImage({ path: props.image, x: 0.94375, y: imgY + 0.1, w: 8.1125, h: imgH - 0.2 });
495
+ }
496
+ catch {
497
+ slide.addText('(图片无法加载)', {
498
+ x: 0.84375, y: imgY + 1.1, w: 8.3125, h: 0.4,
499
+ fontSize: 14, color: COLORS.secondary, align: 'center', fontFace: FONTS.body,
500
+ });
501
+ }
502
+ }
503
+ else {
504
+ slide.addText('点击上传架构图 / 系统图', {
505
+ x: 0.84375, y: imgY + 1.1, w: 8.3125, h: 0.4,
506
+ fontSize: 13, color: COLORS.secondary, align: 'center', fontFace: FONTS.mono,
507
+ });
508
+ }
509
+ if (props.caption) {
510
+ slide.addText(props.caption, {
511
+ x: 0.84375, y: imgY + imgH + 0.15, w: 8.3125, h: 0.3,
512
+ fontSize: 12, color: COLORS.secondary, align: 'center', fontFace: FONTS.body,
513
+ });
514
+ }
515
+ addTheme03Footer(slide, props);
516
+ }
517
+ function renderTheme03ChartDonut(slide, props) {
518
+ addTheme03Topbar(slide, props);
519
+ slide.addText(theme03TitleText(props.title ?? '环形图'), {
520
+ x: 0.84375, y: 1.35, w: 8.3, h: 0.7,
521
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
522
+ });
523
+ if (props.subtitle) {
524
+ slide.addText(props.subtitle, {
525
+ x: 0.84375, y: 2.05, w: 7.5, h: 0.35,
526
+ fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
527
+ });
528
+ }
529
+ const rawSegments = (props.segments || [])
530
+ .filter((s) => s != null && !!(s.label || s.value))
531
+ .map((s) => ({ ...s }))
532
+ .sort((a, b) => {
533
+ const na = parseFloat(String(a.value || '0').replace(/,/g, ''));
534
+ const nb = parseFloat(String(b.value || '0').replace(/,/g, ''));
535
+ return nb - na;
536
+ });
537
+ if (rawSegments.length === 0) {
538
+ slide.addText('(暂无环形图数据)', {
539
+ x: 2, y: 3.2, w: 6, h: 0.8,
540
+ fontSize: 18, color: COLORS.secondary, align: 'center', valign: 'middle',
541
+ fontFace: FONTS.body,
542
+ });
543
+ addTheme03Footer(slide, props);
544
+ return;
545
+ }
546
+ const totalValue = rawSegments.reduce((sum, s) => {
547
+ return sum + parseFloat(String(s.value || '0').replace(/,/g, ''));
548
+ }, 0);
549
+ const segments = rawSegments.map((s) => {
550
+ const n = parseFloat(String(s.value || '0').replace(/,/g, ''));
551
+ const pct = totalValue > 0 ? Math.round((n / totalValue) * 1000) / 10 : 0;
552
+ return { ...s, percent: `${pct}%`, numericValue: n };
553
+ });
554
+ const labels = segments.map((s) => s.label || '');
555
+ const values = segments.map((s) => s.numericValue);
556
+ const chartColors = segments.map((_, i) => CHART_COLORS[i % CHART_COLORS.length]);
557
+ const showInsight = props.showInsight !== false;
558
+ const insight = props.insight;
559
+ const hasInsight = showInsight && !!insight && (!!insight.value || !!insight.label || !!insight.description);
560
+ const chartW = hasInsight ? 4.0 : 4.8;
561
+ slide.addChart('doughnut', [
562
+ { name: props.title || '', labels, values },
563
+ ], {
564
+ x: 0.6, y: 2.4, w: chartW, h: 3.0,
565
+ chartColors,
566
+ showValue: false,
567
+ holeSize: 60,
568
+ });
569
+ const total = props.total || { value: String(totalValue), label: '合计' };
570
+ const centerX = 0.6 + chartW / 2;
571
+ slide.addText(total.value || String(totalValue), {
572
+ x: centerX - 1.8, y: 3.4, w: 3.6, h: 0.6,
573
+ fontSize: 28, color: COLORS.primary, bold: true, align: 'center', valign: 'middle',
574
+ fontFace: FONTS.heading,
575
+ });
576
+ slide.addText(total.label || '合计', {
577
+ x: centerX - 1.8, y: 4.0, w: 3.6, h: 0.3,
578
+ fontSize: 12, color: COLORS.secondary, align: 'center', valign: 'top',
579
+ fontFace: FONTS.body,
580
+ });
581
+ const legendX = hasInsight ? 4.8 : 5.6;
582
+ const legendW = hasInsight ? 2.6 : 3.2;
583
+ let legendY = 2.5;
584
+ segments.forEach((segment, index) => {
585
+ const color = chartColors[index % chartColors.length];
586
+ slide.addShape('ellipse', {
587
+ x: legendX, y: legendY + 0.08, w: 0.18, h: 0.18,
588
+ fill: { color },
589
+ });
590
+ slide.addText(segment.label || '', {
591
+ x: legendX + 0.3, y: legendY, w: legendW - 1.4, h: 0.28,
592
+ fontSize: 13, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
593
+ fontFace: FONTS.heading,
594
+ });
595
+ if (segment.labelEn) {
596
+ slide.addText(segment.labelEn, {
597
+ x: legendX + 0.3, y: legendY + 0.26, w: legendW - 1.4, h: 0.22,
598
+ fontSize: 10, color: COLORS.secondary, align: 'left', valign: 'top',
599
+ fontFace: FONTS.body,
600
+ });
601
+ }
602
+ slide.addText(segment.percent || '', {
603
+ x: legendX + legendW - 1.0, y: legendY, w: 0.9, h: 0.28,
604
+ fontSize: 13, color: COLORS.accent, bold: true, align: 'right', valign: 'top',
605
+ fontFace: FONTS.heading,
606
+ });
607
+ slide.addText(segment.value || '', {
608
+ x: legendX + legendW - 1.0, y: legendY + 0.26, w: 0.9, h: 0.22,
609
+ fontSize: 10, color: COLORS.secondary, align: 'right', valign: 'top',
610
+ fontFace: FONTS.mono,
611
+ });
612
+ legendY += segment.labelEn ? 0.65 : 0.5;
613
+ });
614
+ if (hasInsight) {
615
+ slide.addShape('roundRect', {
616
+ x: 7.6, y: 2.3, w: 2.4, h: 3.0,
617
+ fill: { color: COLORS.surfaceElevated },
618
+ line: { color: COLORS.border, width: 1 },
619
+ rectRadius: 0.15,
620
+ });
621
+ let cursorY = 2.6;
622
+ if (insight.value) {
623
+ slide.addText(insight.value, {
624
+ x: 7.8, y: cursorY, w: 2.0, h: 0.6,
625
+ fontSize: 34, fontFace: FONTS.heading, bold: true, color: COLORS.accent,
626
+ });
627
+ cursorY += 0.6;
628
+ }
629
+ if (insight.label) {
630
+ slide.addText(insight.label, {
631
+ x: 7.8, y: cursorY, w: 2.0, h: 0.3,
632
+ fontSize: 10, fontFace: FONTS.body, color: COLORS.secondary,
633
+ });
634
+ cursorY += 0.45;
635
+ }
636
+ if (insight.description) {
637
+ cursorY += 0.15;
638
+ slide.addText(insight.description, {
639
+ x: 7.8, y: cursorY, w: 2.0, h: 1.5,
640
+ fontSize: 11, fontFace: FONTS.body, color: COLORS.secondary, valign: 'top',
641
+ });
642
+ }
643
+ }
644
+ addTheme03Footer(slide, props);
645
+ }
646
+ function renderTheme03ChartBar(slide, props) {
647
+ addTheme03Topbar(slide, props);
648
+ slide.addText(theme03TitleText(props.title ?? '柱状图'), {
649
+ x: 0.84375, y: 1.35, w: 8.3, h: 0.7,
650
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
651
+ });
652
+ if (props.subtitle) {
653
+ slide.addText(props.subtitle, {
654
+ x: 0.84375, y: 2.05, w: 7.5, h: 0.35,
655
+ fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
656
+ });
657
+ }
658
+ const bars = (props.bars || [])
659
+ .filter((b) => b != null && !!(b.label || b.value))
660
+ .map((b) => ({ ...b }));
661
+ if (bars.length === 0) {
662
+ slide.addText('(暂无柱状图数据)', {
663
+ x: 2, y: 3.2, w: 6, h: 0.8,
664
+ fontSize: 18, color: COLORS.secondary, align: 'center', valign: 'middle',
665
+ fontFace: FONTS.body,
666
+ });
667
+ addTheme03Footer(slide, props);
668
+ return;
669
+ }
670
+ const values = bars.map((b) => parseFloat(String(b.value || '0').replace(/,/g, '')) || 0);
671
+ const max = Math.max(...values, 1);
672
+ const topIndex = values.indexOf(max);
673
+ const labels = bars.map((b) => b.label || '');
674
+ const chartColors = bars.map((_, i) => (i === topIndex ? COLORS.accent : COLORS.secondary));
675
+ const showInsight = props.showInsight !== false;
676
+ const insight = props.insight;
677
+ const hasInsight = showInsight && !!insight && (!!insight.value || !!insight.label || !!insight.description);
678
+ const chartW = hasInsight ? 6.4 : 8.4;
679
+ const chartX = 0.84375;
680
+ slide.addChart('bar', [
681
+ { name: props.title || '', labels, values },
682
+ ], {
683
+ x: chartX, y: 2.55, w: chartW, h: 3.0,
684
+ chartColors,
685
+ barDir: 'col',
686
+ barGrouping: 'clustered',
687
+ showValue: true,
688
+ dataLabelPosition: 'outEnd',
689
+ dataLabelFontSize: 10,
690
+ dataLabelColor: COLORS.primary,
691
+ });
692
+ if (hasInsight) {
693
+ const insightX = chartX + chartW + 0.3;
694
+ slide.addShape('roundRect', {
695
+ x: insightX, y: 2.55, w: 2.4, h: 3.0,
696
+ fill: { color: COLORS.surfaceElevated },
697
+ line: { color: COLORS.border, width: 1 },
698
+ rectRadius: 0.15,
699
+ });
700
+ let cursorY = 2.85;
701
+ if (insight.value) {
702
+ slide.addText(insight.value, {
703
+ x: insightX + 0.2, y: cursorY, w: 2.0, h: 0.6,
704
+ fontSize: 34, fontFace: FONTS.heading, bold: true, color: COLORS.accent,
705
+ });
706
+ cursorY += 0.6;
707
+ }
708
+ if (insight.label) {
709
+ slide.addText(insight.label, {
710
+ x: insightX + 0.2, y: cursorY, w: 2.0, h: 0.3,
711
+ fontSize: 10, fontFace: FONTS.body, color: COLORS.secondary,
712
+ });
713
+ cursorY += 0.45;
714
+ }
715
+ if (insight.description) {
716
+ cursorY += 0.15;
717
+ slide.addText(insight.description, {
718
+ x: insightX + 0.2, y: cursorY, w: 2.0, h: 1.5,
719
+ fontSize: 11, fontFace: FONTS.body, color: COLORS.secondary, valign: 'top',
720
+ });
721
+ }
722
+ }
723
+ addTheme03Footer(slide, props);
724
+ }
725
+ function renderTheme03ChartFallback(slide, props, label) {
726
+ addTheme03Topbar(slide, props);
727
+ slide.addText(theme03TitleText(props.title ?? label), {
728
+ x: 0.84375, y: 1.35, w: 8.3, h: 0.7,
729
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
730
+ });
731
+ if (props.subtitle) {
732
+ slide.addText(props.subtitle, {
733
+ x: 0.84375, y: 2.05, w: 7.5, h: 0.35,
734
+ fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
735
+ });
736
+ }
737
+ const insight = props.insight;
738
+ const hasInsight = props.showInsight !== false && insight && (insight.value || insight.label || insight.description);
739
+ const placeholderW = hasInsight ? 5.4 : 6;
740
+ const placeholderX = hasInsight ? 1.1 : 2;
741
+ slide.addText(`(${label}占位:请在编辑器中查看完整图表)`, {
742
+ x: placeholderX, y: 3.2, w: placeholderW, h: 0.8,
743
+ fontSize: 18, color: COLORS.secondary, align: 'center', valign: 'middle',
744
+ fontFace: FONTS.body,
745
+ });
746
+ if (hasInsight) {
747
+ const panelX = 6.9;
748
+ const panelY = 2.55;
749
+ const panelW = 2.2;
750
+ const panelH = 3.05;
751
+ slide.addShape('roundRect', {
752
+ x: panelX, y: panelY, w: panelW, h: panelH,
753
+ fill: { color: COLORS.surface },
754
+ line: { color: COLORS.border, width: 1 },
755
+ rectRadius: 0.08,
756
+ });
757
+ slide.addShape('rect', {
758
+ x: panelX, y: panelY, w: 0.05, h: panelH,
759
+ fill: { color: COLORS.accent },
760
+ });
761
+ let currentY = panelY + 0.25;
762
+ if (insight.value) {
763
+ slide.addText(insight.value, {
764
+ x: panelX + 0.12, y: currentY, w: panelW - 0.24, h: 0.45,
765
+ fontSize: 26, color: COLORS.accent, bold: true, fontFace: FONTS.heading, valign: 'top',
766
+ });
767
+ currentY += 0.45;
768
+ }
769
+ if (insight.label) {
770
+ slide.addText(insight.label, {
771
+ x: panelX + 0.12, y: currentY, w: panelW - 0.24, h: 0.3,
772
+ fontSize: 13, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
773
+ });
774
+ currentY += 0.35;
775
+ }
776
+ if (insight.description) {
777
+ slide.addText(insight.description, {
778
+ x: panelX + 0.12, y: currentY, w: panelW - 0.24, h: panelH - (currentY - panelY) - 0.2,
779
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
780
+ });
781
+ }
782
+ }
783
+ addTheme03Footer(slide, props);
784
+ }
785
+ function renderTheme03ChartV1(slide, props) {
786
+ renderTheme03ChartFallback(slide, props, '通用图表');
787
+ }
788
+ function renderTheme03TrendV1(slide, props) {
789
+ renderTheme03ChartFallback(slide, props, '趋势图');
790
+ }
791
+ function renderTheme03ChartRadar(slide, props) {
792
+ renderTheme03ChartFallback(slide, props, '雷达图');
793
+ }
794
+ function renderTheme03ChartFunnel(slide, props) {
795
+ renderTheme03ChartFallback(slide, props, '漏斗图');
796
+ }
797
+ function renderTheme03ChartGauge(slide, props) {
798
+ renderTheme03ChartFallback(slide, props, '仪表盘');
799
+ }
800
+ function renderTheme03ChartHeatmap(slide, props) {
801
+ renderTheme03ChartFallback(slide, props, '热力图');
802
+ }
803
+ function renderTheme03ChartTreemap(slide, props) {
804
+ renderTheme03ChartFallback(slide, props, '矩形树图');
805
+ }
806
+ function renderTheme03ChartWordcloud(slide, props) {
807
+ renderTheme03ChartFallback(slide, props, '词云');
808
+ }
809
+ function renderTheme03ChartBar3d(slide, props) {
810
+ renderTheme03ChartFallback(slide, props, '3D 柱状图');
811
+ }
812
+ function renderTheme03ChartGraph(slide, props) {
813
+ renderTheme03ChartFallback(slide, props, '关系图');
814
+ }
815
+ function renderTheme03ChartSankey(slide, props) {
816
+ renderTheme03ChartFallback(slide, props, '桑基图');
817
+ }
818
+ function renderTheme03ChartSunburst(slide, props) {
819
+ renderTheme03ChartFallback(slide, props, '旭日图');
820
+ }
821
+ function renderTheme03TeamV1(slide, props) {
822
+ addTheme03Topbar(slide, props);
823
+ slide.addText(theme03TitleText(props.title ?? ''), {
824
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
825
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
826
+ });
827
+ if (props.subtitle) {
828
+ slide.addText(props.subtitle, {
829
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
830
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
831
+ });
832
+ }
833
+ const members = (props.members || []).filter((m) => m != null).slice(0, 6);
834
+ if (members.length > 0) {
835
+ const cols = members.length <= 4 ? 4 : 3;
836
+ const rows = Math.ceil(members.length / cols);
837
+ const gap = 0.25;
838
+ const cardW = (8.3125 - (cols - 1) * gap) / cols;
839
+ const cardH = rows === 1 ? 2.25 : 1.55;
840
+ const startY = 2.85;
841
+ members.forEach((member, idx) => {
842
+ const col = idx % cols;
843
+ const row = Math.floor(idx / cols);
844
+ const x = 0.84375 + col * (cardW + gap);
845
+ const y = startY + row * (cardH + gap);
846
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
847
+ slide.addShape('roundRect', {
848
+ x, y, w: cardW, h: 0.08,
849
+ fill: { color: COLORS.accent }, rectRadius: 0.04,
850
+ });
851
+ const avatarSize = 0.6875;
852
+ const avatarX = x + (cardW - avatarSize) / 2;
853
+ const avatarY = y + 0.18;
854
+ if (member.image) {
855
+ addImageMaybe(slide, member.image, avatarX, avatarY, avatarSize, avatarSize);
856
+ }
857
+ slide.addText(member.name ?? '', {
858
+ x, y: avatarY + avatarSize + 0.12, w: cardW, h: 0.28,
859
+ fontSize: 15, color: COLORS.primary, bold: true, fontFace: FONTS.body, align: 'center', valign: 'top',
860
+ });
861
+ slide.addText(member.role ?? '', {
862
+ x, y: avatarY + avatarSize + 0.42, w: cardW, h: 0.22,
863
+ fontSize: 11, color: COLORS.accent, fontFace: FONTS.mono, align: 'center', valign: 'top',
864
+ });
865
+ if (member.bio) {
866
+ slide.addText(member.bio, {
867
+ x: x + 0.12, y: avatarY + avatarSize + 0.68, w: cardW - 0.24, h: cardH - (avatarY + avatarSize + 0.68 - y) - 0.12,
868
+ fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
869
+ });
870
+ }
871
+ });
872
+ }
873
+ addTheme03Footer(slide, props);
874
+ }
875
+ function renderTheme03PartnersV1(slide, props) {
876
+ addTheme03Topbar(slide, props);
877
+ slide.addText(theme03TitleText(props.title ?? ''), {
878
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
879
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
880
+ });
881
+ if (props.subtitle) {
882
+ slide.addText(props.subtitle, {
883
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
884
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
885
+ });
886
+ }
887
+ const partners = (props.partners || []).filter((p) => p != null).slice(0, 8);
888
+ if (partners.length > 0) {
889
+ const cols = 4;
890
+ const rows = Math.ceil(partners.length / cols);
891
+ const gapX = 0.25;
892
+ const gapY = 0.25;
893
+ const cardW = (8.3125 - (cols - 1) * gapX) / cols;
894
+ const cardH = rows === 1 ? 1.35 : 1.15;
895
+ const startY = 2.85;
896
+ partners.forEach((partner, idx) => {
897
+ const col = idx % cols;
898
+ const row = Math.floor(idx / cols);
899
+ const x = 0.84375 + col * (cardW + gapX);
900
+ const y = startY + row * (cardH + gapY);
901
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
902
+ const logoH = 0.55;
903
+ if (partner.logo) {
904
+ addImageMaybe(slide, partner.logo, x + 0.2, y + 0.15, cardW - 0.4, logoH);
905
+ }
906
+ slide.addText(partner.name ?? '', {
907
+ x, y: y + logoH + 0.22, w: cardW, h: 0.22,
908
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, align: 'center', valign: 'top',
909
+ });
910
+ });
911
+ }
912
+ addTheme03Footer(slide, props);
913
+ }
914
+ function renderTheme03PricingV1(slide, props) {
915
+ addTheme03Topbar(slide, props);
916
+ slide.addText(theme03TitleText(props.title ?? ''), {
917
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
918
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
919
+ });
920
+ if (props.subtitle) {
921
+ slide.addText(props.subtitle, {
922
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
923
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
924
+ });
925
+ }
926
+ const tiers = (props.tiers || []).filter((t) => t != null).slice(0, 4);
927
+ if (tiers.length > 0) {
928
+ const cols = tiers.length <= 3 ? 3 : 4;
929
+ const rows = Math.ceil(tiers.length / cols);
930
+ const gap = 0.25;
931
+ const cardW = (8.3125 - (cols - 1) * gap) / cols;
932
+ const cardH = rows === 1 ? 2.5 : 1.9;
933
+ const startY = 2.85;
934
+ tiers.forEach((tier, idx) => {
935
+ const col = idx % cols;
936
+ const row = Math.floor(idx / cols);
937
+ const x = 0.84375 + col * (cardW + gap);
938
+ const y = startY + row * (cardH + gap);
939
+ const isHighlight = tier.highlight === true;
940
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
941
+ slide.addShape('roundRect', {
942
+ x, y, w: cardW, h: 0.08,
943
+ fill: { color: isHighlight ? COLORS.accent : COLORS.border }, rectRadius: 0.04,
944
+ });
945
+ if (isHighlight) {
946
+ slide.addText('推荐', {
947
+ x: x + cardW - 0.6, y: y + 0.12, w: 0.5, h: 0.18,
948
+ fontSize: 9, color: COLORS.accent, fontFace: FONTS.mono, align: 'right', valign: 'top',
949
+ });
950
+ }
951
+ slide.addText(tier.name ?? '', {
952
+ x, y: y + 0.22, w: cardW, h: 0.28,
953
+ fontSize: 15, color: COLORS.primary, bold: true, fontFace: FONTS.body, align: 'center', valign: 'top',
954
+ });
955
+ slide.addText([tier.price, tier.period].filter(Boolean).join(' '), {
956
+ x, y: y + 0.55, w: cardW, h: 0.32,
957
+ fontSize: 20, color: COLORS.primary, bold: true, fontFace: FONTS.heading, align: 'center', valign: 'top',
958
+ });
959
+ const features = (tier.features || []).slice(0, 5).map(getTheme03PricingFeatureValue).filter(Boolean);
960
+ let fy = y + 0.98;
961
+ features.forEach((feature) => {
962
+ slide.addText(feature, {
963
+ x: x + 0.12, y: fy, w: cardW - 0.24, h: 0.24,
964
+ fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, align: 'center', valign: 'top',
965
+ });
966
+ fy += 0.26;
967
+ });
968
+ if (tier.cta) {
969
+ slide.addText(tier.cta, {
970
+ x: x + 0.2, y: y + cardH - 0.38, w: cardW - 0.4, h: 0.26,
971
+ fontSize: 10, color: isHighlight ? COLORS.white : COLORS.accent,
972
+ fill: isHighlight ? { color: COLORS.accent } : { color: COLORS.surfaceElevated },
973
+ line: { color: isHighlight ? COLORS.accent : COLORS.border, width: 1 },
974
+ align: 'center', valign: 'middle', fontFace: FONTS.body,
975
+ });
976
+ }
977
+ });
978
+ }
979
+ addTheme03Footer(slide, props);
980
+ }
981
+ function renderTheme03ComparisonV1(slide, props) {
982
+ addTheme03Topbar(slide, props);
983
+ slide.addText(theme03TitleText(props.title ?? ''), {
984
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
985
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
986
+ });
987
+ if (props.subtitle) {
988
+ slide.addText(props.subtitle, {
989
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
990
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
991
+ });
992
+ }
993
+ const colW = 3.75;
994
+ const gap = 0.4;
995
+ const vsW = 0.5;
996
+ const totalW = colW * 2 + gap * 2 + vsW;
997
+ const startX = (10 - totalW) / 2;
998
+ const y = 2.9;
999
+ const colH = 2.5;
1000
+ addTheme02Card(slide, startX, y, colW, colH, 0.08);
1001
+ slide.addShape('roundRect', {
1002
+ x: startX, y, w: colW, h: 0.08,
1003
+ fill: { color: COLORS.secondary }, rectRadius: 0.04,
1004
+ });
1005
+ slide.addText(props.leftTitle ?? '方案 A', {
1006
+ x: startX, y: y + 0.18, w: colW, h: 0.32,
1007
+ fontSize: 18, color: COLORS.primary, bold: true, fontFace: FONTS.heading, align: 'center', valign: 'top',
1008
+ });
1009
+ const leftItems = (props.leftItems || []).map(getTheme03ComparisonValue).filter(Boolean);
1010
+ slide.addText(leftItems.map((i) => `• ${i}`).join('\n'), {
1011
+ x: startX + 0.15, y: y + 0.62, w: colW - 0.3, h: colH - 0.8,
1012
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1013
+ });
1014
+ slide.addShape('ellipse', {
1015
+ x: startX + colW + gap + (vsW - 0.4) / 2, y: y + colH / 2 - 0.2, w: 0.4, h: 0.4,
1016
+ fill: { color: COLORS.surfaceElevated },
1017
+ line: { color: COLORS.border, width: 1 },
1018
+ });
1019
+ slide.addText('VS', {
1020
+ x: startX + colW + gap, y: y + colH / 2 - 0.2, w: vsW, h: 0.4,
1021
+ fontSize: 12, color: COLORS.secondary, bold: true, fontFace: FONTS.mono, align: 'center', valign: 'middle',
1022
+ });
1023
+ const rightX = startX + colW + gap + vsW + gap;
1024
+ addTheme02Card(slide, rightX, y, colW, colH, 0.08);
1025
+ slide.addShape('roundRect', {
1026
+ x: rightX, y, w: colW, h: 0.08,
1027
+ fill: { color: COLORS.accent }, rectRadius: 0.04,
1028
+ });
1029
+ slide.addText(props.rightTitle ?? '方案 B', {
1030
+ x: rightX, y: y + 0.18, w: colW, h: 0.32,
1031
+ fontSize: 18, color: COLORS.primary, bold: true, fontFace: FONTS.heading, align: 'center', valign: 'top',
1032
+ });
1033
+ const rightItems = (props.rightItems || []).map(getTheme03ComparisonValue).filter(Boolean);
1034
+ slide.addText(rightItems.map((i) => `• ${i}`).join('\n'), {
1035
+ x: rightX + 0.15, y: y + 0.62, w: colW - 0.3, h: colH - 0.8,
1036
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1037
+ });
1038
+ addTheme03Footer(slide, props);
1039
+ }
1040
+ function renderTheme03GalleryV1(slide, props) {
1041
+ addTheme03Topbar(slide, props);
1042
+ slide.addText(theme03TitleText(props.title ?? ''), {
1043
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1044
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1045
+ });
1046
+ if (props.subtitle) {
1047
+ slide.addText(props.subtitle, {
1048
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
1049
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1050
+ });
1051
+ }
1052
+ const images = (props.images || []).slice(0, 4);
1053
+ if (images.length > 0) {
1054
+ const cols = images.length <= 2 ? images.length : 2;
1055
+ const rows = Math.ceil(images.length / cols);
1056
+ const gapX = 0.28;
1057
+ const gapY = 0.24;
1058
+ const cardW = (8.3125 - (cols - 1) * gapX) / cols;
1059
+ const cardH = rows === 1 ? 2.6 : 1.55;
1060
+ const startY = 2.85;
1061
+ images.forEach((image, idx) => {
1062
+ const col = idx % cols;
1063
+ const row = Math.floor(idx / cols);
1064
+ const x = 0.84375 + col * (cardW + gapX);
1065
+ const y = startY + row * (cardH + gapY);
1066
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
1067
+ slide.addShape('roundRect', {
1068
+ x, y, w: cardW, h: 0.08,
1069
+ fill: { color: COLORS.accent }, rectRadius: 0.04,
1070
+ });
1071
+ slide.addText(String(idx + 1).padStart(2, '0'), {
1072
+ x: x + cardW - 0.55, y: y + 0.1, w: 0.45, h: 0.22,
1073
+ fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, align: 'right', valign: 'top',
1074
+ });
1075
+ if (image.url) {
1076
+ try {
1077
+ slide.addImage({ path: image.url, x: x + 0.1, y: y + 0.22, w: cardW - 0.2, h: cardH - 0.54 });
1078
+ }
1079
+ catch {
1080
+ slide.addText('(图片)', {
1081
+ x: x + 0.1, y: y + 0.6, w: cardW - 0.2, h: 0.3,
1082
+ fontSize: 12, color: COLORS.secondary, align: 'center', fontFace: FONTS.body,
1083
+ });
1084
+ }
1085
+ }
1086
+ if (image.caption) {
1087
+ slide.addText(image.caption, {
1088
+ x: x + 0.1, y: y + cardH - 0.28, w: cardW - 0.2, h: 0.22,
1089
+ fontSize: 11, color: COLORS.secondary, align: 'center', fontFace: FONTS.mono,
1090
+ });
1091
+ }
1092
+ });
1093
+ }
1094
+ addTheme03Footer(slide, props);
1095
+ }
1096
+ function renderTheme03NumberShowcaseV1(slide, props) {
1097
+ addTheme03Topbar(slide, props);
1098
+ slide.addText(theme03TitleText(props.title ?? ''), {
1099
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.7,
1100
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top', align: 'center',
1101
+ });
1102
+ if (props.subtitle) {
1103
+ slide.addText(props.subtitle, {
1104
+ x: 0.84375, y: 2.15, w: 8.3, h: 0.35,
1105
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top', align: 'center',
1106
+ });
1107
+ }
1108
+ const value = props.value ?? '';
1109
+ const unit = props.unit ?? '';
1110
+ const valueWidth = value.length > 3 ? 6.0 : 4.0;
1111
+ slide.addText(value, {
1112
+ x: (10 - valueWidth) / 2, y: 2.7, w: valueWidth, h: 1.2,
1113
+ fontSize: 100, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
1114
+ align: 'center', valign: 'middle',
1115
+ });
1116
+ if (unit) {
1117
+ slide.addText(unit, {
1118
+ x: (10 - valueWidth) / 2 + valueWidth + 0.1, y: 3.4, w: 1.2, h: 0.4,
1119
+ fontSize: 22, color: COLORS.secondary, bold: true, fontFace: FONTS.body,
1120
+ valign: 'top',
1121
+ });
1122
+ }
1123
+ if (props.description) {
1124
+ slide.addText(props.description, {
1125
+ x: 1.5, y: 4.05, w: 7.0, h: 0.6,
1126
+ fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top', align: 'center',
1127
+ });
1128
+ }
1129
+ addTheme03Footer(slide, props);
1130
+ }
1131
+ function renderTheme03BentoV1(slide, props) {
1132
+ addTheme03Topbar(slide, props);
1133
+ slide.addText(theme03TitleText(props.title ?? ''), {
1134
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1135
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1136
+ });
1137
+ if (props.subtitle) {
1138
+ slide.addText(props.subtitle, {
1139
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
1140
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1141
+ });
1142
+ }
1143
+ const items = (props.items || []).slice(0, 6);
1144
+ if (items.length > 0) {
1145
+ const gap = 0.22;
1146
+ const areaW = 8.3125;
1147
+ const areaH = 2.4;
1148
+ const startY = 2.85;
1149
+ items.forEach((item, idx) => {
1150
+ const size = item.size || 'medium';
1151
+ const isLarge = size === 'large';
1152
+ const isSmall = size === 'small';
1153
+ const w = isLarge ? (areaW - gap) / 2 : isSmall ? (areaW - gap * 3) / 4 : (areaW - gap) / 2;
1154
+ const h = isLarge ? areaH : (areaH - gap) / 2;
1155
+ const colSpan = isLarge ? 2 : isSmall ? 1 : 2;
1156
+ const col = idx % (4 / colSpan);
1157
+ const row = Math.floor(idx / (4 / colSpan));
1158
+ const x = 0.84375 + col * (w + gap);
1159
+ const y = startY + row * (h + gap);
1160
+ addTheme02Card(slide, x, y, w, h, 0.08);
1161
+ slide.addShape('roundRect', {
1162
+ x, y, w, h: 0.08,
1163
+ fill: { color: COLORS.accent }, rectRadius: 0.04,
1164
+ });
1165
+ const valueText = `${item.value ?? ''}${item.unit ?? ''}`;
1166
+ slide.addText(valueText, {
1167
+ x: x + 0.15, y: y + 0.25, w: w - 0.3, h: 0.5,
1168
+ fontSize: isLarge ? 36 : 24, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
1169
+ });
1170
+ slide.addText(item.label ?? '', {
1171
+ x: x + 0.15, y: y + h - 0.45, w: w - 0.3, h: 0.3,
1172
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1173
+ });
1174
+ });
1175
+ }
1176
+ addTheme03Footer(slide, props);
1177
+ }
1178
+ function renderTheme03QuadrantV1(slide, props) {
1179
+ addTheme03Topbar(slide, props);
1180
+ slide.addText(theme03TitleText(props.title ?? ''), {
1181
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1182
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1183
+ });
1184
+ if (props.subtitle) {
1185
+ slide.addText(props.subtitle, {
1186
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
1187
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1188
+ });
1189
+ }
1190
+ if (props.xAxis || props.yAxis) {
1191
+ slide.addText([props.xAxis, props.yAxis].filter(Boolean).join(' | '), {
1192
+ x: 0.84375, y: 2.62, w: 7.5, h: 0.22,
1193
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.mono, valign: 'top',
1194
+ });
1195
+ }
1196
+ const quadrants = (props.quadrants || []).slice(0, 4);
1197
+ const defaultLabels = ['高价值 / 高可行性', '高价值 / 低可行性', '低价值 / 高可行性', '低价值 / 低可行性'];
1198
+ const colors = [COLORS.accent, COLORS.secondary, CHART_COLORS[2] || COLORS.secondary, COLORS.secondary];
1199
+ if (quadrants.length > 0) {
1200
+ const cols = 2;
1201
+ const gap = 0.24;
1202
+ const cardW = (8.3125 - gap) / cols;
1203
+ const cardH = 1.35;
1204
+ const startY = 2.95;
1205
+ quadrants.forEach((q, idx) => {
1206
+ const col = idx % cols;
1207
+ const row = Math.floor(idx / cols);
1208
+ const x = 0.84375 + col * (cardW + gap);
1209
+ const y = startY + row * (cardH + gap);
1210
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
1211
+ slide.addShape('roundRect', {
1212
+ x, y, w: cardW, h: 0.08,
1213
+ fill: { color: colors[idx % colors.length] }, rectRadius: 0.04,
1214
+ });
1215
+ slide.addText(q.label || defaultLabels[idx] || '', {
1216
+ x: x + 0.15, y: y + 0.16, w: cardW - 0.3, h: 0.3,
1217
+ fontSize: 14, color: COLORS.primary, bold: true, fontFace: FONTS.mono, valign: 'top',
1218
+ });
1219
+ const items = (q.items || []).slice(0, 4).map((item) => typeof item === 'string' ? item : item?.value).filter(Boolean);
1220
+ if (items.length > 0) {
1221
+ slide.addText(items.map((i) => `> ${i}`).join('\n'), {
1222
+ x: x + 0.15, y: y + 0.52, w: cardW - 0.3, h: cardH - 0.65,
1223
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1224
+ });
1225
+ }
1226
+ });
1227
+ }
1228
+ addTheme03Footer(slide, props);
1229
+ }
1230
+ function renderTheme03TableV1(slide, props) {
1231
+ addTheme03Topbar(slide, props);
1232
+ slide.addText(theme03TitleText(props.title ?? ''), {
1233
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1234
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1235
+ });
1236
+ if (props.subtitle) {
1237
+ slide.addText(props.subtitle, {
1238
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
1239
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1240
+ });
1241
+ }
1242
+ const headers = (props.headers || []).slice(0, 8);
1243
+ const rows = (props.rows || []).slice(0, 8);
1244
+ if (headers.length > 0 || rows.length > 0) {
1245
+ const tableY = 2.85;
1246
+ const tableH = 2.6;
1247
+ addTheme02Card(slide, 0.84375, tableY, 8.3125, tableH, 0.08);
1248
+ const colCount = Math.max(headers.length, rows[0]?.length || 1);
1249
+ const colW = 8.3125 / colCount;
1250
+ const headerH = 0.42;
1251
+ const rowH = (tableH - headerH) / Math.max(rows.length, 1);
1252
+ if (headers.length > 0) {
1253
+ headers.forEach((header, idx) => {
1254
+ slide.addText(header, {
1255
+ x: 0.84375 + idx * colW + 0.1, y: tableY, w: colW - 0.2, h: headerH,
1256
+ fontSize: 12, color: COLORS.accent, bold: true, fontFace: FONTS.mono, valign: 'middle',
1257
+ });
1258
+ });
1259
+ }
1260
+ rows.forEach((row, rIdx) => {
1261
+ const y = tableY + headerH + rIdx * rowH;
1262
+ row.forEach((cell, cIdx) => {
1263
+ const isHighlight = props.highlightFirstColumn && cIdx === 0;
1264
+ slide.addText(cell ?? '', {
1265
+ x: 0.84375 + cIdx * colW + 0.1, y, w: colW - 0.2, h: rowH,
1266
+ fontSize: 12, color: isHighlight ? COLORS.primary : COLORS.secondary, bold: isHighlight,
1267
+ fontFace: FONTS.body, valign: 'middle',
1268
+ });
1269
+ });
1270
+ });
1271
+ }
1272
+ addTheme03Footer(slide, props);
1273
+ }
1274
+ function renderTheme03TestimonialV1(slide, props) {
1275
+ addTheme03Topbar(slide, props);
1276
+ const cardX = 0.84375;
1277
+ const cardW = 8.3125;
1278
+ const cardY = 1.65;
1279
+ const cardH = 3.4;
1280
+ addTheme02Card(slide, cardX, cardY, cardW, cardH, 0.12);
1281
+ slide.addText('“', {
1282
+ x: cardX + 0.35, y: cardY + 0.25, w: 0.8, h: 0.7,
1283
+ fontSize: 72, color: COLORS.accent, fontFace: FONTS.heading,
1284
+ valign: 'top',
1285
+ });
1286
+ slide.addText(props.quote ?? '', {
1287
+ x: cardX + 0.7, y: cardY + 0.55, w: cardW - 1.4, h: 1.6,
1288
+ fontSize: 26, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
1289
+ });
1290
+ if (props.avatarUrl) {
1291
+ try {
1292
+ slide.addImage({ path: props.avatarUrl, x: cardX + 0.7, y: cardY + 2.35, w: 0.7, h: 0.7 });
1293
+ }
1294
+ catch {
1295
+ slide.addShape('ellipse', {
1296
+ x: cardX + 0.7, y: cardY + 2.35, w: 0.7, h: 0.7,
1297
+ fill: { color: COLORS.border },
1298
+ });
1299
+ }
1300
+ }
1301
+ else {
1302
+ slide.addShape('ellipse', {
1303
+ x: cardX + 0.7, y: cardY + 2.35, w: 0.7, h: 0.7,
1304
+ fill: { color: COLORS.border },
1305
+ });
1306
+ }
1307
+ const metaY = cardY + 2.45;
1308
+ if (props.author) {
1309
+ slide.addText(props.author, {
1310
+ x: cardX + 1.6, y: metaY, w: 4, h: 0.3,
1311
+ fontSize: 15, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
1312
+ });
1313
+ }
1314
+ if (props.role || props.company) {
1315
+ slide.addText([props.role, props.company].filter(Boolean).join(' · '), {
1316
+ x: cardX + 1.6, y: metaY + 0.28, w: 4, h: 0.25,
1317
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.mono, valign: 'top',
1318
+ });
1319
+ }
1320
+ addTheme03Footer(slide, props);
1321
+ }
1322
+ function renderTheme03TagsV1(slide, props) {
1323
+ addTheme03Topbar(slide, props);
1324
+ slide.addText(theme03TitleText(props.title ?? ''), {
1325
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1326
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1327
+ });
1328
+ if (props.subtitle) {
1329
+ slide.addText(props.subtitle, {
1330
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
1331
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1332
+ });
1333
+ }
1334
+ const tags = (props.tags || []).slice(0, 24);
1335
+ if (tags.length > 0) {
1336
+ const cardY = 2.8;
1337
+ const cardH = 2.55;
1338
+ addTheme02Card(slide, 0.84375, cardY, 8.3125, cardH, 0.08);
1339
+ const cols = 4;
1340
+ const rows = Math.ceil(tags.length / cols);
1341
+ const tagW = 1.9;
1342
+ const tagH = 0.34;
1343
+ const gapX = (8.3125 - cols * tagW) / (cols + 1);
1344
+ const gapY = (cardH - rows * tagH) / (rows + 1);
1345
+ tags.forEach((tag, idx) => {
1346
+ const col = idx % cols;
1347
+ const row = Math.floor(idx / cols);
1348
+ const x = 0.84375 + gapX + col * (tagW + gapX);
1349
+ const y = cardY + gapY + row * (tagH + gapY);
1350
+ const label = typeof tag === 'string' ? tag : tag.label ?? '';
1351
+ const value = typeof tag === 'string' ? undefined : tag.value;
1352
+ const text = value ? `${label} ${value}` : label;
1353
+ slide.addShape('roundRect', {
1354
+ x, y, w: tagW, h: tagH,
1355
+ fill: { color: COLORS.surfaceElevated }, line: { color: COLORS.border, width: 1 }, rectRadius: 0.06,
1356
+ });
1357
+ slide.addText(text, {
1358
+ x: x + 0.08, y, w: tagW - 0.16, h: tagH,
1359
+ fontSize: 11, color: COLORS.primary, bold: true, fontFace: FONTS.mono, align: 'center', valign: 'middle',
1360
+ });
1361
+ });
1362
+ }
1363
+ addTheme03Footer(slide, props);
1364
+ }
1365
+ function renderTheme03ProgressV1(slide, props) {
1366
+ addTheme03Topbar(slide, props);
1367
+ slide.addText(theme03TitleText(props.title ?? ''), {
1368
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1369
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1370
+ });
1371
+ if (props.subtitle) {
1372
+ slide.addText(props.subtitle, {
1373
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
1374
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1375
+ });
1376
+ }
1377
+ const items = (props.items || [])
1378
+ .filter((item) => item != null)
1379
+ .map((item) => {
1380
+ const value = Number(item.value) || 0;
1381
+ const max = Number(item.max) || 100;
1382
+ const pct = max > 0 ? Math.min(100, Math.round((value / max) * 1000) / 10) : 0;
1383
+ return { ...item, value, max, pct };
1384
+ })
1385
+ .slice(0, 6);
1386
+ if (items.length > 0) {
1387
+ const cardY = 2.8;
1388
+ const cardH = 2.55;
1389
+ addTheme02Card(slide, 0.84375, cardY, 8.3125, cardH, 0.08);
1390
+ const itemH = cardH / items.length;
1391
+ items.forEach((item, idx) => {
1392
+ const y = cardY + idx * itemH;
1393
+ const labelY = y + 0.12;
1394
+ slide.addText(item.label ?? '', {
1395
+ x: 0.99375, y: labelY, w: 4, h: 0.28,
1396
+ fontSize: 13, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
1397
+ });
1398
+ slide.addText(`${item.value}${item.unit ?? ''}`, {
1399
+ x: 5.5, y: labelY, w: 1.2, h: 0.28,
1400
+ fontSize: 13, color: COLORS.primary, bold: true, fontFace: FONTS.mono, align: 'right', valign: 'top',
1401
+ });
1402
+ slide.addText(`${item.pct}%`, {
1403
+ x: 6.8, y: labelY, w: 1.2, h: 0.28,
1404
+ fontSize: 13, color: COLORS.accent, bold: true, fontFace: FONTS.mono, align: 'right', valign: 'top',
1405
+ });
1406
+ const barY = y + 0.45;
1407
+ const barH = 0.1;
1408
+ const barW = 8.0125;
1409
+ slide.addShape('roundRect', {
1410
+ x: 0.99375, y: barY, w: barW, h: barH,
1411
+ fill: { color: COLORS.border }, rectRadius: 0.05,
1412
+ });
1413
+ if (item.pct > 0) {
1414
+ slide.addShape('roundRect', {
1415
+ x: 0.99375, y: barY, w: barW * (item.pct / 100), h: barH,
1416
+ fill: { color: COLORS.accent }, rectRadius: 0.05,
1417
+ });
1418
+ }
1419
+ });
1420
+ }
1421
+ addTheme03Footer(slide, props);
1422
+ }
1423
+ function renderTheme03FaqV1(slide, props) {
1424
+ addTheme03Topbar(slide, props);
1425
+ slide.addText(theme03TitleText(props.title ?? ''), {
1426
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1427
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1428
+ });
1429
+ if (props.subtitle) {
1430
+ slide.addText(props.subtitle, {
1431
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
1432
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1433
+ });
1434
+ }
1435
+ const items = (props.items || []).filter((i) => i != null).slice(0, 8);
1436
+ if (items.length > 0) {
1437
+ const cols = 2;
1438
+ const rows = Math.ceil(items.length / cols);
1439
+ const gapX = 0.28;
1440
+ const gapY = 0.22;
1441
+ const cardW = (8.3125 - gapX) / cols;
1442
+ const cardH = rows <= 2 ? 1.35 : 1.05;
1443
+ const startY = 2.85;
1444
+ items.forEach((item, idx) => {
1445
+ const col = idx % cols;
1446
+ const row = Math.floor(idx / cols);
1447
+ const x = 0.84375 + col * (cardW + gapX);
1448
+ const y = startY + row * (cardH + gapY);
1449
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
1450
+ slide.addShape('rect', {
1451
+ x, y, w: 0.06, h: cardH,
1452
+ fill: { color: COLORS.accent },
1453
+ });
1454
+ slide.addText('Q', {
1455
+ x: x + 0.12, y: y + 0.12, w: 0.22, h: 0.22,
1456
+ fontSize: 10, color: COLORS.accent, bold: true, fontFace: FONTS.mono, align: 'center', valign: 'middle',
1457
+ });
1458
+ slide.addText(item.q ?? '', {
1459
+ x: x + 0.4, y: y + 0.1, w: cardW - 0.55, h: 0.28,
1460
+ fontSize: 13, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
1461
+ });
1462
+ slide.addText('A', {
1463
+ x: x + 0.12, y: y + 0.42, w: 0.22, h: 0.2,
1464
+ fontSize: 10, color: COLORS.secondary, bold: true, fontFace: FONTS.mono, align: 'center', valign: 'middle',
1465
+ });
1466
+ slide.addText(item.a ?? '', {
1467
+ x: x + 0.4, y: y + 0.42, w: cardW - 0.55, h: cardH - 0.58,
1468
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1469
+ });
1470
+ });
1471
+ }
1472
+ addTheme03Footer(slide, props);
1473
+ }
1474
+ function renderTheme03ProcessV1(slide, props) {
1475
+ addTheme03Topbar(slide, props);
1476
+ slide.addText(theme03TitleText(props.title ?? ''), {
1477
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1478
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1479
+ });
1480
+ if (props.subtitle) {
1481
+ slide.addText(props.subtitle, {
1482
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
1483
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1484
+ });
1485
+ }
1486
+ const steps = (props.steps || []).filter((s) => s != null);
1487
+ if (steps.length > 0) {
1488
+ const items = steps.slice(0, 6);
1489
+ const count = items.length;
1490
+ const gap = 0.22;
1491
+ const cardW = (8.3125 - gap * (count - 1)) / count;
1492
+ const cardH = 1.95;
1493
+ const startY = 2.9;
1494
+ items.forEach((step, idx) => {
1495
+ const x = 0.84375 + idx * (cardW + gap);
1496
+ addTheme02Card(slide, x, startY, cardW, cardH, 0.08);
1497
+ slide.addText(String(idx + 1).padStart(2, '0'), {
1498
+ x: x + 0.12, y: startY + 0.12, w: 0.5, h: 0.28,
1499
+ fontSize: 12, color: COLORS.accent, bold: true, fontFace: FONTS.mono, valign: 'top',
1500
+ });
1501
+ slide.addText(step.title ?? '', {
1502
+ x: x + 0.12, y: startY + 0.44, w: cardW - 0.24, h: 0.35,
1503
+ fontSize: 15, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
1504
+ });
1505
+ slide.addText(step.description ?? '', {
1506
+ x: x + 0.12, y: startY + 0.84, w: cardW - 0.24, h: cardH - 1.0,
1507
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1508
+ });
1509
+ if (idx < items.length - 1) {
1510
+ slide.addShape('line', {
1511
+ x1: x + cardW + 0.02, y1: startY + cardH / 2, x2: x + cardW + gap - 0.02, y2: startY + cardH / 2,
1512
+ line: { color: COLORS.accent, width: 2 },
1513
+ });
1514
+ slide.addShape('triangle', {
1515
+ x: x + cardW + gap - 0.08, y: startY + cardH / 2 - 0.08, w: 0.16, h: 0.16,
1516
+ fill: { color: COLORS.accent },
1517
+ });
1518
+ }
1519
+ });
1520
+ }
1521
+ addTheme03Footer(slide, props);
1522
+ }
1523
+ function renderTheme03TimelineV1(slide, props) {
1524
+ addTheme03Topbar(slide, props);
1525
+ slide.addText(theme03TitleText(props.title ?? ''), {
1526
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1527
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1528
+ });
1529
+ if (props.subtitle) {
1530
+ slide.addText(props.subtitle, {
1531
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
1532
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1533
+ });
1534
+ }
1535
+ const milestones = (props.milestones || []).filter((m) => m != null && !!(m.date || m.title)).slice(0, 6);
1536
+ if (milestones.length > 0) {
1537
+ const count = milestones.length;
1538
+ const stepWidth = 8.3125 / count;
1539
+ const lineY = 3.55;
1540
+ slide.addShape('line', {
1541
+ x1: 0.84375, y1: lineY, x2: 9.15625, y2: lineY,
1542
+ line: { color: COLORS.border, width: 2 },
1543
+ });
1544
+ milestones.forEach((m, idx) => {
1545
+ const cx = 0.84375 + stepWidth * idx + stepWidth / 2;
1546
+ const isTop = idx % 2 === 0;
1547
+ const cardW = Math.min(stepWidth - 0.2, 2.0);
1548
+ const cardH = 1.15;
1549
+ const cardY = isTop ? lineY - 1.35 : lineY + 0.35;
1550
+ slide.addShape('ellipse', {
1551
+ x: cx - 0.12, y: lineY - 0.12, w: 0.24, h: 0.24,
1552
+ fill: { color: COLORS.accent },
1553
+ });
1554
+ slide.addText(m.date ?? '', {
1555
+ x: cx - stepWidth / 2 + 0.05, y: isTop ? lineY + 0.35 : lineY - 0.65, w: stepWidth - 0.1, h: 0.3,
1556
+ fontSize: 11, color: COLORS.accent, bold: true, fontFace: FONTS.mono, align: 'center', valign: 'middle',
1557
+ });
1558
+ addTheme02Card(slide, cx - cardW / 2, cardY, cardW, cardH, 0.08);
1559
+ slide.addText(m.title ?? '', {
1560
+ x: cx - cardW / 2 + 0.1, y: cardY + 0.1, w: cardW - 0.2, h: 0.3,
1561
+ fontSize: 13, color: COLORS.primary, bold: true, fontFace: FONTS.heading, align: 'center', valign: 'top',
1562
+ });
1563
+ slide.addText(m.description ?? '', {
1564
+ x: cx - cardW / 2 + 0.1, y: cardY + 0.44, w: cardW - 0.2, h: cardH - 0.58,
1565
+ fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, align: 'center', valign: 'top',
1566
+ });
1567
+ });
1568
+ }
1569
+ addTheme03Footer(slide, props);
1570
+ }
1571
+ function renderTheme03RoadmapV1(slide, props) {
1572
+ addTheme03Topbar(slide, props);
1573
+ slide.addText(theme03TitleText(props.title ?? ''), {
1574
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1575
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1576
+ });
1577
+ if (props.subtitle) {
1578
+ slide.addText(props.subtitle, {
1579
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
1580
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1581
+ });
1582
+ }
1583
+ const phases = (props.phases || []).filter((p) => p != null && !!(p.phase || (p.items && p.items.length > 0))).slice(0, 6);
1584
+ if (phases.length > 0) {
1585
+ const count = phases.length;
1586
+ const gap = 0.22;
1587
+ const cardW = (8.3125 - gap * (count - 1)) / count;
1588
+ const cardH = 2.05;
1589
+ const startY = 2.9;
1590
+ phases.forEach((phase, idx) => {
1591
+ const x = 0.84375 + idx * (cardW + gap);
1592
+ addTheme02Card(slide, x, startY, cardW, cardH, 0.08);
1593
+ slide.addShape('roundRect', {
1594
+ x: x + cardW / 2 - 0.22, y: startY - 0.18, w: 0.44, h: 0.36,
1595
+ fill: { color: COLORS.accent }, rectRadius: 0.18,
1596
+ });
1597
+ slide.addText(String(idx + 1).padStart(2, '0'), {
1598
+ x: x + cardW / 2 - 0.22, y: startY - 0.18, w: 0.44, h: 0.36,
1599
+ fontSize: 12, color: COLORS.white, bold: true, fontFace: FONTS.mono, align: 'center', valign: 'middle',
1600
+ });
1601
+ slide.addText(phase.phase ?? '', {
1602
+ x: x + 0.12, y: startY + 0.28, w: cardW - 0.24, h: 0.35,
1603
+ fontSize: 14, color: COLORS.primary, bold: true, fontFace: FONTS.heading, align: 'center', valign: 'top',
1604
+ });
1605
+ const items = (phase.items || []).filter((i) => i != null).slice(0, 5);
1606
+ items.forEach((item, itemIdx) => {
1607
+ const value = typeof item === 'string' ? item : (item.value ?? '');
1608
+ slide.addText('• ' + value, {
1609
+ x: x + 0.12, y: startY + 0.72 + itemIdx * 0.24, w: cardW - 0.24, h: 0.22,
1610
+ fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1611
+ });
1612
+ });
1613
+ if (idx < phases.length - 1) {
1614
+ slide.addShape('line', {
1615
+ x1: x + cardW + 0.02, y1: startY + cardH / 2, x2: x + cardW + gap - 0.02, y2: startY + cardH / 2,
1616
+ line: { color: COLORS.accent, width: 2 },
1617
+ });
1618
+ }
1619
+ });
1620
+ }
1621
+ addTheme03Footer(slide, props);
1622
+ }
1623
+ function renderTheme03SwotV1(slide, props) {
1624
+ addTheme03Topbar(slide, props);
1625
+ slide.addText(theme03TitleText(props.title ?? ''), {
1626
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1627
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1628
+ });
1629
+ if (props.subtitle) {
1630
+ slide.addText(props.subtitle, {
1631
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
1632
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1633
+ });
1634
+ }
1635
+ const cells = [
1636
+ { key: 'S', subtitle: '优势', value: props.strength ?? '' },
1637
+ { key: 'W', subtitle: '劣势', value: props.weakness ?? '' },
1638
+ { key: 'O', subtitle: '机会', value: props.opportunity ?? '' },
1639
+ { key: 'T', subtitle: '威胁', value: props.threat ?? '' },
1640
+ ];
1641
+ const positions = [
1642
+ { x: 0.84375, y: 2.85 },
1643
+ { x: 5.15625, y: 2.85 },
1644
+ { x: 0.84375, y: 4.25 },
1645
+ { x: 5.15625, y: 4.25 },
1646
+ ];
1647
+ cells.forEach((cell, idx) => {
1648
+ const pos = positions[idx];
1649
+ addTheme02Card(slide, pos.x, pos.y, 4.0, 1.25, 0.08);
1650
+ slide.addShape('rect', {
1651
+ x: pos.x, y: pos.y, w: 0.06, h: 1.25,
1652
+ fill: { color: COLORS.accent },
1653
+ });
1654
+ slide.addText(cell.key, {
1655
+ x: pos.x + 0.12, y: pos.y + 0.1, w: 0.35, h: 0.32,
1656
+ fontSize: 18, color: COLORS.accent, bold: true, fontFace: FONTS.mono, align: 'left', valign: 'top',
1657
+ });
1658
+ slide.addText(cell.subtitle, {
1659
+ x: pos.x + 0.5, y: pos.y + 0.12, w: 1.5, h: 0.28,
1660
+ fontSize: 13, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
1661
+ });
1662
+ slide.addText(cell.value, {
1663
+ x: pos.x + 0.12, y: pos.y + 0.48, w: 3.76, h: 0.68,
1664
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1665
+ });
1666
+ });
1667
+ addTheme03Footer(slide, props);
1668
+ }
1669
+ function renderTheme03MetricV1(slide, props) {
1670
+ addTheme03Topbar(slide, props);
1671
+ slide.addText(theme03TitleText(props.title ?? ''), {
1672
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1673
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1674
+ });
1675
+ const value = props.value ?? '';
1676
+ const unit = props.unit ?? '';
1677
+ const valueWidth = value.length > 4 ? 5.0 : 3.5;
1678
+ slide.addText(value, {
1679
+ x: 0.84375, y: 2.2, w: valueWidth, h: 1.1,
1680
+ fontSize: 88, color: COLORS.accent, bold: true, fontFace: FONTS.heading, valign: 'top',
1681
+ });
1682
+ if (unit) {
1683
+ slide.addText(unit, {
1684
+ x: 0.84375 + valueWidth + 0.1, y: 2.8, w: 1.5, h: 0.4,
1685
+ fontSize: 24, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
1686
+ });
1687
+ }
1688
+ if (props.description) {
1689
+ slide.addText(props.description, {
1690
+ x: 0.84375, y: 3.45, w: 6.0, h: 0.7,
1691
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1692
+ });
1693
+ }
1694
+ addTheme03Footer(slide, props);
1695
+ }
1696
+ function renderTheme03MetricV2(slide, props) {
1697
+ addTheme03Topbar(slide, props);
1698
+ slide.addText(theme03TitleText(props.title ?? ''), {
1699
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1700
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1701
+ });
1702
+ if (props.subtitle) {
1703
+ slide.addText(props.subtitle, {
1704
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
1705
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1706
+ });
1707
+ }
1708
+ const metrics = (props.metrics || []).filter((m) => m != null).slice(0, 6);
1709
+ if (metrics.length > 0) {
1710
+ const cols = Math.min(metrics.length, 3);
1711
+ const rows = Math.ceil(metrics.length / cols);
1712
+ const gap = 0.24;
1713
+ const cardW = (8.3125 - (cols - 1) * gap) / cols;
1714
+ const cardH = rows === 1 ? 2.4 : 1.1;
1715
+ const startY = 2.85;
1716
+ metrics.forEach((metric, idx) => {
1717
+ const col = idx % cols;
1718
+ const row = Math.floor(idx / cols);
1719
+ const x = 0.84375 + col * (cardW + gap);
1720
+ const y = startY + row * (cardH + gap);
1721
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
1722
+ slide.addText(`${metric.value ?? ''}${metric.unit ?? ''}`, {
1723
+ x: x + 0.15, y: y + 0.18, w: cardW - 0.3, h: 0.45,
1724
+ fontSize: 28, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
1725
+ });
1726
+ if (metric.change) {
1727
+ slide.addText(metric.change, {
1728
+ x: x + cardW - 0.95, y: y + 0.2, w: 0.8, h: 0.25,
1729
+ fontSize: 10, color: COLORS.accent, bold: true, fontFace: FONTS.mono, align: 'right', valign: 'top',
1730
+ });
1731
+ }
1732
+ slide.addText(metric.label ?? '', {
1733
+ x: x + 0.15, y: y + cardH - 0.45, w: cardW - 0.3, h: 0.3,
1734
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1735
+ });
1736
+ });
1737
+ }
1738
+ addTheme03Footer(slide, props);
1739
+ }
1740
+ function renderTheme03MetricV3(slide, props) {
1741
+ addTheme03Topbar(slide, props);
1742
+ slide.addText(theme03TitleText(props.title ?? ''), {
1743
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1744
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1745
+ });
1746
+ const cardX = 0.84375;
1747
+ const cardW = 8.3125;
1748
+ const cardY = 2.5;
1749
+ const cardH = 2.0;
1750
+ addTheme02Card(slide, cardX, cardY, cardW, cardH, 0.08);
1751
+ slide.addShape('roundRect', {
1752
+ x: cardX + 0.25, y: cardY + 0.45, w: 0.9, h: 0.9,
1753
+ fill: { color: COLORS.accent }, rectRadius: 0.12,
1754
+ });
1755
+ slide.addText(props.icon ?? '01', {
1756
+ x: cardX + 0.25, y: cardY + 0.55, w: 0.9, h: 0.7,
1757
+ fontSize: 28, color: COLORS.white, bold: true, fontFace: FONTS.mono, align: 'center', valign: 'middle',
1758
+ });
1759
+ slide.addText(`${props.value ?? ''}${props.unit ?? ''}`, {
1760
+ x: cardX + 1.4, y: cardY + 0.35, w: 6.0, h: 0.7,
1761
+ fontSize: 54, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
1762
+ });
1763
+ if (props.description) {
1764
+ slide.addText(props.description, {
1765
+ x: cardX + 1.4, y: cardY + 1.1, w: 6.2, h: 0.7,
1766
+ fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1767
+ });
1768
+ }
1769
+ addTheme03Footer(slide, props);
1770
+ }
1771
+ function renderTheme03MetricTriptych(slide, props) {
1772
+ addTheme03Topbar(slide, props);
1773
+ slide.addText(theme03TitleText(props.title ?? ''), {
1774
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1775
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1776
+ });
1777
+ if (props.subtitle) {
1778
+ slide.addText(props.subtitle, {
1779
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
1780
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1781
+ });
1782
+ }
1783
+ const panels = (props.panels || []).filter((p) => p != null).slice(0, 3);
1784
+ if (panels.length > 0) {
1785
+ const count = panels.length;
1786
+ const gap = 0.24;
1787
+ const cardW = (8.3125 - gap * (count - 1)) / count;
1788
+ const cardH = 2.4;
1789
+ const startY = 2.8;
1790
+ panels.forEach((panel, idx) => {
1791
+ const x = 0.84375 + idx * (cardW + gap);
1792
+ addTheme02Card(slide, x, startY, cardW, cardH, 0.08);
1793
+ slide.addText(panel.index ?? String(idx + 1).padStart(2, '0'), {
1794
+ x: x + 0.12, y: startY + 0.12, w: 0.6, h: 0.25,
1795
+ fontSize: 11, color: COLORS.accent, fontFace: FONTS.mono, valign: 'top',
1796
+ });
1797
+ slide.addText(panel.title ?? '', {
1798
+ x: x + 0.75, y: startY + 0.12, w: cardW - 0.9, h: 0.3,
1799
+ fontSize: 14, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
1800
+ });
1801
+ slide.addText(`${panel.value ?? ''}`, {
1802
+ x: x + 0.12, y: startY + 0.55, w: cardW - 0.24, h: 0.55,
1803
+ fontSize: 32, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
1804
+ });
1805
+ const data = (panel.chartData || []).filter((v) => typeof v === 'number').slice(0, 8);
1806
+ if (data.length >= 2) {
1807
+ const chartY = startY + 1.2;
1808
+ const chartH = 0.55;
1809
+ const max = Math.max(...data, 1);
1810
+ const step = (cardW - 0.34) / data.length;
1811
+ const barW = step * 0.6;
1812
+ data.forEach((v, i) => {
1813
+ const h = chartH * (v / max);
1814
+ slide.addShape('roundRect', {
1815
+ x: x + 0.12 + i * step + (step - barW) / 2,
1816
+ y: chartY + chartH - h,
1817
+ w: barW,
1818
+ h: Math.max(h, 0.05),
1819
+ fill: { color: COLORS.accent },
1820
+ rectRadius: 0.02,
1821
+ });
1822
+ });
1823
+ }
1824
+ if (panel.subtitle) {
1825
+ slide.addText(panel.subtitle, {
1826
+ x: x + 0.12, y: startY + cardH - 0.45, w: cardW - 0.24, h: 0.35,
1827
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1828
+ });
1829
+ }
1830
+ });
1831
+ }
1832
+ addTheme03Footer(slide, props);
1833
+ }
1834
+ function renderTheme03ScorecardV1(slide, props) {
1835
+ addTheme03Topbar(slide, props);
1836
+ slide.addText(theme03TitleText(props.title ?? ''), {
1837
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1838
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1839
+ });
1840
+ if (props.subtitle) {
1841
+ slide.addText(props.subtitle, {
1842
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
1843
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1844
+ });
1845
+ }
1846
+ const items = (props.items || []).filter((i) => i != null).slice(0, 8);
1847
+ if (items.length > 0) {
1848
+ const cols = items.length > 4 ? 2 : 1;
1849
+ const gapX = 0.4;
1850
+ const gapY = 0.28;
1851
+ const colW = (8.3125 - gapX * (cols - 1)) / cols;
1852
+ const rows = Math.ceil(items.length / cols);
1853
+ const rowH = (2.6 - gapY * (rows - 1)) / rows;
1854
+ const startY = 2.8;
1855
+ items.forEach((item, idx) => {
1856
+ const col = idx % cols;
1857
+ const row = Math.floor(idx / cols);
1858
+ const x = 0.84375 + col * (colW + gapX);
1859
+ const y = startY + row * (rowH + gapY);
1860
+ const score = Number(item.score) || 0;
1861
+ const max = Number(item.max) || 100;
1862
+ const pct = max > 0 ? Math.min(100, Math.max(0, (score / max) * 100)) : 0;
1863
+ slide.addText(item.label ?? '', {
1864
+ x, y, w: colW - 1.2, h: 0.25,
1865
+ fontSize: 13, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
1866
+ });
1867
+ slide.addText(`${score} / ${max}`, {
1868
+ x: x + colW - 1.1, y, w: 1.1, h: 0.25,
1869
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.mono, align: 'right', valign: 'top',
1870
+ });
1871
+ slide.addShape('roundRect', {
1872
+ x, y: y + 0.32, w: colW, h: 0.08,
1873
+ fill: { color: COLORS.border }, rectRadius: 0.04,
1874
+ });
1875
+ if (pct > 0) {
1876
+ slide.addShape('roundRect', {
1877
+ x, y: y + 0.32, w: colW * (pct / 100), h: 0.08,
1878
+ fill: { color: COLORS.accent }, rectRadius: 0.04,
1879
+ });
1880
+ }
1881
+ if (item.note) {
1882
+ slide.addText(item.note, {
1883
+ x, y: y + 0.5, w: colW, h: rowH - 0.6,
1884
+ fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1885
+ });
1886
+ }
1887
+ });
1888
+ }
1889
+ addTheme03Footer(slide, props);
1890
+ }
1891
+ function renderTheme03AppendixV1(slide, props) {
1892
+ addTheme03Topbar(slide, props);
1893
+ slide.addText(theme03TitleText(props.title ?? ''), {
1894
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1895
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1896
+ });
1897
+ if (props.subtitle) {
1898
+ slide.addText(props.subtitle, {
1899
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
1900
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1901
+ });
1902
+ }
1903
+ const sources = (props.sources || []).filter((s) => s != null).slice(0, 10);
1904
+ if (sources.length > 0) {
1905
+ const startY = 2.85;
1906
+ sources.forEach((source, idx) => {
1907
+ const y = startY + idx * 0.34;
1908
+ slide.addShape('line', {
1909
+ x1: 0.84375, y1: y + 0.32, x2: 9.15625, y2: y + 0.32,
1910
+ line: { color: COLORS.border, width: 1 },
1911
+ });
1912
+ slide.addText(source.label ?? '', {
1913
+ x: 0.84375, y, w: 2.5, h: 0.25,
1914
+ fontSize: 12, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
1915
+ });
1916
+ slide.addText(source.value ?? '', {
1917
+ x: 3.5, y, w: 5.5, h: 0.25,
1918
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1919
+ });
1920
+ });
1921
+ }
1922
+ addTheme03Footer(slide, props);
1923
+ }
1924
+ function renderTheme03CaseStudy(slide, props) {
1925
+ addTheme03Topbar(slide, props);
1926
+ slide.addText(theme03TitleText(props.title ?? ''), {
1927
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1928
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1929
+ });
1930
+ if (props.subtitle) {
1931
+ slide.addText(props.subtitle, {
1932
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.3,
1933
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1934
+ });
1935
+ }
1936
+ const leftW = 5.0;
1937
+ if (props.intro) {
1938
+ slide.addText(props.intro, {
1939
+ x: 0.84375, y: 2.7, w: leftW, h: 0.7,
1940
+ fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1941
+ });
1942
+ }
1943
+ const rounds = (props.rounds || []).filter((r) => r != null && !!(r.date || r.round || r.amount)).slice(0, 6);
1944
+ if (rounds.length > 0) {
1945
+ const startY = 3.55;
1946
+ rounds.forEach((round, idx) => {
1947
+ const y = startY + idx * 0.5;
1948
+ slide.addShape('ellipse', {
1949
+ x: 0.84375, y: y + 0.05, w: 0.12, h: 0.12,
1950
+ fill: { color: COLORS.accent },
1951
+ });
1952
+ slide.addText(round.date ?? '', {
1953
+ x: 1.05, y, w: 1.0, h: 0.2,
1954
+ fontSize: 10, color: COLORS.accent, fontFace: FONTS.mono, valign: 'top',
1955
+ });
1956
+ slide.addText(round.round ?? '', {
1957
+ x: 1.05, y: y + 0.2, w: 1.8, h: 0.22,
1958
+ fontSize: 12, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
1959
+ });
1960
+ slide.addText(round.valuation ?? '', {
1961
+ x: 3.0, y: y + 0.05, w: 1.3, h: 0.2,
1962
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1963
+ });
1964
+ slide.addText(round.amount ?? '', {
1965
+ x: 3.0, y: y + 0.25, w: 1.3, h: 0.22,
1966
+ fontSize: 12, color: COLORS.primary, bold: true, fontFace: FONTS.mono, valign: 'top',
1967
+ });
1968
+ });
1969
+ }
1970
+ if (props.quote) {
1971
+ const cardX = 6.0;
1972
+ const cardY = 2.7;
1973
+ const cardW = 3.15625;
1974
+ const cardH = 1.9;
1975
+ addTheme02Card(slide, cardX, cardY, cardW, cardH, 0.08);
1976
+ slide.addText('“', {
1977
+ x: cardX + 0.15, y: cardY + 0.1, w: 0.5, h: 0.4,
1978
+ fontSize: 36, color: COLORS.accent, fontFace: FONTS.heading, valign: 'top',
1979
+ });
1980
+ slide.addText(props.quote, {
1981
+ x: cardX + 0.25, y: cardY + 0.45, w: cardW - 0.45, h: 1.0,
1982
+ fontSize: 14, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
1983
+ });
1984
+ if (props.quoteAuthor) {
1985
+ slide.addText(`—— ${props.quoteAuthor}`, {
1986
+ x: cardX + 0.25, y: cardY + cardH - 0.4, w: cardW - 0.45, h: 0.25,
1987
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.mono, valign: 'top',
1988
+ });
1989
+ }
1990
+ }
1991
+ addTheme03Footer(slide, props);
1992
+ }
1993
+ function renderTheme03OutlookV1(slide, props) {
1994
+ addTheme03Topbar(slide, props);
1995
+ slide.addText(theme03TitleText(props.title ?? ''), {
1996
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
1997
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
1998
+ });
1999
+ if (props.subtitle) {
2000
+ slide.addText(props.subtitle, {
2001
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
2002
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2003
+ });
2004
+ }
2005
+ const items = (props.items || []).filter((i) => i != null).slice(0, 3);
2006
+ if (items.length > 0) {
2007
+ const cols = items.length;
2008
+ const gap = 0.24;
2009
+ const cardW = (8.3125 - gap * (cols - 1)) / cols;
2010
+ const cardH = 2.3;
2011
+ const startY = 2.85;
2012
+ items.forEach((item, idx) => {
2013
+ const x = 0.84375 + idx * (cardW + gap);
2014
+ addTheme02Card(slide, x, startY, cardW, cardH, 0.08);
2015
+ slide.addShape('roundRect', {
2016
+ x: x + 0.12, y: startY + 0.12, w: 0.4, h: 0.4,
2017
+ fill: { color: COLORS.accent }, rectRadius: 0.2,
2018
+ });
2019
+ slide.addText(String(idx + 1), {
2020
+ x: x + 0.12, y: startY + 0.15, w: 0.4, h: 0.34,
2021
+ fontSize: 14, color: COLORS.white, bold: true, fontFace: FONTS.mono, align: 'center', valign: 'middle',
2022
+ });
2023
+ slide.addText(item.title ?? '', {
2024
+ x: x + 0.62, y: startY + 0.15, w: cardW - 0.8, h: 0.3,
2025
+ fontSize: 15, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
2026
+ });
2027
+ slide.addText(`趋势:${item.trend ?? ''}`, {
2028
+ x: x + 0.12, y: startY + 0.65, w: cardW - 0.24, h: 0.25,
2029
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2030
+ });
2031
+ slide.addText(`行动:${item.action ?? ''}`, {
2032
+ x: x + 0.12, y: startY + 0.95, w: cardW - 0.24, h: 1.0,
2033
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2034
+ });
2035
+ });
2036
+ }
2037
+ addTheme03Footer(slide, props);
2038
+ }
2039
+ function renderTheme03RegionV1(slide, props) {
2040
+ addTheme03Topbar(slide, props);
2041
+ slide.addText(theme03TitleText(props.title ?? ''), {
2042
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2043
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2044
+ });
2045
+ if (props.subtitle) {
2046
+ slide.addText(props.subtitle, {
2047
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
2048
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2049
+ });
2050
+ }
2051
+ const regions = (props.regions || []).filter((r) => r != null).slice(0, 6);
2052
+ if (regions.length > 0) {
2053
+ const cols = Math.min(regions.length, 3);
2054
+ const rows = Math.ceil(regions.length / cols);
2055
+ const gap = 0.24;
2056
+ const cardW = (8.3125 - gap * (cols - 1)) / cols;
2057
+ const cardH = rows === 1 ? 2.3 : 1.05;
2058
+ const startY = 2.85;
2059
+ regions.forEach((region, idx) => {
2060
+ const col = idx % cols;
2061
+ const row = Math.floor(idx / cols);
2062
+ const x = 0.84375 + col * (cardW + gap);
2063
+ const y = startY + row * (cardH + gap);
2064
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
2065
+ slide.addText(region.name ?? '', {
2066
+ x: x + 0.15, y: y + 0.12, w: cardW - 0.55, h: 0.3,
2067
+ fontSize: 15, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
2068
+ });
2069
+ slide.addText(region.change ?? '', {
2070
+ x: x + cardW - 0.9, y: y + 0.14, w: 0.75, h: 0.25,
2071
+ fontSize: 11, color: COLORS.accent, fontFace: FONTS.mono, align: 'right', valign: 'top',
2072
+ });
2073
+ slide.addText(`${region.value ?? ''}`, {
2074
+ x: x + 0.15, y: y + cardH - 0.7, w: cardW - 0.3, h: 0.5,
2075
+ fontSize: 28, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
2076
+ });
2077
+ if (region.note) {
2078
+ slide.addText(region.note, {
2079
+ x: x + 0.15, y: y + cardH - 0.32, w: cardW - 0.3, h: 0.3,
2080
+ fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2081
+ });
2082
+ }
2083
+ });
2084
+ }
2085
+ addTheme03Footer(slide, props);
2086
+ }
2087
+ function renderTheme03RiskV1(slide, props) {
2088
+ addTheme03Topbar(slide, props);
2089
+ slide.addText(theme03TitleText(props.title ?? ''), {
2090
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2091
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2092
+ });
2093
+ if (props.subtitle) {
2094
+ slide.addText(props.subtitle, {
2095
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
2096
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2097
+ });
2098
+ }
2099
+ const items = (props.items || []).filter((i) => i != null).slice(0, 4);
2100
+ if (items.length > 0) {
2101
+ const cols = Math.min(items.length, 2);
2102
+ const rows = Math.ceil(items.length / cols);
2103
+ const gap = 0.24;
2104
+ const cardW = (8.3125 - gap * (cols - 1)) / cols;
2105
+ const cardH = rows === 1 ? 2.3 : 1.05;
2106
+ const startY = 2.85;
2107
+ items.forEach((item, idx) => {
2108
+ const col = idx % cols;
2109
+ const row = Math.floor(idx / cols);
2110
+ const x = 0.84375 + col * (cardW + gap);
2111
+ const y = startY + row * (cardH + gap);
2112
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
2113
+ slide.addShape('rect', {
2114
+ x, y, w: 0.06, h: cardH,
2115
+ fill: { color: COLORS.accent },
2116
+ });
2117
+ slide.addText(String(idx + 1).padStart(2, '0'), {
2118
+ x: x + cardW - 0.55, y: y + 0.1, w: 0.45, h: 0.22,
2119
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.mono, align: 'right', valign: 'top',
2120
+ });
2121
+ slide.addText(item.risk ?? '', {
2122
+ x: x + 0.15, y: y + 0.12, w: cardW - 0.7, h: 0.3,
2123
+ fontSize: 15, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
2124
+ });
2125
+ slide.addText(`影响:${item.impact ?? ''}`, {
2126
+ x: x + 0.15, y: y + 0.5, w: cardW - 0.3, h: 0.22,
2127
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2128
+ });
2129
+ slide.addText(`应对:${item.response ?? ''}`, {
2130
+ x: x + 0.15, y: y + 0.76, w: cardW - 0.3, h: cardH - 1.0,
2131
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2132
+ });
2133
+ });
2134
+ }
2135
+ addTheme03Footer(slide, props);
2136
+ }
2137
+ function renderTheme03SpotlightGrid(slide, props) {
2138
+ addTheme03Topbar(slide, props);
2139
+ slide.addText(theme03TitleText(props.title ?? ''), {
2140
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2141
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2142
+ });
2143
+ if (props.subtitle) {
2144
+ slide.addText(props.subtitle, {
2145
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
2146
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2147
+ });
2148
+ }
2149
+ const columns = (props.columns || []).filter((c) => c != null).slice(0, 4);
2150
+ if (columns.length > 0) {
2151
+ const cols = columns.length <= 2 ? columns.length : 2;
2152
+ const rows = Math.ceil(columns.length / cols);
2153
+ const gapX = 0.24;
2154
+ const gapY = 0.24;
2155
+ const cardW = (8.3125 - gapX * (cols - 1)) / cols;
2156
+ const cardH = rows === 1 ? 2.6 : 1.25;
2157
+ const startY = 2.85;
2158
+ columns.forEach((col, idx) => {
2159
+ const c = idx % cols;
2160
+ const r = Math.floor(idx / cols);
2161
+ const x = 0.84375 + c * (cardW + gapX);
2162
+ const y = startY + r * (cardH + gapY);
2163
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
2164
+ if (col.tag) {
2165
+ slide.addText(col.tag, {
2166
+ x: x + 0.12, y: y + 0.1, w: cardW - 0.24, h: 0.22,
2167
+ fontSize: 10, color: COLORS.accent, bold: true, fontFace: FONTS.mono, valign: 'top',
2168
+ });
2169
+ }
2170
+ slide.addShape('roundRect', {
2171
+ x: x + 0.12,
2172
+ y: y + 0.36,
2173
+ w: cardW - 0.24,
2174
+ h: cardH - 0.9,
2175
+ fill: { color: COLORS.surface },
2176
+ line: { color: COLORS.border, width: 1 },
2177
+ rectRadius: 0.06,
2178
+ });
2179
+ slide.addText('(图片)', {
2180
+ x: x + 0.12,
2181
+ y: y + 0.36 + (cardH - 0.9) / 2 - 0.12,
2182
+ w: cardW - 0.24,
2183
+ h: 0.24,
2184
+ fontSize: 11, color: COLORS.secondary, align: 'center', fontFace: FONTS.mono, valign: 'middle',
2185
+ });
2186
+ slide.addText(col.title ?? '', {
2187
+ x: x + 0.12, y: y + cardH - 0.45, w: cardW - 0.24, h: 0.25,
2188
+ fontSize: 13, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
2189
+ });
2190
+ });
2191
+ }
2192
+ addTheme03Footer(slide, props);
2193
+ }
2194
+ function renderTheme03ConclusionV1(slide, props) {
2195
+ addTheme03Topbar(slide, props);
2196
+ slide.addText(theme03TitleText(props.title ?? ''), {
2197
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2198
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2199
+ });
2200
+ if (props.subtitle) {
2201
+ slide.addText(props.subtitle, {
2202
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
2203
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2204
+ });
2205
+ }
2206
+ const points = (props.points || [])
2207
+ .map((p) => typeof p === 'string' ? p : p?.item)
2208
+ .filter(Boolean)
2209
+ .slice(0, 4);
2210
+ if (points.length === 0) {
2211
+ addTheme03Footer(slide, props);
2212
+ return;
2213
+ }
2214
+ const count = points.length;
2215
+ const gap = 0.22;
2216
+ const cardW = (8.3125 - gap * (count - 1)) / count;
2217
+ const startX = 0.84375;
2218
+ const y = 2.85;
2219
+ const h = 2.55;
2220
+ points.forEach((point, idx) => {
2221
+ const x = startX + idx * (cardW + gap);
2222
+ addTheme02Card(slide, x, y, cardW, h, 0.08);
2223
+ slide.addShape('roundRect', {
2224
+ x, y, w: cardW, h: 0.08,
2225
+ fill: { color: COLORS.accent }, rectRadius: 0.04,
2226
+ });
2227
+ slide.addText(String(idx + 1).padStart(2, '0'), {
2228
+ x: x + 0.12, y: y + 0.14, w: 0.6, h: 0.32,
2229
+ fontSize: 18, color: COLORS.accent, bold: true, fontFace: FONTS.mono, valign: 'top',
2230
+ });
2231
+ slide.addText(point, {
2232
+ x: x + 0.12, y: y + 0.58, w: cardW - 0.24, h: h - 0.74,
2233
+ fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2234
+ });
2235
+ });
2236
+ addTheme03Footer(slide, props);
2237
+ }
2238
+ function renderTheme03DiptychContrast(slide, props) {
2239
+ addTheme03Topbar(slide, props);
2240
+ slide.addText(theme03TitleText(props.title ?? ''), {
2241
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2242
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2243
+ });
2244
+ if (props.subtitle) {
2245
+ slide.addText(props.subtitle, {
2246
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
2247
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2248
+ });
2249
+ }
2250
+ const left = props.left || {};
2251
+ const right = props.right || {};
2252
+ const centerCard = props.centerCard || {};
2253
+ const comparisons = (centerCard.comparisons || []).slice(0, 3);
2254
+ // 左右背景区
2255
+ const sideY = 2.85;
2256
+ const sideW = 2.5;
2257
+ const sideH = 2.55;
2258
+ addTheme02Card(slide, 0.84375, sideY, sideW, sideH, 0.08);
2259
+ addTheme02Card(slide, 6.65625, sideY, sideW, sideH, 0.08);
2260
+ if (left.imageUrl) {
2261
+ try {
2262
+ slide.addImage({ path: left.imageUrl, x: 0.94375, y: sideY + 0.1, w: sideW - 0.2, h: sideH - 0.9 });
2263
+ }
2264
+ catch {
2265
+ // ignore
2266
+ }
2267
+ }
2268
+ if (right.imageUrl) {
2269
+ try {
2270
+ slide.addImage({ path: right.imageUrl, x: 6.75625, y: sideY + 0.1, w: sideW - 0.2, h: sideH - 0.9 });
2271
+ }
2272
+ catch {
2273
+ // ignore
2274
+ }
2275
+ }
2276
+ slide.addText(left.labelEn || '', {
2277
+ x: 0.84375, y: sideY + 0.12, w: sideW, h: 0.22,
2278
+ fontSize: 10, color: COLORS.secondary, align: 'center', valign: 'top', fontFace: FONTS.mono,
2279
+ });
2280
+ slide.addText(left.label || '', {
2281
+ x: 0.84375, y: sideY + sideH - 0.6, w: sideW, h: 0.45,
2282
+ fontSize: 18, color: COLORS.primary, bold: true, align: 'center', valign: 'middle', fontFace: FONTS.heading,
2283
+ });
2284
+ slide.addText(right.labelEn || '', {
2285
+ x: 6.65625, y: sideY + 0.12, w: sideW, h: 0.22,
2286
+ fontSize: 10, color: COLORS.secondary, align: 'center', valign: 'top', fontFace: FONTS.mono,
2287
+ });
2288
+ slide.addText(right.label || '', {
2289
+ x: 6.65625, y: sideY + sideH - 0.6, w: sideW, h: 0.45,
2290
+ fontSize: 18, color: COLORS.primary, bold: true, align: 'center', valign: 'middle', fontFace: FONTS.heading,
2291
+ });
2292
+ // 中央结论卡
2293
+ const centerX = 3.59375;
2294
+ const centerW = 2.8125;
2295
+ const centerH = 2.55;
2296
+ addTheme02Card(slide, centerX, sideY, centerW, centerH, 0.08);
2297
+ if (centerCard.title) {
2298
+ slide.addText(centerCard.title, {
2299
+ x: centerX + 0.12, y: sideY + 0.14, w: centerW - 0.24, h: 0.32,
2300
+ fontSize: 14, color: COLORS.primary, bold: true, align: 'center', valign: 'top', fontFace: FONTS.heading,
2301
+ });
2302
+ }
2303
+ comparisons.forEach((item, index) => {
2304
+ const y = sideY + 0.55 + index * 0.58;
2305
+ slide.addText(item.leftValue || '', {
2306
+ x: centerX + 0.12, y, w: 0.9, h: 0.22,
2307
+ fontSize: 13, color: COLORS.secondary, bold: true, align: 'right', valign: 'middle', fontFace: FONTS.heading,
2308
+ });
2309
+ slide.addText(item.leftLabel || '', {
2310
+ x: centerX + 0.12, y: y + 0.2, w: 0.9, h: 0.16,
2311
+ fontSize: 9, color: COLORS.secondary, align: 'right', valign: 'middle', fontFace: FONTS.body,
2312
+ });
2313
+ slide.addText('VS', {
2314
+ x: centerX + centerW / 2 - 0.25, y, w: 0.5, h: 0.22,
2315
+ fontSize: 10, color: COLORS.accent, bold: true, align: 'center', valign: 'middle', fontFace: FONTS.mono,
2316
+ });
2317
+ slide.addText(item.rightValue || '', {
2318
+ x: centerX + centerW - 1.02, y, w: 0.9, h: 0.22,
2319
+ fontSize: 13, color: COLORS.accent, bold: true, align: 'left', valign: 'middle', fontFace: FONTS.heading,
2320
+ });
2321
+ slide.addText(item.rightLabel || '', {
2322
+ x: centerX + centerW - 1.02, y: y + 0.2, w: 0.9, h: 0.16,
2323
+ fontSize: 9, color: COLORS.secondary, align: 'left', valign: 'middle', fontFace: FONTS.body,
2324
+ });
2325
+ });
2326
+ if (centerCard.conclusion) {
2327
+ slide.addText(centerCard.conclusion, {
2328
+ x: centerX + 0.12, y: sideY + centerH - 0.55, w: centerW - 0.24, h: 0.45,
2329
+ fontSize: 11, color: COLORS.secondary, align: 'center', valign: 'top', fontFace: FONTS.body,
2330
+ });
2331
+ }
2332
+ addTheme03Footer(slide, props);
2333
+ }
2334
+ function renderTheme03FilmstripV1(slide, props) {
2335
+ addTheme03Topbar(slide, props);
2336
+ slide.addText(theme03TitleText(props.title ?? ''), {
2337
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2338
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2339
+ });
2340
+ if (props.subtitle) {
2341
+ slide.addText(props.subtitle, {
2342
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
2343
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2344
+ });
2345
+ }
2346
+ const images = (props.images || []).slice(0, 5);
2347
+ if (images.length === 0) {
2348
+ addTheme03Footer(slide, props);
2349
+ return;
2350
+ }
2351
+ const gap = 0.2;
2352
+ const frameW = (8.3125 - gap * (images.length - 1)) / images.length;
2353
+ const startX = 0.84375;
2354
+ const y = 2.85;
2355
+ const imgH = 2.0;
2356
+ images.forEach((image, index) => {
2357
+ const x = startX + index * (frameW + gap);
2358
+ addTheme02Card(slide, x, y, frameW, imgH + 0.5, 0.08);
2359
+ slide.addShape('roundRect', {
2360
+ x, y, w: frameW, h: 0.08,
2361
+ fill: { color: COLORS.accent }, rectRadius: 0.04,
2362
+ });
2363
+ slide.addText(String(index + 1).padStart(2, '0'), {
2364
+ x: x + frameW - 0.55, y: y + 0.1, w: 0.45, h: 0.22,
2365
+ fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, align: 'right', valign: 'top',
2366
+ });
2367
+ if (image.url) {
2368
+ try {
2369
+ slide.addImage({ path: image.url, x: x + 0.08, y: y + 0.22, w: frameW - 0.16, h: imgH - 0.22 });
2370
+ }
2371
+ catch {
2372
+ slide.addText('(图片)', {
2373
+ x: x + 0.08, y: y + imgH / 2, w: frameW - 0.16, h: 0.3,
2374
+ fontSize: 12, color: COLORS.secondary, align: 'center', fontFace: FONTS.body,
2375
+ });
2376
+ }
2377
+ }
2378
+ if (image.caption) {
2379
+ slide.addText(image.caption, {
2380
+ x, y: y + imgH + 0.08, w: frameW, h: 0.3,
2381
+ fontSize: 10, color: COLORS.secondary, align: 'center', valign: 'middle', fontFace: FONTS.mono,
2382
+ });
2383
+ }
2384
+ });
2385
+ addTheme03Footer(slide, props);
2386
+ }
2387
+ function renderTheme03GanttV1(slide, props) {
2388
+ addTheme03Topbar(slide, props);
2389
+ slide.addText(theme03TitleText(props.title ?? ''), {
2390
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2391
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2392
+ });
2393
+ if (props.subtitle) {
2394
+ slide.addText(props.subtitle, {
2395
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
2396
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2397
+ });
2398
+ }
2399
+ const tasks = (props.tasks || []).slice(0, 8);
2400
+ const phases = (props.phases?.length ? props.phases : ['Q1', 'Q2', 'Q3', 'Q4']).slice(0, 6);
2401
+ const chartColors = ['00B4FF', 'FF2A6D', '8B5CF6', '8A8F99', '22D3EE', 'FF9F1C', 'FF7B54', '6B21A8'];
2402
+ const cardY = 2.85;
2403
+ const cardH = 2.55;
2404
+ addTheme02Card(slide, 0.84375, cardY, 8.3125, cardH, 0.08);
2405
+ if (tasks.length === 0) {
2406
+ addTheme03Footer(slide, props);
2407
+ return;
2408
+ }
2409
+ const startX = 0.84375;
2410
+ const endX = 9.15625;
2411
+ const totalW = endX - startX;
2412
+ const headerY = cardY + 0.2;
2413
+ const bodyY = cardY + 0.65;
2414
+ const rowH = (cardH - 0.85) / Math.max(tasks.length, 1);
2415
+ const phaseW = totalW / phases.length;
2416
+ phases.forEach((phase, i) => {
2417
+ const x = startX + i * phaseW;
2418
+ slide.addText(phase, {
2419
+ x, y: headerY, w: phaseW, h: 0.3,
2420
+ fontSize: 11, color: COLORS.secondary, align: 'center', valign: 'middle', fontFace: FONTS.mono,
2421
+ });
2422
+ if (i > 0) {
2423
+ slide.addShape('line', {
2424
+ x1: x, y1: headerY, x2: x, y2: cardY + cardH - 0.1,
2425
+ line: { color: COLORS.border, width: 1 },
2426
+ });
2427
+ }
2428
+ });
2429
+ tasks.forEach((task, index) => {
2430
+ const y = bodyY + index * rowH;
2431
+ const start = Math.max(0, Math.min(100, task.start ?? 0));
2432
+ const end = Math.max(start, Math.min(100, task.end ?? 100));
2433
+ const barX = startX + (start / 100) * totalW;
2434
+ const barW = ((end - start) / 100) * totalW;
2435
+ const color = task.color ? task.color.replace('#', '') : chartColors[index % chartColors.length];
2436
+ slide.addText(task.name || '', {
2437
+ x: startX + 0.1, y, w: 1.8, h: rowH,
2438
+ fontSize: 11, color: COLORS.primary, align: 'left', valign: 'middle', fontFace: FONTS.body,
2439
+ });
2440
+ slide.addShape('rect', {
2441
+ x: Math.max(startX + 2.0, barX), y: y + rowH * 0.25, w: Math.max(0.04, barW), h: rowH * 0.5,
2442
+ fill: { color }, rectRadius: 0.04,
2443
+ });
2444
+ });
2445
+ addTheme03Footer(slide, props);
2446
+ }
2447
+ function renderTheme03PestV1(slide, props) {
2448
+ addTheme03Topbar(slide, props);
2449
+ slide.addText(theme03TitleText(props.title ?? ''), {
2450
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2451
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2452
+ });
2453
+ if (props.subtitle) {
2454
+ slide.addText(props.subtitle, {
2455
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
2456
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2457
+ });
2458
+ }
2459
+ const cells = [
2460
+ { key: 'P', subtitle: '政治环境', value: props.political ?? '', color: COLORS.accent },
2461
+ { key: 'E', subtitle: '经济环境', value: props.economic ?? '', color: COLORS.secondary },
2462
+ { key: 'S', subtitle: '社会环境', value: props.social ?? '', color: '8B5CF6' },
2463
+ { key: 'T', subtitle: '技术环境', value: props.technological ?? '', color: '8A8F99' },
2464
+ ];
2465
+ const positions = [
2466
+ { x: 0.84375, y: 2.85 },
2467
+ { x: 5.15625, y: 2.85 },
2468
+ { x: 0.84375, y: 4.25 },
2469
+ { x: 5.15625, y: 4.25 },
2470
+ ];
2471
+ cells.forEach((cell, idx) => {
2472
+ const pos = positions[idx];
2473
+ addTheme02Card(slide, pos.x, pos.y, 4.0, 1.25, 0.08);
2474
+ slide.addShape('rect', {
2475
+ x: pos.x, y: pos.y, w: 0.06, h: 1.25,
2476
+ fill: { color: cell.color },
2477
+ });
2478
+ slide.addText(cell.key, {
2479
+ x: pos.x + 0.12, y: pos.y + 0.1, w: 0.35, h: 0.32,
2480
+ fontSize: 18, color: cell.color, bold: true, fontFace: FONTS.mono, align: 'left', valign: 'top',
2481
+ });
2482
+ slide.addText(cell.subtitle, {
2483
+ x: pos.x + 0.5, y: pos.y + 0.12, w: 1.5, h: 0.28,
2484
+ fontSize: 13, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
2485
+ });
2486
+ slide.addText(cell.value, {
2487
+ x: pos.x + 0.12, y: pos.y + 0.48, w: 3.76, h: 0.68,
2488
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2489
+ });
2490
+ });
2491
+ addTheme03Footer(slide, props);
2492
+ }
2493
+ function renderTheme03StatsV1(slide, props) {
2494
+ addTheme03Topbar(slide, props);
2495
+ slide.addText(theme03TitleText(props.title ?? ''), {
2496
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2497
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2498
+ });
2499
+ if (props.subtitle) {
2500
+ slide.addText(props.subtitle, {
2501
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
2502
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2503
+ });
2504
+ }
2505
+ const stats = (props.stats || []).filter((s) => s != null).slice(0, 6);
2506
+ if (stats.length === 0) {
2507
+ addTheme03Footer(slide, props);
2508
+ return;
2509
+ }
2510
+ const cols = Math.min(stats.length, 3);
2511
+ const rows = Math.ceil(stats.length / cols);
2512
+ const gap = 0.24;
2513
+ const cardW = (8.3125 - (cols - 1) * gap) / cols;
2514
+ const cardH = rows === 1 ? 2.55 : 1.15;
2515
+ const startY = 2.85;
2516
+ stats.forEach((stat, idx) => {
2517
+ const col = idx % cols;
2518
+ const row = Math.floor(idx / cols);
2519
+ const x = 0.84375 + col * (cardW + gap);
2520
+ const y = startY + row * (cardH + gap);
2521
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
2522
+ slide.addShape('roundRect', {
2523
+ x, y, w: cardW, h: 0.08,
2524
+ fill: { color: COLORS.accent }, rectRadius: 0.04,
2525
+ });
2526
+ slide.addText(`${stat.value ?? ''}${stat.unit ?? ''}`, {
2527
+ x: x + 0.12, y: y + 0.14, w: cardW - 0.24, h: 0.45,
2528
+ fontSize: 26, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
2529
+ });
2530
+ if (stat.change) {
2531
+ slide.addText(stat.change, {
2532
+ x: x + cardW - 0.95, y: y + 0.16, w: 0.8, h: 0.25,
2533
+ fontSize: 10, color: COLORS.accent, bold: true, fontFace: FONTS.mono, align: 'right', valign: 'top',
2534
+ });
2535
+ }
2536
+ slide.addText(stat.label ?? '', {
2537
+ x: x + 0.12, y: y + cardH - 0.42, w: cardW - 0.24, h: 0.28,
2538
+ fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2539
+ });
2540
+ });
2541
+ addTheme03Footer(slide, props);
2542
+ }
2543
+ function renderTheme03TableData(slide, props) {
2544
+ addTheme03Topbar(slide, props);
2545
+ slide.addText(theme03TitleText(props.title ?? ''), {
2546
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2547
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2548
+ });
2549
+ if (props.subtitle) {
2550
+ slide.addText(props.subtitle, {
2551
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
2552
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2553
+ });
2554
+ }
2555
+ const columns = (props.columns || []).filter((c) => c.key && c.label);
2556
+ const rows = (props.rows || []).filter((r) => r != null).slice(0, 12);
2557
+ const effectiveHighlightRow = props.highlightRow ?? (Array.isArray(props.highlightRows) && props.highlightRows.length ? props.highlightRows[0] : undefined);
2558
+ const tableY = 2.85;
2559
+ const tableH = 2.55;
2560
+ addTheme02Card(slide, 0.84375, tableY, 8.3125, tableH, 0.08);
2561
+ if (columns.length === 0 || rows.length === 0) {
2562
+ addTheme03Footer(slide, props);
2563
+ return;
2564
+ }
2565
+ const tableW = 8.3125;
2566
+ const startX = 0.84375;
2567
+ const headerH = 0.42;
2568
+ const rowH = (tableH - headerH) / Math.max(rows.length, 1);
2569
+ const colW = tableW / columns.length;
2570
+ columns.forEach((column, index) => {
2571
+ const x = startX + index * colW;
2572
+ slide.addText(column.label || '', {
2573
+ x: x + 0.1, y: tableY, w: colW - 0.2, h: headerH,
2574
+ fontSize: 12, color: COLORS.accent, bold: true, fontFace: FONTS.mono, align: column.align || 'left', valign: 'middle',
2575
+ });
2576
+ });
2577
+ rows.forEach((row, rowIndex) => {
2578
+ const y = tableY + headerH + rowIndex * rowH;
2579
+ const isHighlight = effectiveHighlightRow === rowIndex;
2580
+ columns.forEach((column, colIndex) => {
2581
+ const x = startX + colIndex * colW;
2582
+ slide.addText(row[column.key || ''] ?? '', {
2583
+ x: x + 0.1, y, w: colW - 0.2, h: rowH,
2584
+ fontSize: 11, color: isHighlight ? COLORS.accent : COLORS.secondary,
2585
+ bold: isHighlight,
2586
+ align: column.align || 'left', valign: 'middle', fontFace: FONTS.body,
2587
+ });
2588
+ });
2589
+ });
2590
+ addTheme03Footer(slide, props);
2591
+ }
2592
+ function renderTheme03ChapterV2(slide, props) {
2593
+ addTheme03Topbar(slide, props);
2594
+ slide.addText(props.number ?? String(props._slideIdx ?? 1).padStart(2, '0'), {
2595
+ x: 0.84375, y: 1.55, w: 2.6, h: 1.5,
2596
+ fontSize: 120, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
2597
+ valign: 'top',
2598
+ });
2599
+ if (props.kicker) {
2600
+ slide.addText(props.kicker, {
2601
+ x: 3.6, y: 1.7, w: 5.5, h: 0.25,
2602
+ fontSize: 12, color: COLORS.accent, fontFace: FONTS.mono,
2603
+ });
2604
+ }
2605
+ slide.addText(theme03TitleText(props.title ?? ''), {
2606
+ x: 3.6, y: props.kicker ? 2.0 : 1.7, w: 5.5, h: 1.0,
2607
+ fontSize: 46, fontFace: FONTS.heading, valign: 'top',
2608
+ });
2609
+ if (props.subtitle) {
2610
+ slide.addText(props.subtitle, {
2611
+ x: 3.6, y: 3.05, w: 5.0, h: 0.7,
2612
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2613
+ });
2614
+ }
2615
+ addTheme03Footer(slide, props);
2616
+ }
2617
+ function renderTheme03ChapterV3(slide, props) {
2618
+ addTheme03Topbar(slide, props);
2619
+ if (props.imageUrl) {
2620
+ addImageMaybe(slide, props.imageUrl, 0, 0, 10, 5.625);
2621
+ }
2622
+ slide.addShape('rect', {
2623
+ x: 0, y: 0, w: 10, h: 5.625,
2624
+ fill: { color: '05080D', transparency: 55 },
2625
+ });
2626
+ const cardW = 6.5;
2627
+ const cardH = 2.4;
2628
+ const cardX = 0.84375;
2629
+ const cardY = 2.4;
2630
+ addTheme02Card(slide, cardX, cardY, cardW, cardH, 0.12);
2631
+ slide.addShape('line', {
2632
+ x1: cardX, y1: cardY, x2: cardX, y2: cardY + cardH,
2633
+ line: { color: COLORS.accent, width: 3 },
2634
+ });
2635
+ if (props.kicker) {
2636
+ slide.addText(props.kicker, {
2637
+ x: cardX + 0.25, y: cardY + 0.25, w: cardW - 0.5, h: 0.22,
2638
+ fontSize: 11, color: COLORS.accent, fontFace: FONTS.mono,
2639
+ });
2640
+ }
2641
+ slide.addText(theme03TitleText(props.title ?? ''), {
2642
+ x: cardX + 0.25, y: cardY + (props.kicker ? 0.55 : 0.25), w: cardW - 0.5, h: 0.9,
2643
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2644
+ });
2645
+ if (props.subtitle) {
2646
+ slide.addText(props.subtitle, {
2647
+ x: cardX + 0.25, y: cardY + 1.55, w: cardW - 0.5, h: 0.6,
2648
+ fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2649
+ });
2650
+ }
2651
+ addTheme03Footer(slide, props);
2652
+ }
2653
+ function renderTheme03ClosingV2(slide, props) {
2654
+ addTheme03Topbar(slide, props);
2655
+ const centerX = 5.0;
2656
+ const cardW = 6.5;
2657
+ const cardH = 3.2;
2658
+ const cardX = centerX - cardW / 2;
2659
+ const cardY = 1.4;
2660
+ addTheme02Card(slide, cardX, cardY, cardW, cardH, 0.12);
2661
+ slide.addShape('line', {
2662
+ x1: cardX, y1: cardY, x2: cardX, y2: cardY + cardH,
2663
+ line: { color: COLORS.accent, width: 3 },
2664
+ });
2665
+ if (props.kicker) {
2666
+ slide.addText(props.kicker, {
2667
+ x: cardX + 0.3, y: cardY + 0.3, w: cardW - 0.6, h: 0.22,
2668
+ fontSize: 11, color: COLORS.accent, fontFace: FONTS.mono, align: 'center',
2669
+ });
2670
+ }
2671
+ slide.addText(theme03TitleText(props.title ?? '感谢阅读'), {
2672
+ x: cardX + 0.3, y: cardY + (props.kicker ? 0.65 : 0.3), w: cardW - 0.6, h: 0.9,
2673
+ fontSize: 48, fontFace: FONTS.heading, align: 'center', valign: 'top',
2674
+ });
2675
+ if (props.subtitle) {
2676
+ slide.addText(props.subtitle, {
2677
+ x: cardX + 0.5, y: cardY + 1.6, w: cardW - 1.0, h: 0.5,
2678
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, align: 'center', valign: 'top',
2679
+ });
2680
+ }
2681
+ if (props.cta) {
2682
+ slide.addText(props.cta, {
2683
+ x: cardX + 2.0, y: cardY + 2.2, w: 2.5, h: 0.35,
2684
+ fontSize: 13, color: COLORS.white, fill: { color: COLORS.accent },
2685
+ align: 'center', valign: 'middle', fontFace: FONTS.body,
2686
+ });
2687
+ }
2688
+ const contacts = [props.contact, props.email, props.link].filter(Boolean).join(' · ');
2689
+ if (contacts) {
2690
+ slide.addText(contacts, {
2691
+ x: cardX + 0.3, y: cardY + 2.75, w: cardW - 0.6, h: 0.22,
2692
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center', valign: 'top',
2693
+ });
2694
+ }
2695
+ addTheme03Footer(slide, props);
2696
+ }
2697
+ function renderTheme03ComparisonV2(slide, props) {
2698
+ addTheme03Topbar(slide, props);
2699
+ slide.addText(theme03TitleText(props.title ?? ''), {
2700
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2701
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2702
+ });
2703
+ if (props.subtitle) {
2704
+ slide.addText(props.subtitle, {
2705
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
2706
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2707
+ });
2708
+ }
2709
+ const cards = (props.cards || []).slice(0, 4);
2710
+ if (cards.length > 0) {
2711
+ const cols = cards.length <= 2 ? 2 : Math.min(cards.length, 4);
2712
+ const rows = Math.ceil(cards.length / cols);
2713
+ const gap = 0.25;
2714
+ const cardW = (8.3125 - (cols - 1) * gap) / cols;
2715
+ const cardH = rows === 1 ? 2.4 : 1.5;
2716
+ const startY = 2.85;
2717
+ cards.forEach((card, idx) => {
2718
+ const col = idx % cols;
2719
+ const row = Math.floor(idx / cols);
2720
+ const x = 0.84375 + col * (cardW + gap);
2721
+ const y = startY + row * (cardH + gap);
2722
+ addTheme02Card(slide, x, y, cardW, cardH, 0.08);
2723
+ slide.addShape('roundRect', {
2724
+ x, y, w: cardW, h: 0.08,
2725
+ fill: { color: COLORS.accent }, rectRadius: 0.04,
2726
+ });
2727
+ slide.addText(card.label ?? '', {
2728
+ x: x + 0.15, y: y + 0.18, w: cardW - 0.3, h: 0.3,
2729
+ fontSize: 14, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
2730
+ });
2731
+ const score = typeof card.score === 'number' ? card.score : Number(card.score) || 0;
2732
+ const max = (typeof card.max === 'number' && card.max > 0) ? card.max : Number(card.max) || 100;
2733
+ slide.addText(`${score} / ${max}`, {
2734
+ x: x + 0.15, y: y + 0.55, w: cardW - 0.3, h: 0.35,
2735
+ fontSize: 28, color: COLORS.accent, bold: true, fontFace: FONTS.heading, valign: 'top',
2736
+ });
2737
+ const pct = Math.min(100, Math.max(0, (score / max) * 100));
2738
+ addTheme02Card(slide, x + 0.15, y + 0.98, cardW - 0.3, 0.1, 0.05);
2739
+ slide.addShape('roundRect', {
2740
+ x: x + 0.15, y: y + 0.98, w: (cardW - 0.3) * (pct / 100), h: 0.1,
2741
+ fill: { color: COLORS.accent }, rectRadius: 0.05,
2742
+ });
2743
+ if (card.note) {
2744
+ slide.addText(card.note, {
2745
+ x: x + 0.15, y: y + 1.2, w: cardW - 0.3, h: cardH - 1.35,
2746
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2747
+ });
2748
+ }
2749
+ });
2750
+ }
2751
+ addTheme03Footer(slide, props);
2752
+ }
2753
+ function renderTheme03ComparisonV3(slide, props) {
2754
+ addTheme03Topbar(slide, props);
2755
+ slide.addText(theme03TitleText(props.title ?? ''), {
2756
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2757
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2758
+ });
2759
+ if (props.subtitle) {
2760
+ slide.addText(props.subtitle, {
2761
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
2762
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2763
+ });
2764
+ }
2765
+ const rows = (props.rows || []).slice(0, 6);
2766
+ if (rows.length > 0) {
2767
+ const tableY = 2.85;
2768
+ const tableH = 2.5;
2769
+ const tableW = 8.3125;
2770
+ const rowH = tableH / (rows.length + 1);
2771
+ const colW = tableW / 3;
2772
+ addTheme02Card(slide, 0.84375, tableY, tableW, tableH, 0.08);
2773
+ const headers = ['维度', props.leftTitle ?? '方案 A', props.rightTitle ?? '方案 B'];
2774
+ headers.forEach((header, idx) => {
2775
+ slide.addText(header, {
2776
+ x: 0.84375 + idx * colW + 0.1, y: tableY, w: colW - 0.2, h: rowH,
2777
+ fontSize: 12, color: COLORS.accent, bold: true, fontFace: FONTS.mono, align: idx === 0 ? 'left' : 'center', valign: 'middle',
2778
+ });
2779
+ });
2780
+ rows.forEach((row, rowIndex) => {
2781
+ const y = tableY + (rowIndex + 1) * rowH;
2782
+ [row.feature, row.left, row.right].forEach((cell, idx) => {
2783
+ slide.addText(cell ?? '', {
2784
+ x: 0.84375 + idx * colW + 0.1, y, w: colW - 0.2, h: rowH,
2785
+ fontSize: 12, color: idx === 0 ? COLORS.primary : COLORS.secondary,
2786
+ bold: idx === 0,
2787
+ align: idx === 0 ? 'left' : 'center', valign: 'middle', fontFace: FONTS.body,
2788
+ });
2789
+ });
2790
+ });
2791
+ }
2792
+ addTheme03Footer(slide, props);
2793
+ }
2794
+ function renderTheme03ContentV2(slide, props) {
2795
+ addTheme03Topbar(slide, props);
2796
+ slide.addText(theme03TitleText(props.title ?? ''), {
2797
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2798
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2799
+ });
2800
+ if (props.subtitle) {
2801
+ slide.addText(props.subtitle, {
2802
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
2803
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2804
+ });
2805
+ }
2806
+ const left = (props.leftPoints || []).slice(0, 6);
2807
+ const right = (props.rightPoints || []).slice(0, 6);
2808
+ const colW = 3.9;
2809
+ const colH = 2.5;
2810
+ const startY = 2.85;
2811
+ const gap = 0.5;
2812
+ [left, right].forEach((points, colIdx) => {
2813
+ const x = 0.84375 + colIdx * (colW + gap);
2814
+ addTheme02Card(slide, x, startY, colW, colH, 0.08);
2815
+ slide.addText(points.map((p) => `• ${p}`).join('\n'), {
2816
+ x: x + 0.2, y: startY + 0.2, w: colW - 0.4, h: colH - 0.4,
2817
+ fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2818
+ });
2819
+ });
2820
+ addTheme03Footer(slide, props);
2821
+ }
2822
+ function renderTheme03ContentV3(slide, props) {
2823
+ addTheme03Topbar(slide, props);
2824
+ slide.addText(theme03TitleText(props.title ?? ''), {
2825
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
2826
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
2827
+ });
2828
+ if (props.subtitle) {
2829
+ slide.addText(props.subtitle, {
2830
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
2831
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2832
+ });
2833
+ }
2834
+ const columns = (props.columns || []).slice(0, 3);
2835
+ if (columns.length > 0) {
2836
+ const colW = (8.3125 - (columns.length - 1) * 0.25) / columns.length;
2837
+ const startY = 2.85;
2838
+ columns.forEach((col, idx) => {
2839
+ const x = 0.84375 + idx * (colW + 0.25);
2840
+ addTheme02Card(slide, x, startY, colW, 2.4, 0.08);
2841
+ slide.addShape('roundRect', {
2842
+ x, y: startY, w: colW, h: 0.08,
2843
+ fill: { color: COLORS.accent }, rectRadius: 0.04,
2844
+ });
2845
+ slide.addText(col.title ?? '', {
2846
+ x: x + 0.15, y: startY + 0.2, w: colW - 0.3, h: 0.35,
2847
+ fontSize: 16, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
2848
+ });
2849
+ if (col.text) {
2850
+ slide.addText(col.text, {
2851
+ x: x + 0.15, y: startY + 0.65, w: colW - 0.3, h: 1.6,
2852
+ fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2853
+ });
2854
+ }
2855
+ });
2856
+ }
2857
+ addTheme03Footer(slide, props);
2858
+ }
2859
+ function renderTheme03ContentV4(slide, props) {
2860
+ addTheme03Topbar(slide, props);
2861
+ const cardW = 8.3125;
2862
+ const cardH = 3.0;
2863
+ const cardX = 0.84375;
2864
+ const cardY = 1.6;
2865
+ addTheme02Card(slide, cardX, cardY, cardW, cardH, 0.12);
2866
+ slide.addShape('line', {
2867
+ x1: cardX, y1: cardY, x2: cardX, y2: cardY + cardH,
2868
+ line: { color: COLORS.accent, width: 4 },
2869
+ });
2870
+ if (props.kicker) {
2871
+ slide.addText(props.kicker, {
2872
+ x: cardX + 0.3, y: cardY + 0.3, w: cardW - 0.6, h: 0.25,
2873
+ fontSize: 12, color: COLORS.accent, fontFace: FONTS.mono,
2874
+ });
2875
+ }
2876
+ slide.addText(theme03TitleText(props.title ?? ''), {
2877
+ x: cardX + 0.3, y: cardY + (props.kicker ? 0.65 : 0.3), w: cardW - 0.6, h: 1.4,
2878
+ fontSize: 54, fontFace: FONTS.heading, valign: 'top',
2879
+ });
2880
+ if (props.subtitle) {
2881
+ slide.addText(props.subtitle, {
2882
+ x: cardX + 0.3, y: cardY + 2.1, w: cardW - 0.6, h: 0.6,
2883
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2884
+ });
2885
+ }
2886
+ addTheme03Footer(slide, props);
2887
+ }
2888
+ function renderTheme03CoverV2(slide, props) {
2889
+ addTheme03Topbar(slide, props);
2890
+ if (props.image) {
2891
+ addImageMaybe(slide, props.image, 0, 0, 10, 5.625);
2892
+ }
2893
+ slide.addShape('rect', {
2894
+ x: 0, y: 0, w: 10, h: 5.625,
2895
+ fill: { color: '05080D', transparency: 50 },
2896
+ });
2897
+ const cardW = 5.2;
2898
+ const cardH = 3.2;
2899
+ const cardX = 0.84375;
2900
+ const cardY = 1.4;
2901
+ addTheme02Card(slide, cardX, cardY, cardW, cardH, 0.12);
2902
+ slide.addShape('line', {
2903
+ x1: cardX, y1: cardY, x2: cardX, y2: cardY + cardH,
2904
+ line: { color: COLORS.accent, width: 4 },
2905
+ });
2906
+ if (props.kicker) {
2907
+ slide.addText(props.kicker, {
2908
+ x: cardX + 0.25, y: cardY + 0.25, w: cardW - 0.5, h: 0.22,
2909
+ fontSize: 11, color: COLORS.accent, fontFace: FONTS.mono,
2910
+ });
2911
+ }
2912
+ slide.addText(theme03TitleText(props.title ?? ''), {
2913
+ x: cardX + 0.25, y: cardY + (props.kicker ? 0.55 : 0.25), w: cardW - 0.5, h: 1.2,
2914
+ fontSize: 48, fontFace: FONTS.heading, valign: 'top',
2915
+ });
2916
+ if (props.subtitle) {
2917
+ slide.addText(props.subtitle, {
2918
+ x: cardX + 0.25, y: cardY + 1.85, w: cardW - 0.5, h: 0.6,
2919
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2920
+ });
2921
+ }
2922
+ if (props.date) {
2923
+ slide.addText(props.date, {
2924
+ x: cardX + 0.25, y: cardY + 2.65, w: cardW - 0.5, h: 0.25,
2925
+ fontSize: 12, color: COLORS.accent, fontFace: FONTS.mono,
2926
+ });
2927
+ }
2928
+ addTheme03Footer(slide, props);
2929
+ }
2930
+ function renderTheme03CoverV3(slide, props) {
2931
+ addTheme03Topbar(slide, props);
2932
+ const mainW = 5.4;
2933
+ const mainH = 3.2;
2934
+ const mainX = 0.84375;
2935
+ const mainY = 1.4;
2936
+ addTheme02Card(slide, mainX, mainY, mainW, mainH, 0.12);
2937
+ slide.addShape('line', {
2938
+ x1: mainX, y1: mainY, x2: mainX, y2: mainY + mainH,
2939
+ line: { color: COLORS.accent, width: 4 },
2940
+ });
2941
+ if (props.kicker) {
2942
+ slide.addText(props.kicker, {
2943
+ x: mainX + 0.25, y: mainY + 0.25, w: mainW - 0.5, h: 0.22,
2944
+ fontSize: 11, color: COLORS.accent, fontFace: FONTS.mono,
2945
+ });
2946
+ }
2947
+ slide.addText(theme03TitleText(props.title ?? ''), {
2948
+ x: mainX + 0.25, y: mainY + (props.kicker ? 0.55 : 0.25), w: mainW - 0.5, h: 1.4,
2949
+ fontSize: 44, fontFace: FONTS.heading, valign: 'top',
2950
+ });
2951
+ if (props.subtitle) {
2952
+ slide.addText(props.subtitle, {
2953
+ x: mainX + 0.25, y: mainY + 2.0, w: mainW - 0.5, h: 0.8,
2954
+ fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2955
+ });
2956
+ }
2957
+ const bentoW = 2.5;
2958
+ const bentoH = 1.5;
2959
+ const bentoX = mainX + mainW + 0.25;
2960
+ addTheme02Card(slide, bentoX, mainY, bentoW, bentoH, 0.08);
2961
+ slide.addText(props.presenter ?? 'lemonPPT Research', {
2962
+ x: bentoX + 0.15, y: mainY + 0.55, w: bentoW - 0.3, h: 0.4,
2963
+ fontSize: 16, color: COLORS.primary, bold: true, fontFace: FONTS.body, align: 'center', valign: 'middle',
2964
+ });
2965
+ addTheme02Card(slide, bentoX, mainY + bentoH + 0.2, bentoW, bentoH, 0.08);
2966
+ slide.addText([props.date, 'Theme 03'].filter(Boolean).join(' · '), {
2967
+ x: bentoX + 0.15, y: mainY + bentoH + 0.75, w: bentoW - 0.3, h: 0.4,
2968
+ fontSize: 13, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center', valign: 'middle',
2969
+ });
2970
+ addTheme03Footer(slide, props);
2971
+ }
2972
+ function renderTheme03CoverV4(slide, props) {
2973
+ addTheme03Topbar(slide, props);
2974
+ slide.addText(props.kicker || 'FEATURED STORY', {
2975
+ x: 0.84375, y: 1.55, w: 4.0, h: 0.25,
2976
+ fontSize: 12, color: COLORS.accent, fontFace: FONTS.mono,
2977
+ });
2978
+ slide.addText(props.edition || 'ED. 01', {
2979
+ x: 5.5, y: 1.55, w: 3.5, h: 0.25,
2980
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.mono, align: 'right',
2981
+ });
2982
+ slide.addShape('line', {
2983
+ x1: 0.84375, y1: 1.85, x2: 9.15625, y2: 1.85,
2984
+ line: { color: COLORS.border, width: 1 },
2985
+ });
2986
+ slide.addText(theme03TitleText(props.title ?? ''), {
2987
+ x: 0.84375, y: 2.05, w: 8.3125, h: 1.4,
2988
+ fontSize: 72, fontFace: FONTS.heading, valign: 'top',
2989
+ });
2990
+ if (props.subtitle) {
2991
+ slide.addText(props.subtitle, {
2992
+ x: 0.84375, y: 3.55, w: 6.5, h: 0.6,
2993
+ fontSize: 16, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2994
+ });
2995
+ }
2996
+ slide.addText([props.date, 'lemonPPT'].filter(Boolean).join(' · '), {
2997
+ x: 0.84375, y: 4.5, w: 4.0, h: 0.25,
2998
+ fontSize: 12, color: COLORS.secondary, fontFace: FONTS.mono,
2999
+ });
3000
+ addTheme03Footer(slide, props);
3001
+ }
3002
+ function renderTheme03FeatureV2(slide, props) {
3003
+ addTheme03Topbar(slide, props);
3004
+ slide.addText(theme03TitleText(props.title ?? ''), {
3005
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
3006
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
3007
+ });
3008
+ if (props.subtitle) {
3009
+ slide.addText(props.subtitle, {
3010
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.35,
3011
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
3012
+ });
3013
+ }
3014
+ const imgW = 3.4;
3015
+ const imgH = 2.6;
3016
+ const imgX = 0.84375;
3017
+ const imgY = 2.75;
3018
+ addTheme02Card(slide, imgX, imgY, imgW, imgH, 0.08);
3019
+ if (props.imageUrl) {
3020
+ addImageMaybe(slide, props.imageUrl, imgX + 0.1, imgY + 0.1, imgW - 0.2, imgH - 0.2);
3021
+ }
3022
+ else {
3023
+ slide.addText('点击上传图片', {
3024
+ x: imgX, y: imgY + imgH / 2 - 0.15, w: imgW, h: 0.3,
3025
+ fontSize: 12, color: COLORS.secondary, align: 'center', fontFace: FONTS.mono,
3026
+ });
3027
+ }
3028
+ const cardsX = imgX + imgW + 0.35;
3029
+ const cardsW = 4.6;
3030
+ const items = (props.items || []).slice(0, 5);
3031
+ if (items.length > 0) {
3032
+ const cardH = items.length <= 3 ? 0.75 : 0.6;
3033
+ const gap = 0.12;
3034
+ items.forEach((item, idx) => {
3035
+ const y = imgY + idx * (cardH + gap);
3036
+ addTheme02Card(slide, cardsX, y, cardsW, cardH, 0.06);
3037
+ slide.addText(String(idx + 1).padStart(2, '0'), {
3038
+ x: cardsX + 0.12, y: y + 0.12, w: 0.6, h: 0.4,
3039
+ fontSize: 18, color: COLORS.accent, bold: true, fontFace: FONTS.heading, valign: 'top',
3040
+ });
3041
+ slide.addText(item.title ?? '', {
3042
+ x: cardsX + 0.8, y: y + 0.08, w: cardsW - 0.95, h: 0.25,
3043
+ fontSize: 14, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
3044
+ });
3045
+ if (item.description) {
3046
+ slide.addText(item.description, {
3047
+ x: cardsX + 0.8, y: y + 0.32, w: cardsW - 0.95, h: cardH - 0.42,
3048
+ fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
3049
+ });
3050
+ }
3051
+ });
3052
+ }
3053
+ if (props.footer) {
3054
+ slide.addText(props.footer, {
3055
+ x: 0.84375, y: 5.05, w: 8.3125, h: 0.25,
3056
+ fontSize: 11, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center',
3057
+ });
3058
+ }
3059
+ addTheme03Footer(slide, props);
3060
+ }
3061
+ function renderTheme03QuoteV2(slide, props) {
3062
+ addTheme03Topbar(slide, props);
3063
+ slide.addText('“', {
3064
+ x: 0.84375, y: 1.4, w: 1.0, h: 1.0,
3065
+ fontSize: 100, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
3066
+ valign: 'top',
3067
+ });
3068
+ const cardX = 1.9;
3069
+ const cardW = 7.25;
3070
+ const cardH = 3.2;
3071
+ const cardY = 1.4;
3072
+ addTheme02Card(slide, cardX, cardY, cardW, cardH, 0.12);
3073
+ slide.addShape('line', {
3074
+ x1: cardX, y1: cardY, x2: cardX, y2: cardY + cardH,
3075
+ line: { color: COLORS.accent, width: 3 },
3076
+ });
3077
+ slide.addText(theme03TitleText(props.quote ?? ''), {
3078
+ x: cardX + 0.25, y: cardY + 0.25, w: cardW - 0.5, h: 1.6,
3079
+ fontSize: 34, fontFace: FONTS.heading, valign: 'top',
3080
+ });
3081
+ const attrParts = [props.author, props.role, props.source].filter(Boolean).join(' / ');
3082
+ if (attrParts) {
3083
+ slide.addText(attrParts, {
3084
+ x: cardX + 0.25, y: cardY + 2.6, w: cardW - 0.5, h: 0.3,
3085
+ fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body,
3086
+ });
3087
+ }
3088
+ addTheme03Footer(slide, props);
3089
+ }
3090
+ function renderTheme03QuoteV3(slide, props) {
3091
+ addTheme03Topbar(slide, props);
3092
+ slide.addText(theme03TitleText(props.quote ?? ''), {
3093
+ x: 0.84375, y: 1.5, w: 8.3125, h: 2.0,
3094
+ fontSize: 56, fontFace: FONTS.heading, valign: 'top',
3095
+ });
3096
+ slide.addShape('line', {
3097
+ x1: 0.84375, y1: 3.65, x2: 2.5, y2: 3.65,
3098
+ line: { color: COLORS.accent, width: 2 },
3099
+ });
3100
+ const attrParts = [props.author, props.role].filter(Boolean).join(' / ');
3101
+ if (attrParts) {
3102
+ slide.addText(attrParts, {
3103
+ x: 0.84375, y: 3.85, w: 6.0, h: 0.3,
3104
+ fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body,
3105
+ });
3106
+ }
3107
+ addTheme03Footer(slide, props);
3108
+ }
3109
+ function renderTheme03TableOfContentsV2(slide, props) {
3110
+ addTheme03Topbar(slide, props);
3111
+ slide.addText(theme03TitleText(props.title ?? '目录'), {
3112
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
3113
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
3114
+ });
3115
+ if (props.subtitle) {
3116
+ slide.addText(props.subtitle, {
3117
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
3118
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
3119
+ });
3120
+ }
3121
+ const items = (props.items || []).slice(0, 8);
3122
+ if (items.length > 0) {
3123
+ const startY = 2.85;
3124
+ const rowH = 0.48;
3125
+ items.forEach((item, idx) => {
3126
+ const y = startY + idx * rowH;
3127
+ addTheme02Card(slide, 0.84375, y, 8.3125, 0.4, 0.06);
3128
+ slide.addText(String(idx + 1).padStart(2, '0'), {
3129
+ x: 1.05, y: y + 0.08, w: 0.5, h: 0.24,
3130
+ fontSize: 13, color: COLORS.accent, bold: true, fontFace: FONTS.mono, valign: 'middle',
3131
+ });
3132
+ slide.addText(item ?? '', {
3133
+ x: 1.6, y: y + 0.08, w: 6.8, h: 0.24,
3134
+ fontSize: 14, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'middle',
3135
+ });
3136
+ });
3137
+ }
3138
+ addTheme03Footer(slide, props);
3139
+ }
3140
+ function renderTheme03TeamV2(slide, props) {
3141
+ addTheme03Topbar(slide, props);
3142
+ slide.addText(theme03TitleText(props.title ?? ''), {
3143
+ x: 0.84375, y: 1.45, w: 8.3, h: 0.8,
3144
+ fontSize: 40, fontFace: FONTS.heading, valign: 'top',
3145
+ });
3146
+ if (props.subtitle) {
3147
+ slide.addText(props.subtitle, {
3148
+ x: 0.84375, y: 2.25, w: 7.5, h: 0.4,
3149
+ fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
3150
+ });
3151
+ }
3152
+ const members = (props.members || []).filter((m) => m != null).slice(0, 3);
3153
+ if (members.length > 0) {
3154
+ const cols = members.length;
3155
+ const gap = 0.3;
3156
+ const cardW = (8.3125 - (cols - 1) * gap) / cols;
3157
+ const cardH = 2.6;
3158
+ const startY = 2.85;
3159
+ members.forEach((member, idx) => {
3160
+ const x = 0.84375 + idx * (cardW + gap);
3161
+ addTheme02Card(slide, x, startY, cardW, cardH, 0.08);
3162
+ slide.addShape('roundRect', {
3163
+ x, y: startY, w: cardW, h: 0.08,
3164
+ fill: { color: COLORS.accent }, rectRadius: 0.04,
3165
+ });
3166
+ const avatarSize = 0.9;
3167
+ const avatarX = x + (cardW - avatarSize) / 2;
3168
+ const avatarY = startY + 0.2;
3169
+ if (member.imageUrl) {
3170
+ addImageMaybe(slide, member.imageUrl, avatarX, avatarY, avatarSize, avatarSize);
3171
+ }
3172
+ else {
3173
+ slide.addShape('ellipse', {
3174
+ x: avatarX, y: avatarY, w: avatarSize, h: avatarSize,
3175
+ fill: { color: COLORS.surfaceElevated }, line: { color: COLORS.border, width: 1 },
3176
+ });
3177
+ slide.addText((member.name ?? '?').charAt(0), {
3178
+ x: avatarX, y: avatarY + 0.25, w: avatarSize, h: 0.4,
3179
+ fontSize: 24, color: COLORS.accent, bold: true, align: 'center', fontFace: FONTS.heading,
3180
+ });
3181
+ }
3182
+ slide.addText(member.name ?? '', {
3183
+ x: x, y: avatarY + avatarSize + 0.12, w: cardW, h: 0.28,
3184
+ fontSize: 16, color: COLORS.primary, bold: true, fontFace: FONTS.body, align: 'center', valign: 'top',
3185
+ });
3186
+ slide.addText(member.role ?? '', {
3187
+ x: x, y: avatarY + avatarSize + 0.45, w: cardW, h: 0.22,
3188
+ fontSize: 11, color: COLORS.accent, fontFace: FONTS.mono, align: 'center', valign: 'top',
3189
+ });
3190
+ if (member.bio) {
3191
+ slide.addText(member.bio, {
3192
+ x: x + 0.12, y: avatarY + avatarSize + 0.75, w: cardW - 0.24, h: 0.8,
3193
+ fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, align: 'center', valign: 'top',
3194
+ });
3195
+ }
3196
+ });
3197
+ }
3198
+ addTheme03Footer(slide, props);
3199
+ }
3200
+ export function registerTheme03Renderers(registerPptxLayoutRenderer) {
3201
+ registerPptxLayoutRenderer('theme03_cover_v1', renderTheme03CoverV1);
3202
+ registerPptxLayoutRenderer('theme03_chapter_v1', renderTheme03ChapterV1);
3203
+ registerPptxLayoutRenderer('theme03_content_v1', renderTheme03ContentV1);
3204
+ registerPptxLayoutRenderer('theme03_metric_big', renderTheme03MetricBig);
3205
+ registerPptxLayoutRenderer('theme03_ranking_v1', renderTheme03RankingV1);
3206
+ registerPptxLayoutRenderer('theme03_quote_v1', renderTheme03QuoteV1);
3207
+ registerPptxLayoutRenderer('theme03_case_v1', renderTheme03CaseV1);
3208
+ registerPptxLayoutRenderer('theme03_closing_v1', renderTheme03ClosingV1);
3209
+ registerPptxLayoutRenderer('theme03_table_of_contents_v1', renderTheme03TableOfContentsV1);
3210
+ registerPptxLayoutRenderer('theme03_metrics_v1', renderTheme03MetricsV1);
3211
+ registerPptxLayoutRenderer('theme03_feature_v1', renderTheme03FeatureV1);
3212
+ registerPptxLayoutRenderer('theme03_image_v1', renderTheme03ImageV1);
3213
+ registerPptxLayoutRenderer('theme03_chart_donut', renderTheme03ChartDonut);
3214
+ registerPptxLayoutRenderer('theme03_chart_bar', renderTheme03ChartBar);
3215
+ registerPptxLayoutRenderer('theme03_chart_v1', renderTheme03ChartV1);
3216
+ registerPptxLayoutRenderer('theme03_trend_v1', renderTheme03TrendV1);
3217
+ registerPptxLayoutRenderer('theme03_chart_radar', renderTheme03ChartRadar);
3218
+ registerPptxLayoutRenderer('theme03_chart_funnel', renderTheme03ChartFunnel);
3219
+ registerPptxLayoutRenderer('theme03_chart_gauge', renderTheme03ChartGauge);
3220
+ registerPptxLayoutRenderer('theme03_chart_heatmap', renderTheme03ChartHeatmap);
3221
+ registerPptxLayoutRenderer('theme03_chart_treemap', renderTheme03ChartTreemap);
3222
+ registerPptxLayoutRenderer('theme03_chart_wordcloud', renderTheme03ChartWordcloud);
3223
+ registerPptxLayoutRenderer('theme03_chart_bar3d', renderTheme03ChartBar3d);
3224
+ registerPptxLayoutRenderer('theme03_chart_graph', renderTheme03ChartGraph);
3225
+ registerPptxLayoutRenderer('theme03_chart_sankey', renderTheme03ChartSankey);
3226
+ registerPptxLayoutRenderer('theme03_chart_sunburst', renderTheme03ChartSunburst);
3227
+ registerPptxLayoutRenderer('theme03_team_v1', renderTheme03TeamV1);
3228
+ registerPptxLayoutRenderer('theme03_partners_v1', renderTheme03PartnersV1);
3229
+ registerPptxLayoutRenderer('theme03_pricing_v1', renderTheme03PricingV1);
3230
+ registerPptxLayoutRenderer('theme03_comparison_v1', renderTheme03ComparisonV1);
3231
+ registerPptxLayoutRenderer('theme03_faq_v1', renderTheme03FaqV1);
3232
+ registerPptxLayoutRenderer('theme03_gallery_v1', renderTheme03GalleryV1);
3233
+ registerPptxLayoutRenderer('theme03_number_showcase_v1', renderTheme03NumberShowcaseV1);
3234
+ registerPptxLayoutRenderer('theme03_bento_v1', renderTheme03BentoV1);
3235
+ registerPptxLayoutRenderer('theme03_quadrant_v1', renderTheme03QuadrantV1);
3236
+ registerPptxLayoutRenderer('theme03_table_v1', renderTheme03TableV1);
3237
+ registerPptxLayoutRenderer('theme03_testimonial_v1', renderTheme03TestimonialV1);
3238
+ registerPptxLayoutRenderer('theme03_tags_v1', renderTheme03TagsV1);
3239
+ registerPptxLayoutRenderer('theme03_progress_v1', renderTheme03ProgressV1);
3240
+ registerPptxLayoutRenderer('theme03_process_v1', renderTheme03ProcessV1);
3241
+ registerPptxLayoutRenderer('theme03_timeline_v1', renderTheme03TimelineV1);
3242
+ registerPptxLayoutRenderer('theme03_roadmap_v1', renderTheme03RoadmapV1);
3243
+ registerPptxLayoutRenderer('theme03_swot_v1', renderTheme03SwotV1);
3244
+ registerPptxLayoutRenderer('theme03_metric_v1', renderTheme03MetricV1);
3245
+ registerPptxLayoutRenderer('theme03_metric_v2', renderTheme03MetricV2);
3246
+ registerPptxLayoutRenderer('theme03_metric_v3', renderTheme03MetricV3);
3247
+ registerPptxLayoutRenderer('theme03_metric_triptych', renderTheme03MetricTriptych);
3248
+ registerPptxLayoutRenderer('theme03_scorecard_v1', renderTheme03ScorecardV1);
3249
+ registerPptxLayoutRenderer('theme03_appendix_v1', renderTheme03AppendixV1);
3250
+ registerPptxLayoutRenderer('theme03_case_study', renderTheme03CaseStudy);
3251
+ registerPptxLayoutRenderer('theme03_outlook_v1', renderTheme03OutlookV1);
3252
+ registerPptxLayoutRenderer('theme03_region_v1', renderTheme03RegionV1);
3253
+ registerPptxLayoutRenderer('theme03_risk_v1', renderTheme03RiskV1);
3254
+ registerPptxLayoutRenderer('theme03_spotlight_grid', renderTheme03SpotlightGrid);
3255
+ registerPptxLayoutRenderer('theme03_conclusion_v1', renderTheme03ConclusionV1);
3256
+ registerPptxLayoutRenderer('theme03_diptych_contrast', renderTheme03DiptychContrast);
3257
+ registerPptxLayoutRenderer('theme03_filmstrip_v1', renderTheme03FilmstripV1);
3258
+ registerPptxLayoutRenderer('theme03_gantt_v1', renderTheme03GanttV1);
3259
+ registerPptxLayoutRenderer('theme03_pest_v1', renderTheme03PestV1);
3260
+ registerPptxLayoutRenderer('theme03_stats_v1', renderTheme03StatsV1);
3261
+ registerPptxLayoutRenderer('theme03_table_data', renderTheme03TableData);
3262
+ registerPptxLayoutRenderer('theme03_chapter_v2', renderTheme03ChapterV2);
3263
+ registerPptxLayoutRenderer('theme03_chapter_v3', renderTheme03ChapterV3);
3264
+ registerPptxLayoutRenderer('theme03_closing_v2', renderTheme03ClosingV2);
3265
+ registerPptxLayoutRenderer('theme03_comparison_v2', renderTheme03ComparisonV2);
3266
+ registerPptxLayoutRenderer('theme03_comparison_v3', renderTheme03ComparisonV3);
3267
+ registerPptxLayoutRenderer('theme03_content_v2', renderTheme03ContentV2);
3268
+ registerPptxLayoutRenderer('theme03_content_v3', renderTheme03ContentV3);
3269
+ registerPptxLayoutRenderer('theme03_content_v4', renderTheme03ContentV4);
3270
+ registerPptxLayoutRenderer('theme03_cover_v2', renderTheme03CoverV2);
3271
+ registerPptxLayoutRenderer('theme03_cover_v3', renderTheme03CoverV3);
3272
+ registerPptxLayoutRenderer('theme03_cover_v4', renderTheme03CoverV4);
3273
+ registerPptxLayoutRenderer('theme03_feature_v2', renderTheme03FeatureV2);
3274
+ registerPptxLayoutRenderer('theme03_quote_v2', renderTheme03QuoteV2);
3275
+ registerPptxLayoutRenderer('theme03_quote_v3', renderTheme03QuoteV3);
3276
+ registerPptxLayoutRenderer('theme03_table_of_contents_v2', renderTheme03TableOfContentsV2);
3277
+ registerPptxLayoutRenderer('theme03_team_v2', renderTheme03TeamV2);
3278
+ }
3279
+ //# sourceMappingURL=theme03-pptx.js.map