@lemonppt/renderer 1.0.7 → 1.0.8

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 (84) hide show
  1. package/dist/browser-export-driver.d.ts +28 -0
  2. package/dist/browser-export-driver.d.ts.map +1 -0
  3. package/dist/browser-export-driver.js +115 -0
  4. package/dist/browser-export-driver.js.map +1 -0
  5. package/dist/browser-export.d.ts +66 -0
  6. package/dist/browser-export.d.ts.map +1 -0
  7. package/dist/browser-export.js +176 -0
  8. package/dist/browser-export.js.map +1 -0
  9. package/dist/client/browser-export.js +1 -0
  10. package/dist/client/client-render.js +21 -21
  11. package/dist/client/theme-echarts.js +27 -27
  12. package/dist/export-pdf.d.ts.map +1 -1
  13. package/dist/export-pdf.js +6 -3
  14. package/dist/export-pdf.js.map +1 -1
  15. package/dist/export-pptx-screenshot.d.ts +36 -0
  16. package/dist/export-pptx-screenshot.d.ts.map +1 -0
  17. package/dist/export-pptx-screenshot.js +74 -0
  18. package/dist/export-pptx-screenshot.js.map +1 -0
  19. package/dist/index.d.ts +3 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/normalize-goal.d.ts +6 -0
  24. package/dist/normalize-goal.d.ts.map +1 -0
  25. package/dist/normalize-goal.js +12 -0
  26. package/dist/normalize-goal.js.map +1 -0
  27. package/dist/preview-server.d.ts +9 -0
  28. package/dist/preview-server.d.ts.map +1 -0
  29. package/dist/preview-server.js +81 -0
  30. package/dist/preview-server.js.map +1 -0
  31. package/dist/render-editor-data.js +2 -2
  32. package/dist/render-editor-data.js.map +1 -1
  33. package/dist/render.d.ts.map +1 -1
  34. package/dist/render.js +14 -4
  35. package/dist/render.js.map +1 -1
  36. package/package.json +8 -5
  37. package/dist/export-pptx.d.ts +0 -1167
  38. package/dist/export-pptx.d.ts.map +0 -1
  39. package/dist/export-pptx.js +0 -3007
  40. package/dist/export-pptx.js.map +0 -1
  41. package/dist/theme01-pptx.d.ts +0 -12
  42. package/dist/theme01-pptx.d.ts.map +0 -1
  43. package/dist/theme01-pptx.js +0 -2957
  44. package/dist/theme01-pptx.js.map +0 -1
  45. package/dist/theme02-pptx.d.ts +0 -5
  46. package/dist/theme02-pptx.d.ts.map +0 -1
  47. package/dist/theme02-pptx.js +0 -1063
  48. package/dist/theme02-pptx.js.map +0 -1
  49. package/dist/theme03-pptx.d.ts +0 -5
  50. package/dist/theme03-pptx.d.ts.map +0 -1
  51. package/dist/theme03-pptx.js +0 -3279
  52. package/dist/theme03-pptx.js.map +0 -1
  53. package/dist/theme04-pptx.d.ts +0 -6
  54. package/dist/theme04-pptx.d.ts.map +0 -1
  55. package/dist/theme04-pptx.js +0 -5496
  56. package/dist/theme04-pptx.js.map +0 -1
  57. package/dist/theme05-pptx.d.ts +0 -5
  58. package/dist/theme05-pptx.d.ts.map +0 -1
  59. package/dist/theme05-pptx.js +0 -3152
  60. package/dist/theme05-pptx.js.map +0 -1
  61. package/dist/theme06-pptx.d.ts +0 -124
  62. package/dist/theme06-pptx.d.ts.map +0 -1
  63. package/dist/theme06-pptx.js +0 -8154
  64. package/dist/theme06-pptx.js.map +0 -1
  65. package/dist/theme07-pptx.d.ts +0 -49
  66. package/dist/theme07-pptx.d.ts.map +0 -1
  67. package/dist/theme07-pptx.js +0 -2579
  68. package/dist/theme07-pptx.js.map +0 -1
  69. package/dist/theme08-pptx.d.ts +0 -19
  70. package/dist/theme08-pptx.d.ts.map +0 -1
  71. package/dist/theme08-pptx.js +0 -1411
  72. package/dist/theme08-pptx.js.map +0 -1
  73. package/dist/theme09-pptx.d.ts +0 -18
  74. package/dist/theme09-pptx.d.ts.map +0 -1
  75. package/dist/theme09-pptx.js +0 -1513
  76. package/dist/theme09-pptx.js.map +0 -1
  77. package/dist/theme10-pptx.d.ts +0 -18
  78. package/dist/theme10-pptx.d.ts.map +0 -1
  79. package/dist/theme10-pptx.js +0 -2889
  80. package/dist/theme10-pptx.js.map +0 -1
  81. package/dist/theme11-pptx.d.ts +0 -17
  82. package/dist/theme11-pptx.d.ts.map +0 -1
  83. package/dist/theme11-pptx.js +0 -908
  84. package/dist/theme11-pptx.js.map +0 -1
@@ -1,3279 +0,0 @@
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