@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,2579 +0,0 @@
1
- // lemonPPT - AI-powered presentation generation
2
- // Copyright (c) 2026 lemonforme
3
- // SPDX-License-Identifier: AGPL-3.0-or-later
4
- //
5
- // theme07 PPTX 版式专属渲染器(从 export-pptx.ts 抽出)。
6
- // 共享基础设施(全局 helper + 通用渲染器 + 类型 + theme07 局部 helper/const)从 export-pptx.ts 导入;
7
- // theme01 通用渲染器从 theme01-pptx.ts 复用;其他已抽出主题的渲染器从各自 theme<K>-pptx.ts 复用。
8
- // 注册通过 registerTheme07Renderers 回调注入。
9
- import { addTheme07Kicker, FONTS, COLORS, addTheme07Card, addImageMaybe, addTheme07GlowLine, addTheme07Footer, resolveTheme07CssColor, getLayoutCoordinates } from './export-pptx.js';
10
- export function renderTheme07CoverV1(slide, props) {
11
- if (props.tag) {
12
- addTheme07Kicker(slide, props.tag, 0.65, 0.55);
13
- }
14
- slide.addText(props.title ?? '', {
15
- x: 0.65, y: 0.95, w: 4.7, h: 1.6,
16
- fontSize: 46, bold: true, fontFace: FONTS.heading, color: COLORS.primary,
17
- valign: 'top',
18
- });
19
- if (props.subtitle) {
20
- slide.addText(props.subtitle, {
21
- x: 0.65, y: 2.65, w: 4.7, h: 0.6,
22
- fontSize: 15, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
23
- });
24
- }
25
- // 底部标签
26
- if (props.tags && props.tags.length > 0) {
27
- const tags = props.tags.map((t) => typeof t === 'string' ? t : t.item).filter(Boolean);
28
- let tagX = 0.65;
29
- tags.slice(0, 6).forEach((tag) => {
30
- const w = Math.min(2.0, 0.25 + tag.length * 0.12);
31
- addTheme07Card(slide, tagX, 3.55, w, 0.32, 0.04, COLORS.white);
32
- slide.addText(tag, {
33
- x: tagX + 0.05, y: 3.6, w: w - 0.1, h: 0.22,
34
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center',
35
- });
36
- tagX += w + 0.12;
37
- });
38
- }
39
- // 右侧图片区
40
- if (props.imageUrl) {
41
- addImageMaybe(slide, props.imageUrl, 5.6, 0.85, 3.75, 2.3);
42
- }
43
- else {
44
- addTheme07Card(slide, 5.6, 0.85, 3.75, 2.3, 0.08, COLORS.white);
45
- }
46
- // 右侧数据指标
47
- if (props.metrics && props.metrics.length > 0) {
48
- const metricY = 3.35;
49
- const metricW = 3.75 / Math.min(props.metrics.length, 4);
50
- props.metrics.slice(0, 4).forEach((m, idx) => {
51
- const x = 5.6 + idx * metricW;
52
- addTheme07Card(slide, x, metricY, metricW - 0.08, 0.9, 0.06, COLORS.white);
53
- slide.addText(`${m.value ?? ''}${m.unit ?? ''}`, {
54
- x, y: metricY + 0.12, w: metricW - 0.08, h: 0.35,
55
- fontSize: 20, color: m.accent ? COLORS.accent : COLORS.primary, bold: true, fontFace: FONTS.heading,
56
- });
57
- if (m.label) {
58
- slide.addText(m.label, {
59
- x, y: metricY + 0.48, w: metricW - 0.08, h: 0.2,
60
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.mono,
61
- });
62
- }
63
- });
64
- }
65
- addTheme07GlowLine(slide);
66
- addTheme07Footer(slide, props);
67
- }
68
- export function renderTheme07TableOfContentsV1(slide, props) {
69
- if (props.kicker) {
70
- addTheme07Kicker(slide, props.kicker);
71
- }
72
- slide.addText(props.title ?? '', {
73
- x: 0.65, y: 0.9, w: 8.7, h: 0.7,
74
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
75
- });
76
- if (props.subtitle) {
77
- slide.addText(props.subtitle, {
78
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
79
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
80
- });
81
- }
82
- slide.addShape('line', {
83
- x1: 0.65, y1: 2.2, x2: 1.5, y2: 2.2,
84
- line: { color: COLORS.accent, width: 3 },
85
- });
86
- if (props.entries && props.entries.length > 0) {
87
- const colW = 4.2;
88
- const rowH = 0.65;
89
- const startY = 2.5;
90
- props.entries.slice(0, 8).forEach((entry, idx) => {
91
- const col = idx % 2;
92
- const row = Math.floor(idx / 2);
93
- const x = 0.65 + col * (colW + 0.3);
94
- const y = startY + row * rowH;
95
- const fill = entry.accent ? COLORS.accent : COLORS.white;
96
- addTheme07Card(slide, x, y, colW, rowH - 0.1, 0.08, fill);
97
- const numColor = entry.accent ? COLORS.white : COLORS.accent;
98
- const textColor = entry.accent ? COLORS.white : COLORS.primary;
99
- const pageColor = entry.accent ? COLORS.white : COLORS.secondary;
100
- slide.addText(entry.number ?? '', {
101
- x: x + 0.15, y: y + 0.1, w: 0.6, h: 0.35,
102
- fontSize: 20, color: numColor, bold: true, fontFace: FONTS.heading,
103
- });
104
- slide.addText(entry.title ?? '', {
105
- x: x + 0.8, y: y + 0.13, w: colW - 1.4, h: 0.3,
106
- fontSize: 13, color: textColor, fontFace: FONTS.body,
107
- });
108
- if (entry.page) {
109
- slide.addText(entry.page, {
110
- x: x + colW - 0.65, y: y + 0.13, w: 0.5, h: 0.3,
111
- fontSize: 10, color: pageColor, fontFace: FONTS.mono, align: 'right',
112
- });
113
- }
114
- });
115
- }
116
- if (props.footnote) {
117
- slide.addText(props.footnote, {
118
- x: 0.65, y: 4.95, w: 8.7, h: 0.3,
119
- fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
120
- });
121
- }
122
- addTheme07GlowLine(slide);
123
- }
124
- export function renderTheme07ChapterV1(slide, props) {
125
- // 深色背景块
126
- slide.addShape('rect', {
127
- x: 0, y: 0, w: 10, h: 5.625,
128
- fill: { color: '11161D' },
129
- });
130
- // 超大章节号
131
- slide.addText(props.number ?? '', {
132
- x: 0.65, y: 0.9, w: 3.5, h: 2.0,
133
- fontSize: 110, color: COLORS.accent, bold: true, fontFace: FONTS.heading, valign: 'top',
134
- });
135
- slide.addText(props.title ?? '', {
136
- x: 0.65, y: 2.9, w: 8.7, h: 0.85,
137
- fontSize: 42, bold: true, fontFace: FONTS.heading, color: COLORS.white, valign: 'top',
138
- });
139
- if (props.subtitle) {
140
- slide.addText(props.subtitle, {
141
- x: 0.65, y: 3.75, w: 8.7, h: 0.35,
142
- fontSize: 15, color: 'D0D4DC', fontFace: FONTS.body, valign: 'top',
143
- });
144
- }
145
- if (props.enSubtitle) {
146
- slide.addText(props.enSubtitle, {
147
- x: 0.65, y: 4.15, w: 8.7, h: 0.25,
148
- fontSize: 10, color: '8B93A3', fontFace: FONTS.mono, valign: 'top',
149
- });
150
- }
151
- if (props.tags && props.tags.length > 0) {
152
- const tagY = 4.7;
153
- let tagX = 0.65;
154
- const tags = props.tags.map((t) => typeof t === 'string' ? t : t.item).filter(Boolean);
155
- tags.slice(0, 6).forEach((tag) => {
156
- const w = Math.min(2.2, 0.3 + tag.length * 0.12);
157
- slide.addShape('roundRect', {
158
- x: tagX, y: tagY, w, h: 0.32,
159
- fill: { color: '1A2330' },
160
- line: { color: '3A4559', width: 1 },
161
- rectRadius: 0.04,
162
- });
163
- slide.addText(tag, {
164
- x: tagX + 0.05, y: tagY + 0.05, w: w - 0.1, h: 0.22,
165
- fontSize: 10, color: 'D0D4DC', fontFace: FONTS.mono, align: 'center',
166
- });
167
- tagX += w + 0.15;
168
- });
169
- }
170
- }
171
- export function renderTheme07ContentV1(slide, props) {
172
- if (props.imageUrl) {
173
- addImageMaybe(slide, props.imageUrl, 0, 0, 10, 5.625);
174
- }
175
- if (props.kicker) {
176
- addTheme07Kicker(slide, props.kicker);
177
- }
178
- slide.addText(props.title ?? '', {
179
- x: 0.65, y: 0.95, w: 4.7, h: 0.8,
180
- fontSize: 32, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
181
- });
182
- if (props.subtitle) {
183
- slide.addText(props.subtitle, {
184
- x: 0.65, y: 1.75, w: 4.7, h: 0.5,
185
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
186
- });
187
- }
188
- slide.addShape('line', {
189
- x1: 0.65, y1: 2.3, x2: 1.5, y2: 2.3,
190
- line: { color: COLORS.accent, width: 3 },
191
- });
192
- if (props.showConclusion !== false && props.conclusion) {
193
- const c = props.conclusion;
194
- addTheme07Card(slide, 0.65, 2.55, 4.7, 1.5, 0.08, COLORS.white);
195
- slide.addShape('rect', {
196
- x: 0.65, y: 2.55, w: 0.06, h: 1.5,
197
- fill: { color: COLORS.accent },
198
- });
199
- if (c.value) {
200
- slide.addText(c.value, {
201
- x: 0.85, y: 2.7, w: 4.3, h: 0.5,
202
- fontSize: 32, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
203
- });
204
- }
205
- if (c.label) {
206
- slide.addText(c.label, {
207
- x: 0.85, y: 3.2, w: 4.3, h: 0.25,
208
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono,
209
- });
210
- }
211
- if (c.description) {
212
- slide.addText(c.description, {
213
- x: 0.85, y: 3.5, w: 4.3, h: 0.5,
214
- fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
215
- });
216
- }
217
- }
218
- if (props.items && props.items.length > 0) {
219
- const startY = 0.95;
220
- const itemH = 0.75;
221
- props.items.slice(0, 6).forEach((item, idx) => {
222
- const y = startY + idx * itemH;
223
- addTheme07Card(slide, 5.6, y, 3.75, itemH - 0.1, 0.08, COLORS.white);
224
- slide.addText(String(idx + 1).padStart(2, '0'), {
225
- x: 5.75, y: y + 0.1, w: 0.5, h: 0.35,
226
- fontSize: 20, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
227
- });
228
- if (item.title) {
229
- slide.addText(item.title, {
230
- x: 6.35, y: y + 0.1, w: 2.85, h: 0.3,
231
- fontSize: 13, color: COLORS.primary, bold: true, fontFace: FONTS.body,
232
- });
233
- }
234
- if (item.desc) {
235
- slide.addText(item.desc, {
236
- x: 6.35, y: y + 0.4, w: 2.85, h: 0.25,
237
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
238
- });
239
- }
240
- });
241
- }
242
- }
243
- export function renderTheme07SummaryV1(slide, props) {
244
- if (props.kicker) {
245
- addTheme07Kicker(slide, props.kicker);
246
- }
247
- slide.addText(props.title ?? '', {
248
- x: 0.65, y: 0.95, w: 4.7, h: 0.7,
249
- fontSize: 32, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
250
- });
251
- if (props.subtitle) {
252
- slide.addText(props.subtitle, {
253
- x: 0.65, y: 1.7, w: 4.7, h: 0.4,
254
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
255
- });
256
- }
257
- slide.addShape('line', {
258
- x1: 0.65, y1: 2.2, x2: 1.5, y2: 2.2,
259
- line: { color: COLORS.accent, width: 3 },
260
- });
261
- if (props.points && props.points.length > 0) {
262
- const startY = 2.5;
263
- const pointH = 0.55;
264
- props.points.slice(0, 6).forEach((p, idx) => {
265
- const text = typeof p === 'string' ? p : p.text;
266
- const y = startY + idx * pointH;
267
- slide.addShape('ellipse', {
268
- x: 0.7, y: y + 0.1, w: 0.1, h: 0.1,
269
- fill: { color: COLORS.accent },
270
- });
271
- slide.addText(text ?? '', {
272
- x: 0.95, y: y, w: 4.4, h: 0.5,
273
- fontSize: 13, color: COLORS.primary, fontFace: FONTS.body, valign: 'top',
274
- });
275
- });
276
- }
277
- // 右侧指标卡
278
- if (props.metrics && props.metrics.length > 0) {
279
- const cardW = 1.78;
280
- const cardH = 0.85;
281
- props.metrics.slice(0, 4).forEach((m, idx) => {
282
- const col = idx % 2;
283
- const row = Math.floor(idx / 2);
284
- const x = 5.6 + col * (cardW + 0.12);
285
- const y = 0.95 + row * (cardH + 0.12);
286
- addTheme07Card(slide, x, y, cardW, cardH, 0.06, COLORS.white);
287
- slide.addText(m.value ?? '', {
288
- x: x + 0.1, y: y + 0.1, w: cardW - 0.2, h: 0.35,
289
- fontSize: 22, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
290
- });
291
- slide.addText(m.label ?? '', {
292
- x: x + 0.1, y: y + 0.48, w: cardW - 0.2, h: 0.25,
293
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.mono,
294
- });
295
- });
296
- }
297
- // 右侧结论面板
298
- if (props.conclusion) {
299
- addTheme07Card(slide, 5.6, 2.95, 3.75, 1.9, 0.08, COLORS.white);
300
- slide.addShape('rect', {
301
- x: 5.6, y: 2.95, w: 0.06, h: 1.9,
302
- fill: { color: COLORS.accent },
303
- });
304
- slide.addText('CONCLUSION', {
305
- x: 5.85, y: 3.1, w: 3.3, h: 0.3,
306
- fontSize: 11, color: COLORS.accent, bold: true, fontFace: FONTS.mono, charSpacing: 2,
307
- });
308
- slide.addText(props.conclusion, {
309
- x: 5.85, y: 3.45, w: 3.3, h: 1.3,
310
- fontSize: 17, color: COLORS.primary, bold: true, fontFace: FONTS.heading, valign: 'top',
311
- });
312
- }
313
- if (props.footnote) {
314
- slide.addText(props.footnote, {
315
- x: 0.65, y: 4.95, w: 8.7, h: 0.3,
316
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
317
- });
318
- }
319
- addTheme07GlowLine(slide);
320
- }
321
- export function renderTheme07RankingV1(slide, props) {
322
- if (props.kicker) {
323
- addTheme07Kicker(slide, props.kicker);
324
- }
325
- slide.addText(props.title ?? '', {
326
- x: 0.65, y: 0.9, w: 8.7, h: 0.7,
327
- fontSize: 32, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
328
- });
329
- if (props.subtitle) {
330
- slide.addText(props.subtitle, {
331
- x: 0.65, y: 1.6, w: 8.7, h: 0.4,
332
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
333
- });
334
- }
335
- slide.addShape('line', {
336
- x1: 0.65, y1: 2.1, x2: 1.5, y2: 2.1,
337
- line: { color: COLORS.accent, width: 3 },
338
- });
339
- if (props.entries && props.entries.length > 0) {
340
- const startY = 2.35;
341
- const rowH = 0.48;
342
- const maxValue = Math.max(1, ...props.entries.map((e) => parseFloat(e.value) || 0));
343
- props.entries.slice(0, 10).forEach((entry, idx) => {
344
- const y = startY + idx * rowH;
345
- const isAccent = entry.accent === true;
346
- const progress = entry.progress ?? Math.round(((parseFloat(entry.value) || 0) / maxValue) * 100);
347
- if (isAccent) {
348
- slide.addShape('roundRect', {
349
- x: 0.65, y, w: 8.7, h: rowH - 0.06,
350
- fill: { color: COLORS.accent },
351
- rectRadius: 0.06,
352
- });
353
- }
354
- else {
355
- addTheme07Card(slide, 0.65, y, 8.7, rowH - 0.06, 0.06, COLORS.white);
356
- }
357
- const numColor = isAccent ? COLORS.white : COLORS.accent;
358
- const textColor = isAccent ? COLORS.white : COLORS.primary;
359
- const subColor = isAccent ? COLORS.white : COLORS.secondary;
360
- slide.addText(String(idx + 1).padStart(2, '0'), {
361
- x: 0.8, y: y + 0.06, w: 0.55, h: 0.3,
362
- fontSize: 17, color: numColor, bold: true, fontFace: FONTS.heading,
363
- });
364
- slide.addText(entry.name ?? '', {
365
- x: 1.45, y: y + 0.08, w: 4.5, h: 0.3,
366
- fontSize: 14, color: textColor, bold: true, fontFace: FONTS.body,
367
- });
368
- if (entry.note) {
369
- slide.addText(entry.note, {
370
- x: 1.45, y: y + 0.28, w: 4.5, h: 0.15,
371
- fontSize: 9, color: subColor, fontFace: FONTS.mono,
372
- });
373
- }
374
- // 进度条背景
375
- slide.addShape('roundRect', {
376
- x: 5.8, y: y + 0.14, w: 2.4, h: 0.08,
377
- fill: { color: isAccent ? '5A7FA8' : 'E5E8ED' },
378
- rectRadius: 0.04,
379
- });
380
- // 进度条填充
381
- if (progress > 0) {
382
- slide.addShape('roundRect', {
383
- x: 5.8, y: y + 0.14, w: 2.4 * (progress / 100), h: 0.08,
384
- fill: { color: isAccent ? COLORS.white : COLORS.accent },
385
- rectRadius: 0.04,
386
- });
387
- }
388
- slide.addText(`${entry.value ?? ''}${props.unit ?? ''}`, {
389
- x: 8.3, y: y + 0.04, w: 0.9, h: 0.3,
390
- fontSize: 15, color: isAccent ? COLORS.white : COLORS.accent, bold: true, fontFace: FONTS.mono, align: 'right',
391
- });
392
- });
393
- }
394
- if (props.legend && props.legend.length > 0) {
395
- let lx = 0.65;
396
- props.legend.slice(0, 4).forEach((item) => {
397
- slide.addShape('rect', {
398
- x: lx, y: 4.85, w: 0.12, h: 0.12,
399
- fill: { color: resolveTheme07CssColor(item.color) },
400
- });
401
- slide.addText(item.label ?? '', {
402
- x: lx + 0.2, y: 4.82, w: 1.6, h: 0.2,
403
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.mono,
404
- });
405
- lx += 1.8;
406
- });
407
- }
408
- if (props.footnote) {
409
- slide.addText(props.footnote, {
410
- x: 0.65, y: 5.15, w: 8.7, h: 0.25,
411
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
412
- });
413
- }
414
- addTheme07GlowLine(slide);
415
- }
416
- export function renderTheme07CaseV1(slide, props) {
417
- if (props.imageUrl) {
418
- addImageMaybe(slide, props.imageUrl, 0, 0, 10, 5.625);
419
- }
420
- if (props.kicker) {
421
- addTheme07Kicker(slide, props.kicker);
422
- }
423
- slide.addText(props.title ?? '', {
424
- x: 0.65, y: 0.95, w: 4.7, h: 0.6,
425
- fontSize: 28, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
426
- });
427
- if (props.subtitle) {
428
- slide.addText(props.subtitle, {
429
- x: 0.65, y: 1.55, w: 4.7, h: 0.3,
430
- fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
431
- });
432
- }
433
- if (props.description) {
434
- slide.addText(props.description, {
435
- x: 0.65, y: 1.95, w: 4.7, h: 0.6,
436
- fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
437
- });
438
- }
439
- // 案例卡片
440
- if (props.cards && props.cards.length > 0) {
441
- const cardW = 2.85;
442
- const cardH = 3.3;
443
- const startY = 0.95;
444
- props.cards.slice(0, 3).forEach((card, idx) => {
445
- const x = 5.6 + idx * (cardW + 0.15);
446
- const fill = card.accent ? COLORS.accent : COLORS.white;
447
- addTheme07Card(slide, x, startY, cardW, cardH, 0.08, fill);
448
- const textColor = card.accent ? COLORS.white : COLORS.primary;
449
- const subColor = card.accent ? 'D0E0F0' : COLORS.secondary;
450
- // 卡片顶部图片区域(DROP MEDIA 占位)
451
- const imgAreaH = 1.35;
452
- const imgFill = card.accent ? '#E8F0E8' : '#F0F4EF';
453
- slide.addShape('roundRect', {
454
- x: x + 0.08, y: startY + 0.08, w: cardW - 0.16, h: imgAreaH - 0.06,
455
- fill: { color: imgFill }, rectRadius: 0.06,
456
- line: { color: card.accent ? '#B8CCB8' : '#D8E8D8', width: 0.5 },
457
- });
458
- if (card.imageUrl) {
459
- addImageMaybe(slide, card.imageUrl, x + 0.1, startY + 0.1, cardW - 0.2, imgAreaH - 0.1);
460
- }
461
- else {
462
- // DROP MEDIA 文字
463
- if (card.accent) {
464
- slide.addShape('roundRect', {
465
- x: x + 0.15, y: startY + 0.15, w: 0.55, h: 0.22,
466
- fill: { color: '#7B9E7B' }, rectRadius: 0.11,
467
- });
468
- slide.addText('重点', {
469
- x: x + 0.15, y: startY + 0.16, w: 0.55, h: 0.2,
470
- fontSize: 8, color: COLORS.white, bold: true, fontFace: FONTS.mono, align: 'center',
471
- });
472
- }
473
- slide.addText('DROP MEDIA', {
474
- x: x, y: startY + imgAreaH * 0.48, w: cardW, h: 0.22,
475
- fontSize: 9, color: '#A0C0A0', fontFace: FONTS.mono, align: 'center', charSpacing: 1.5,
476
- });
477
- if (card.company) {
478
- slide.addText(card.company, {
479
- x: x + 0.12, y: startY + imgAreaH - 0.32, w: cardW - 0.24, h: 0.22,
480
- fontSize: 11, bold: true, fontFace: FONTS.heading, color: subColor,
481
- });
482
- }
483
- }
484
- // 卡片内容从图片区域下方开始
485
- const contentStartY = startY + imgAreaH + 0.05;
486
- slide.addText(`案例 ${String(idx + 1).padStart(2, '0')}`, {
487
- x: x + 0.15, y: contentStartY, w: cardW - 0.3, h: 0.25,
488
- fontSize: 10, color: card.accent ? COLORS.white : COLORS.accent, bold: true, fontFace: FONTS.mono, charSpacing: 2,
489
- });
490
- if (card.company) {
491
- slide.addText(card.company, {
492
- x: x + 0.15, y: contentStartY + 0.3, w: cardW - 0.3, h: 0.4,
493
- fontSize: 20, color: textColor, bold: true, fontFace: FONTS.heading,
494
- });
495
- }
496
- if (card.tagline) {
497
- slide.addText(card.tagline, {
498
- x: x + 0.15, y: contentStartY + 0.7, w: cardW - 0.3, h: 0.28,
499
- fontSize: 11, color: subColor, fontFace: FONTS.body, valign: 'top',
500
- });
501
- }
502
- const tags = (card.tags || []).map((t) => typeof t === 'string' ? t : t.item).filter(Boolean);
503
- if (tags.length > 0) {
504
- let tx = x + 0.15;
505
- tags.slice(0, 4).forEach((tag) => {
506
- const tw = 0.25 + tag.length * 0.11;
507
- slide.addShape('roundRect', {
508
- x: tx, y: contentStartY + 1.02, w: tw, h: 0.26,
509
- fill: { color: card.accent ? '3A6A99' : COLORS.surface },
510
- rectRadius: 0.04,
511
- });
512
- slide.addText(tag, {
513
- x: tx + 0.03, y: contentStartY + 1.04, w: tw - 0.06, h: 0.22,
514
- fontSize: 9, color: card.accent ? COLORS.white : COLORS.secondary, fontFace: FONTS.mono, align: 'center',
515
- });
516
- tx += tw + 0.06;
517
- });
518
- }
519
- if (card.metricValue || card.metricLabel) {
520
- slide.addShape('line', {
521
- x1: x + 0.15, y1: startY + cardH - 0.85, x2: x + cardW - 0.15, y2: startY + cardH - 0.85,
522
- line: { color: card.accent ? '5A7FA8' : COLORS.border, width: 1 },
523
- });
524
- slide.addText(card.metricValue ?? '', {
525
- x: x + 0.15, y: startY + cardH - 0.75, w: cardW - 0.3, h: 0.4,
526
- fontSize: 24, color: card.accent ? COLORS.white : COLORS.accent, bold: true, fontFace: FONTS.heading,
527
- });
528
- slide.addText(card.metricLabel ?? '', {
529
- x: x + 0.15, y: startY + cardH - 0.35, w: cardW - 0.3, h: 0.25,
530
- fontSize: 9, color: subColor, fontFace: FONTS.mono,
531
- });
532
- }
533
- });
534
- }
535
- if (props.footnote) {
536
- slide.addText(props.footnote, {
537
- x: 0.65, y: 4.95, w: 8.7, h: 0.3,
538
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
539
- });
540
- }
541
- addTheme07GlowLine(slide);
542
- }
543
- export function renderTheme07SourcesV1(slide, props) {
544
- if (props.kicker) {
545
- addTheme07Kicker(slide, props.kicker);
546
- }
547
- slide.addText(props.title ?? '', {
548
- x: 0.65, y: 0.9, w: 7.2, h: 0.7,
549
- fontSize: 32, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
550
- });
551
- if (props.sampleSize?.value) {
552
- slide.addText(props.sampleSize.value, {
553
- x: 8.1, y: 0.85, w: 1.25, h: 0.5,
554
- fontSize: 40, color: COLORS.accent, bold: true, fontFace: FONTS.heading, align: 'right',
555
- });
556
- slide.addText(props.sampleSize.label ?? '', {
557
- x: 7.6, y: 1.35, w: 1.75, h: 0.3,
558
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.mono, align: 'right',
559
- });
560
- }
561
- if (props.subtitle) {
562
- slide.addText(props.subtitle, {
563
- x: 0.65, y: 1.6, w: 8.7, h: 0.4,
564
- fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
565
- });
566
- }
567
- slide.addShape('line', {
568
- x1: 0.65, y1: 2.1, x2: 1.5, y2: 2.1,
569
- line: { color: COLORS.accent, width: 3 },
570
- });
571
- if (props.entries && props.entries.length > 0) {
572
- const colW = 4.2;
573
- const rowH = 0.65;
574
- const startY = 2.35;
575
- props.entries.slice(0, 16).forEach((entry, idx) => {
576
- const col = idx % 2;
577
- const row = Math.floor(idx / 2);
578
- const x = 0.65 + col * (colW + 0.3);
579
- const y = startY + row * rowH;
580
- addTheme07Card(slide, x, y, colW, rowH - 0.08, 0.06, COLORS.white);
581
- slide.addText(String(idx + 1).padStart(2, '0'), {
582
- x: x + 0.15, y: y + 0.08, w: 0.45, h: 0.3,
583
- fontSize: 12, color: COLORS.accent, bold: true, fontFace: FONTS.mono,
584
- });
585
- slide.addText(entry.name ?? '', {
586
- x: x + 0.65, y: y + 0.05, w: colW - 0.8, h: 0.25,
587
- fontSize: 12, color: COLORS.primary, bold: true, fontFace: FONTS.heading,
588
- });
589
- if (entry.note) {
590
- slide.addText(entry.note, {
591
- x: x + 0.65, y: y + 0.28, w: colW - 0.8, h: 0.18,
592
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.body,
593
- });
594
- }
595
- if (typeof entry.confidence === 'number') {
596
- const pct = Math.max(0, Math.min(100, entry.confidence));
597
- slide.addShape('roundRect', {
598
- x: x + colW - 1.05, y: y + 0.38, w: 0.9, h: 0.08,
599
- fill: { color: 'E5E8ED' },
600
- rectRadius: 0.04,
601
- });
602
- slide.addShape('roundRect', {
603
- x: x + colW - 1.05, y: y + 0.38, w: 0.9 * (pct / 100), h: 0.08,
604
- fill: { color: COLORS.accent2 },
605
- rectRadius: 0.04,
606
- });
607
- slide.addText(`${pct}/100`, {
608
- x: x + colW - 1.05, y: y + 0.46, w: 0.9, h: 0.15,
609
- fontSize: 8, color: COLORS.secondary, fontFace: FONTS.mono, align: 'right',
610
- });
611
- }
612
- });
613
- }
614
- // 处理流程
615
- if (props.process && props.process.length > 0) {
616
- const steps = props.process.map((p) => typeof p === 'string' ? p : p.item).filter(Boolean);
617
- let px = 0.65;
618
- steps.slice(0, 6).forEach((step, idx) => {
619
- const pw = 0.45 + step.length * 0.12;
620
- slide.addShape('roundRect', {
621
- x: px, y: 4.55, w: pw, h: 0.32,
622
- fill: { color: COLORS.white },
623
- line: { color: COLORS.border, width: 1 },
624
- rectRadius: 0.04,
625
- });
626
- slide.addText(`${String(idx + 1).padStart(2, '0')} ${step}`, {
627
- x: px + 0.05, y: 4.58, w: pw - 0.1, h: 0.26,
628
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center',
629
- });
630
- px += pw + 0.2;
631
- if (idx < steps.length - 1) {
632
- slide.addText('→', {
633
- x: px - 0.16, y: 4.55, w: 0.12, h: 0.32,
634
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center',
635
- });
636
- }
637
- });
638
- }
639
- if (props.footnote) {
640
- slide.addShape('line', {
641
- x1: 0.65, y1: 4.9, x2: 9.35, y2: 4.9,
642
- line: { color: COLORS.border, width: 1 },
643
- });
644
- slide.addText(props.footnote, {
645
- x: 0.65, y: 5.0, w: 8.7, h: 0.35,
646
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
647
- });
648
- }
649
- addTheme07GlowLine(slide);
650
- }
651
- export function renderTheme07ChapterCapitalV1(slide, props) {
652
- renderTheme07ChapterV1(slide, props);
653
- }
654
- export function renderTheme07ChapterRiskV1(slide, props) {
655
- renderTheme07ChapterV1(slide, props);
656
- }
657
- export function renderTheme07ChapterAppendixV1(slide, props) {
658
- renderTheme07ChapterV1(slide, props);
659
- }
660
- export function renderTheme07MethodV1(slide, props) {
661
- if (props.kicker) {
662
- addTheme07Kicker(slide, props.kicker);
663
- }
664
- slide.addText(props.title ?? '', {
665
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
666
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
667
- });
668
- if (props.subtitle) {
669
- slide.addText(props.subtitle, {
670
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
671
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
672
- });
673
- }
674
- slide.addShape('line', {
675
- x1: 0.65, y1: 2.15, x2: 1.5, y2: 2.15,
676
- line: { color: COLORS.accent, width: 3 },
677
- });
678
- const steps = (props.steps || []).slice(0, 4);
679
- if (steps.length > 0) {
680
- const cardW = 8.7 / steps.length;
681
- const gap = 0.14;
682
- const usableW = cardW - gap;
683
- steps.forEach((step, idx) => {
684
- const x = 0.65 + idx * cardW;
685
- const y = 2.45;
686
- addTheme07Card(slide, x, y, usableW, 2.55, 0.08, COLORS.white);
687
- const num = (step.number || String(idx + 1).padStart(2, '0'));
688
- slide.addText(num, {
689
- x: x + 0.12, y: y + 0.12, w: usableW - 0.24, h: 0.4,
690
- fontSize: 24, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
691
- });
692
- if (step.title) {
693
- slide.addText(step.title, {
694
- x: x + 0.12, y: y + 0.62, w: usableW - 0.24, h: 0.35,
695
- fontSize: 14, color: COLORS.primary, bold: true, fontFace: FONTS.body,
696
- });
697
- }
698
- if (step.description) {
699
- slide.addText(step.description, {
700
- x: x + 0.12, y: y + 1.05, w: usableW - 0.24, h: 1.3,
701
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
702
- });
703
- }
704
- if (idx < steps.length - 1) {
705
- slide.addText('→', {
706
- x: x + usableW + 0.02, y: y + 1.1, w: gap + 0.02, h: 0.3,
707
- fontSize: 12, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center',
708
- });
709
- }
710
- });
711
- }
712
- if (props.footnote) {
713
- slide.addText(props.footnote, {
714
- x: 0.65, y: 5.05, w: 8.7, h: 0.25,
715
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
716
- });
717
- }
718
- addTheme07GlowLine(slide);
719
- }
720
- export function renderTheme07MonthlyV1(slide, props) {
721
- if (props.kicker) {
722
- addTheme07Kicker(slide, props.kicker);
723
- }
724
- slide.addText(props.title ?? '', {
725
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
726
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
727
- });
728
- if (props.subtitle) {
729
- slide.addText(props.subtitle, {
730
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
731
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
732
- });
733
- }
734
- const showConclusion = props.showConclusion !== false;
735
- const hasConclusion = showConclusion && (props.conclusionValue || props.conclusionLabel || props.conclusionDescription);
736
- const chartW = hasConclusion ? 5.6 : 8.7;
737
- const labels = (props.labels || []).slice(0, 24).map((it) => (typeof it === 'string' ? it : it?.item ?? ''));
738
- const series = (props.series || []).slice(0, 4);
739
- if (labels.length >= 2 && series.length > 0) {
740
- const chartData = series.map((s, idx) => {
741
- const values = (s.data || []).map((d) => (typeof d === 'number' ? d : Number(d?.item ?? 0) || 0));
742
- return {
743
- name: s.name || `系列 ${idx + 1}`,
744
- labels,
745
- values,
746
- };
747
- });
748
- slide.addChart('line', chartData, {
749
- x: 0.65, y: 2.25, w: chartW, h: 3.0,
750
- chartColors: ['2E5C8A', '7B9E7B', 'B8623E', '5B9BD5'],
751
- lineDataSymbol: 'circle',
752
- lineSmooth: true,
753
- showLegend: series.length > 1,
754
- showValue: false,
755
- });
756
- }
757
- else {
758
- slide.addText('(暂无趋势数据)', {
759
- x: 0.65, y: 3.0, w: chartW, h: 0.6,
760
- fontSize: 16, color: COLORS.secondary, align: 'center', valign: 'middle', fontFace: FONTS.body,
761
- });
762
- }
763
- if (props.unit) {
764
- addTheme07Card(slide, 6.45, 2.25, 2.9, 0.7, 0.08, COLORS.white);
765
- slide.addText('单位', {
766
- x: 6.6, y: 2.32, w: 2.6, h: 0.2,
767
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body,
768
- });
769
- slide.addText(props.unit, {
770
- x: 6.6, y: 2.52, w: 2.6, h: 0.35,
771
- fontSize: 18, color: COLORS.primary, bold: true, fontFace: FONTS.heading,
772
- });
773
- }
774
- if (hasConclusion) {
775
- const panelY = props.unit ? 3.05 : 2.25;
776
- const panelH = props.unit ? 2.2 : 3.0;
777
- addTheme07Card(slide, 6.45, panelY, 2.9, panelH, 0.08, COLORS.accent);
778
- let cursorY = panelY + 0.18;
779
- if (props.conclusionValue) {
780
- slide.addText(props.conclusionValue, {
781
- x: 6.65, y: cursorY, w: 2.5, h: 0.55,
782
- fontSize: 32, color: COLORS.white, bold: true, fontFace: FONTS.heading,
783
- });
784
- cursorY += 0.58;
785
- }
786
- if (props.conclusionLabel) {
787
- slide.addText(props.conclusionLabel, {
788
- x: 6.65, y: cursorY, w: 2.5, h: 0.25,
789
- fontSize: 10, color: 'FFFFFF', fontFace: FONTS.body,
790
- });
791
- cursorY += 0.32;
792
- }
793
- if (props.conclusionDescription) {
794
- slide.addText(props.conclusionDescription, {
795
- x: 6.65, y: cursorY, w: 2.5, h: panelH - (cursorY - panelY) - 0.25,
796
- fontSize: 10, color: 'FFFFFF', valign: 'top', fontFace: FONTS.body,
797
- });
798
- }
799
- }
800
- addTheme07GlowLine(slide);
801
- }
802
- export function renderTheme07WaterfallV1(slide, props) {
803
- if (props.kicker) {
804
- addTheme07Kicker(slide, props.kicker);
805
- }
806
- slide.addText(props.title ?? '', {
807
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
808
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
809
- });
810
- if (props.subtitle) {
811
- slide.addText(props.subtitle, {
812
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
813
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
814
- });
815
- }
816
- const showConclusion = props.showConclusion !== false;
817
- const hasConclusion = showConclusion && (props.conclusionValue || props.conclusionLabel || props.conclusionDescription);
818
- const chartW = hasConclusion ? 5.6 : 8.7;
819
- const labels = (props.labels || []).slice(0, 12).map((it) => (typeof it === 'string' ? it : it?.item ?? ''));
820
- const values = (props.values || []).slice(0, 12).map((it) => (typeof it === 'number' ? it : Number(it?.item ?? 0) || 0));
821
- if (labels.length >= 2 && values.length >= 2) {
822
- let cumulative = 0;
823
- const baseData = [];
824
- const positiveData = [];
825
- const negativeData = [];
826
- values.forEach((v) => {
827
- baseData.push(cumulative);
828
- if (v >= 0) {
829
- positiveData.push(v);
830
- negativeData.push(0);
831
- }
832
- else {
833
- positiveData.push(0);
834
- negativeData.push(Math.abs(v));
835
- }
836
- cumulative += v;
837
- });
838
- slide.addChart('bar', [
839
- { name: '基础', labels, values: baseData },
840
- { name: '增加', labels, values: positiveData },
841
- { name: '减少', labels, values: negativeData },
842
- ], {
843
- x: 0.65, y: 2.25, w: chartW, h: 3.0,
844
- chartColors: [COLORS.surface, COLORS.accent2, COLORS.accentCool],
845
- showValue: true,
846
- dataLabelColor: COLORS.primary,
847
- dataLabelFontSize: 9,
848
- barGrouping: 'stacked',
849
- barDir: 'col',
850
- dataLabelPosition: 'outEnd',
851
- });
852
- }
853
- else {
854
- slide.addText('(暂无瀑布图数据)', {
855
- x: 0.65, y: 3.0, w: chartW, h: 0.6,
856
- fontSize: 16, color: COLORS.secondary, align: 'center', valign: 'middle', fontFace: FONTS.body,
857
- });
858
- }
859
- if (props.unit) {
860
- addTheme07Card(slide, 6.45, 2.25, 2.9, 0.7, 0.08, COLORS.white);
861
- slide.addText('单位', {
862
- x: 6.6, y: 2.32, w: 2.6, h: 0.2,
863
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body,
864
- });
865
- slide.addText(props.unit, {
866
- x: 6.6, y: 2.52, w: 2.6, h: 0.35,
867
- fontSize: 18, color: COLORS.primary, bold: true, fontFace: FONTS.heading,
868
- });
869
- }
870
- if (hasConclusion) {
871
- const panelY = props.unit ? 3.05 : 2.25;
872
- const panelH = props.unit ? 2.2 : 3.0;
873
- addTheme07Card(slide, 6.45, panelY, 2.9, panelH, 0.08, COLORS.accent);
874
- let cursorY = panelY + 0.18;
875
- if (props.conclusionValue) {
876
- slide.addText(props.conclusionValue, {
877
- x: 6.65, y: cursorY, w: 2.5, h: 0.55,
878
- fontSize: 32, color: COLORS.white, bold: true, fontFace: FONTS.heading,
879
- });
880
- cursorY += 0.58;
881
- }
882
- if (props.conclusionLabel) {
883
- slide.addText(props.conclusionLabel, {
884
- x: 6.65, y: cursorY, w: 2.5, h: 0.25,
885
- fontSize: 10, color: 'FFFFFF', fontFace: FONTS.body,
886
- });
887
- cursorY += 0.32;
888
- }
889
- if (props.conclusionDescription) {
890
- slide.addText(props.conclusionDescription, {
891
- x: 6.65, y: cursorY, w: 2.5, h: panelH - (cursorY - panelY) - 0.25,
892
- fontSize: 10, color: 'FFFFFF', valign: 'top', fontFace: FONTS.body,
893
- });
894
- }
895
- }
896
- addTheme07GlowLine(slide);
897
- }
898
- export function renderTheme07MatrixV1(slide, props) {
899
- if (props.kicker) {
900
- addTheme07Kicker(slide, props.kicker);
901
- }
902
- slide.addText(props.title ?? '', {
903
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
904
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
905
- });
906
- if (props.subtitle) {
907
- slide.addText(props.subtitle, {
908
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
909
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
910
- });
911
- }
912
- if (props.xAxisLabel) {
913
- slide.addText(props.xAxisLabel, {
914
- x: 7.2, y: 5.15, w: 2.2, h: 0.25,
915
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, align: 'right',
916
- });
917
- }
918
- if (props.yAxisLabel) {
919
- slide.addText(props.yAxisLabel, {
920
- x: 0.65, y: 0.55, w: 1.5, h: 0.25,
921
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono,
922
- });
923
- }
924
- const cells = (props.cells || []).slice(0, 4);
925
- const focusIndex = Math.max(0, Math.min(3, Number(props.focusIndex ?? 0)));
926
- if (cells.length === 4) {
927
- const cellW = 4.2;
928
- const cellH = 1.65;
929
- const startX = 0.65;
930
- const startY = 2.2;
931
- cells.forEach((cell, idx) => {
932
- const col = idx % 2;
933
- const row = Math.floor(idx / 2);
934
- const x = startX + col * (cellW + 0.18);
935
- const y = startY + row * (cellH + 0.18);
936
- const isFocus = cell.focus === true || idx === focusIndex;
937
- addTheme07Card(slide, x, y, cellW, cellH, 0.08, isFocus ? COLORS.accent : COLORS.white);
938
- const textColor = isFocus ? COLORS.white : COLORS.primary;
939
- const subColor = isFocus ? 'D0E0F0' : COLORS.secondary;
940
- if (cell.title) {
941
- slide.addText(cell.title, {
942
- x: x + 0.15, y: y + 0.12, w: cellW - 0.3, h: 0.35,
943
- fontSize: 16, color: textColor, bold: true, fontFace: FONTS.heading,
944
- });
945
- }
946
- if (cell.description) {
947
- slide.addText(cell.description, {
948
- x: x + 0.15, y: y + 0.55, w: cellW - 0.3, h: 0.95,
949
- fontSize: 10, color: subColor, fontFace: FONTS.body, valign: 'top',
950
- });
951
- }
952
- });
953
- }
954
- // 轴线(与 HTML 十字轴对齐):象限分隔虚线 + 箭头
955
- slide.addShape('line', { x1: 4.94, y1: 2.15, x2: 4.94, y2: 5.68, line: { color: COLORS.accent, width: 1, dashType: 'dash' } });
956
- slide.addShape('line', { x1: 0.6, y1: 3.915, x2: 9.28, y2: 3.915, line: { color: COLORS.accent, width: 1, dashType: 'dash' } });
957
- slide.addShape('triangle', { x: 9.2, y: 3.83, w: 0.14, h: 0.18, fill: { color: COLORS.accent }, rotate: 90 });
958
- slide.addShape('triangle', { x: 4.86, y: 1.98, w: 0.18, h: 0.14, fill: { color: COLORS.accent } });
959
- addTheme07GlowLine(slide);
960
- }
961
- export function renderTheme07RiskV1(slide, props) {
962
- if (props.kicker) {
963
- addTheme07Kicker(slide, props.kicker);
964
- }
965
- slide.addText(props.title ?? '', {
966
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
967
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
968
- });
969
- if (props.subtitle) {
970
- slide.addText(props.subtitle, {
971
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
972
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
973
- });
974
- }
975
- slide.addShape('line', {
976
- x1: 0.65, y1: 2.15, x2: 1.5, y2: 2.15,
977
- line: { color: COLORS.accent, width: 3 },
978
- });
979
- const items = (props.items || []).slice(0, 6);
980
- if (items.length > 0) {
981
- const colW = 4.2;
982
- const rowH = 1.05;
983
- const startY = 2.4;
984
- items.forEach((item, idx) => {
985
- const col = idx % 2;
986
- const row = Math.floor(idx / 2);
987
- const x = 0.65 + col * (colW + 0.18);
988
- const y = startY + row * rowH;
989
- const level = item.level === 'high' ? 'high' : item.level === 'low' ? 'low' : 'medium';
990
- const accentColor = level === 'high' ? COLORS.accentCool : level === 'medium' ? 'B45309' : COLORS.accent2;
991
- addTheme07Card(slide, x, y, colW, rowH - 0.1, 0.08, COLORS.white);
992
- slide.addShape('rect', {
993
- x, y, w: 0.05, h: rowH - 0.1,
994
- fill: { color: accentColor },
995
- });
996
- slide.addText(item.risk ?? '', {
997
- x: x + 0.15, y: y + 0.1, w: colW - 0.3, h: 0.3,
998
- fontSize: 13, color: COLORS.primary, bold: true, fontFace: FONTS.heading,
999
- });
1000
- slide.addText(`影响:${item.impact ?? '中'}`, {
1001
- x: x + 0.15, y: y + 0.38, w: colW - 0.3, h: 0.22,
1002
- fontSize: 10, color: accentColor, fontFace: FONTS.mono,
1003
- });
1004
- if (item.response) {
1005
- slide.addText(`应对:${item.response}`, {
1006
- x: x + 0.15, y: y + 0.58, w: colW - 0.3, h: 0.35,
1007
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1008
- });
1009
- }
1010
- });
1011
- }
1012
- if (props.footnote) {
1013
- slide.addText(props.footnote, {
1014
- x: 0.65, y: 5.05, w: 8.7, h: 0.25,
1015
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1016
- });
1017
- }
1018
- addTheme07GlowLine(slide);
1019
- }
1020
- export function renderTheme07CoverLeanV1(slide, props) {
1021
- if (props.imageUrl) {
1022
- addImageMaybe(slide, props.imageUrl, 0, 0, 10, 5.625);
1023
- }
1024
- if (props.badge) {
1025
- addTheme07Kicker(slide, props.badge);
1026
- }
1027
- if (props.headline) {
1028
- slide.addText(props.headline, {
1029
- x: 0.65, y: 0.9, w: 8.7, h: 0.35,
1030
- fontSize: 16, color: COLORS.accent, bold: true, fontFace: FONTS.mono, charSpacing: 2,
1031
- });
1032
- }
1033
- const titleY = props.headline ? 1.35 : 0.95;
1034
- slide.addText(props.title ?? '', {
1035
- x: 0.65, y: titleY, w: 7.2, h: 0.9,
1036
- fontSize: 38, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1037
- });
1038
- if (props.subtitle) {
1039
- slide.addText(props.subtitle, {
1040
- x: 0.65, y: titleY + 1.0, w: 6.5, h: 0.4,
1041
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1042
- });
1043
- }
1044
- const metrics = (props.metrics || []).slice(0, 3);
1045
- if (metrics.length > 0) {
1046
- const cardW = 2.75;
1047
- const startX = 0.65;
1048
- const gap = (8.7 - cardW * metrics.length) / (metrics.length - 1 || 1);
1049
- metrics.forEach((m, idx) => {
1050
- const x = metrics.length === 1 ? startX : startX + idx * (cardW + gap);
1051
- const y = 4.2;
1052
- addTheme07Card(slide, x, y, cardW, 0.95, 0.08, COLORS.white);
1053
- slide.addText(`${m.value ?? ''}${m.unit ?? ''}`, {
1054
- x: x + 0.12, y: y + 0.1, w: cardW - 0.24, h: 0.4,
1055
- fontSize: 22, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
1056
- });
1057
- slide.addText(m.label ?? '', {
1058
- x: x + 0.12, y: y + 0.52, w: cardW - 0.24, h: 0.3,
1059
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.mono,
1060
- });
1061
- });
1062
- }
1063
- addTheme07Footer(slide, props);
1064
- addTheme07GlowLine(slide);
1065
- }
1066
- export function renderTheme07CoverSupplyChainV1(slide, props) {
1067
- if (props.imageUrl) {
1068
- addImageMaybe(slide, props.imageUrl, 0, 0, 10, 5.625);
1069
- }
1070
- if (props.badge) {
1071
- addTheme07Kicker(slide, props.badge);
1072
- }
1073
- if (props.headline) {
1074
- slide.addText(props.headline, {
1075
- x: 0.65, y: 0.9, w: 4.5, h: 0.35,
1076
- fontSize: 16, color: COLORS.accent, bold: true, fontFace: FONTS.mono, charSpacing: 2,
1077
- });
1078
- }
1079
- const titleY = props.headline ? 1.35 : 0.95;
1080
- slide.addText(props.title ?? '', {
1081
- x: 0.65, y: titleY, w: 4.5, h: 0.9,
1082
- fontSize: 34, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1083
- });
1084
- if (props.subtitle) {
1085
- slide.addText(props.subtitle, {
1086
- x: 0.65, y: titleY + 1.0, w: 4.3, h: 0.5,
1087
- fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1088
- });
1089
- }
1090
- const channels = (props.channels || []).slice(0, 6);
1091
- if (channels.length > 0) {
1092
- const colW = 2.1;
1093
- const rowH = 0.82;
1094
- const startX = 5.35;
1095
- const startY = 0.95;
1096
- channels.forEach((c, idx) => {
1097
- const col = idx % 2;
1098
- const row = Math.floor(idx / 2);
1099
- const x = startX + col * (colW + 0.12);
1100
- const y = startY + row * (rowH + 0.12);
1101
- addTheme07Card(slide, x, y, colW, rowH, 0.06, COLORS.white);
1102
- slide.addText(c.name ?? '', {
1103
- x: x + 0.1, y: y + 0.08, w: colW - 0.2, h: 0.3,
1104
- fontSize: 12, color: COLORS.primary, bold: true, fontFace: FONTS.heading,
1105
- });
1106
- slide.addText(c.metric ?? '', {
1107
- x: x + 0.1, y: y + 0.38, w: colW - 0.2, h: 0.3,
1108
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.mono,
1109
- });
1110
- });
1111
- }
1112
- addTheme07Footer(slide, props);
1113
- addTheme07GlowLine(slide);
1114
- }
1115
- export function renderTheme07CoverRetailTrendV1(slide, props) {
1116
- renderTheme07CoverV1(slide, props);
1117
- }
1118
- export function renderTheme07CoverSupplyStrategyV1(slide, props) {
1119
- if (props.imageUrl) {
1120
- addImageMaybe(slide, props.imageUrl, 0, 0, 10, 5.625);
1121
- }
1122
- if (props.badge) {
1123
- addTheme07Kicker(slide, props.badge);
1124
- }
1125
- if (props.hero) {
1126
- slide.addText(props.hero, {
1127
- x: 0.65, y: 1.0, w: 8.7, h: 0.4,
1128
- fontSize: 18, color: COLORS.accent, bold: true, fontFace: FONTS.mono, align: 'center', charSpacing: 2,
1129
- });
1130
- }
1131
- slide.addText(props.title ?? '', {
1132
- x: 0.65, y: 1.55, w: 8.7, h: 0.7,
1133
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, align: 'center', valign: 'top',
1134
- });
1135
- if (props.subtitle) {
1136
- slide.addText(props.subtitle, {
1137
- x: 1.5, y: 2.35, w: 7, h: 0.35,
1138
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, align: 'center', valign: 'top',
1139
- });
1140
- }
1141
- const kpis = (props.kpis || []).slice(0, 3);
1142
- if (kpis.length > 0) {
1143
- const cardW = 2.6;
1144
- const gap = (8.7 - cardW * kpis.length) / (kpis.length - 1 || 1);
1145
- kpis.forEach((k, idx) => {
1146
- const x = kpis.length === 1 ? 3.7 : 0.65 + idx * (cardW + gap);
1147
- const y = 3.4;
1148
- addTheme07Card(slide, x, y, cardW, 1.1, 0.08, COLORS.white);
1149
- slide.addText(`${k.value ?? ''}${k.unit ?? ''}`, {
1150
- x: x + 0.12, y: y + 0.15, w: cardW - 0.24, h: 0.5,
1151
- fontSize: 28, color: COLORS.accent, bold: true, fontFace: FONTS.heading, align: 'center',
1152
- });
1153
- slide.addText(k.label ?? '', {
1154
- x: x + 0.12, y: y + 0.7, w: cardW - 0.24, h: 0.3,
1155
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center',
1156
- });
1157
- });
1158
- }
1159
- addTheme07Footer(slide, props);
1160
- addTheme07GlowLine(slide);
1161
- }
1162
- export function renderTheme07PeakV1(slide, props) {
1163
- if (props.kicker) {
1164
- addTheme07Kicker(slide, props.kicker);
1165
- }
1166
- slide.addText(props.title ?? '', {
1167
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
1168
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1169
- });
1170
- if (props.subtitle) {
1171
- slide.addText(props.subtitle, {
1172
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
1173
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1174
- });
1175
- }
1176
- const labels = (props.labels || []).slice(0, 24).map((it) => (typeof it === 'string' ? it : it?.item ?? ''));
1177
- const data = (props.data || []).slice(0, 24).map((it) => (typeof it === 'number' ? it : Number(it?.item ?? 0) || 0));
1178
- const showConclusion = props.showConclusion !== false;
1179
- const hasConclusion = showConclusion && (props.conclusionValue || props.conclusionLabel || props.conclusionDescription);
1180
- const chartW = hasConclusion ? 5.6 : 8.7;
1181
- if (labels.length >= 2 && data.length >= 2) {
1182
- slide.addChart('line', [{ name: '趋势', labels, values: data }], {
1183
- x: 0.65, y: 2.25, w: chartW, h: 3.0,
1184
- chartColors: ['2E5C8A'],
1185
- lineDataSymbol: 'circle',
1186
- showValue: false,
1187
- });
1188
- }
1189
- if (props.unit) {
1190
- addTheme07Card(slide, 6.45, 2.25, 2.9, 0.7, 0.08, COLORS.white);
1191
- slide.addText('单位', { x: 6.6, y: 2.32, w: 2.6, h: 0.2, fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body });
1192
- slide.addText(props.unit, { x: 6.6, y: 2.52, w: 2.6, h: 0.35, fontSize: 18, color: COLORS.primary, bold: true, fontFace: FONTS.heading });
1193
- }
1194
- if (hasConclusion) {
1195
- const panelY = props.unit ? 3.05 : 2.25;
1196
- const panelH = props.unit ? 2.2 : 3.0;
1197
- addTheme07Card(slide, 6.45, panelY, 2.9, panelH, 0.08, COLORS.accent);
1198
- let cursorY = panelY + 0.18;
1199
- if (props.conclusionValue) {
1200
- slide.addText(props.conclusionValue, { x: 6.65, y: cursorY, w: 2.5, h: 0.55, fontSize: 32, color: COLORS.white, bold: true, fontFace: FONTS.heading });
1201
- cursorY += 0.58;
1202
- }
1203
- if (props.conclusionLabel) {
1204
- slide.addText(props.conclusionLabel, { x: 6.65, y: cursorY, w: 2.5, h: 0.25, fontSize: 10, color: 'FFFFFF', fontFace: FONTS.body });
1205
- cursorY += 0.32;
1206
- }
1207
- if (props.conclusionDescription) {
1208
- slide.addText(props.conclusionDescription, { x: 6.65, y: cursorY, w: 2.5, h: panelH - (cursorY - panelY) - 0.25, fontSize: 10, color: 'FFFFFF', valign: 'top', fontFace: FONTS.body });
1209
- }
1210
- }
1211
- addTheme07GlowLine(slide);
1212
- }
1213
- export function renderTheme07CooldownV1(slide, props) {
1214
- renderTheme07PeakV1(slide, props);
1215
- }
1216
- export function renderTheme07PeakTroughV1(slide, props) {
1217
- renderTheme07PeakV1(slide, props);
1218
- }
1219
- export function renderTheme07DealSizeV1(slide, props) {
1220
- if (props.kicker) {
1221
- addTheme07Kicker(slide, props.kicker);
1222
- }
1223
- slide.addText(props.title ?? '', {
1224
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
1225
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1226
- });
1227
- if (props.subtitle) {
1228
- slide.addText(props.subtitle, {
1229
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
1230
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1231
- });
1232
- }
1233
- const items = (props.items || []).slice(0, 8).filter((i) => i && i.name);
1234
- const hasConclusion = !!props.conclusion;
1235
- const chartW = hasConclusion ? 5.6 : 8.7;
1236
- if (items.length > 0) {
1237
- const labels = items.map((i) => i.name);
1238
- const values = items.map((i) => Number(i.value ?? 0) || 0);
1239
- slide.addChart('pie', [{ name: '规模', labels, values }], {
1240
- x: 0.65, y: 2.25, w: chartW, h: 3.0,
1241
- chartColors: ['2E5C8A', '7B9E7B', 'B8623E', '5B9BD5', '7D8FA3', '9AAFC4'],
1242
- showValue: true,
1243
- dataLabelColor: COLORS.primary,
1244
- dataLabelFontSize: 9,
1245
- });
1246
- }
1247
- if (props.unit) {
1248
- addTheme07Card(slide, 6.45, 2.25, 2.9, 0.7, 0.08, COLORS.white);
1249
- slide.addText('单位', { x: 6.6, y: 2.32, w: 2.6, h: 0.2, fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body });
1250
- slide.addText(props.unit, { x: 6.6, y: 2.52, w: 2.6, h: 0.35, fontSize: 18, color: COLORS.primary, bold: true, fontFace: FONTS.heading });
1251
- }
1252
- if (hasConclusion) {
1253
- const panelY = props.unit ? 3.05 : 2.25;
1254
- const panelH = props.unit ? 2.2 : 3.0;
1255
- addTheme07Card(slide, 6.45, panelY, 2.9, panelH, 0.08, COLORS.white);
1256
- slide.addText(props.conclusion, {
1257
- x: 6.65, y: panelY + 0.18, w: 2.5, h: panelH - 0.36,
1258
- fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1259
- });
1260
- }
1261
- addTheme07GlowLine(slide);
1262
- }
1263
- export function renderTheme07AvgTicketV1(slide, props) {
1264
- if (props.kicker) {
1265
- addTheme07Kicker(slide, props.kicker);
1266
- }
1267
- slide.addText(props.title ?? '', {
1268
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
1269
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1270
- });
1271
- if (props.subtitle) {
1272
- slide.addText(props.subtitle, {
1273
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
1274
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1275
- });
1276
- }
1277
- const intervals = (props.intervals || []).slice(0, 8).map((it) => (typeof it === 'string' ? it : it?.item ?? ''));
1278
- const values = (props.values || []).slice(0, 8).map((it) => (typeof it === 'number' ? it : Number(it?.item ?? 0) || 0));
1279
- const hasConclusion = !!props.conclusion;
1280
- const chartW = hasConclusion ? 5.6 : 8.7;
1281
- if (intervals.length > 0 && values.length > 0) {
1282
- slide.addChart('bar', [{ name: '数量', labels: intervals, values }], {
1283
- x: 0.65, y: 2.25, w: chartW, h: 3.0,
1284
- chartColors: ['2E5C8A'],
1285
- showValue: true,
1286
- dataLabelColor: COLORS.primary,
1287
- dataLabelFontSize: 9,
1288
- barGrouping: 'clustered',
1289
- barDir: 'col',
1290
- });
1291
- }
1292
- if (props.unit) {
1293
- addTheme07Card(slide, 6.45, 2.25, 2.9, 0.7, 0.08, COLORS.white);
1294
- slide.addText('单位', { x: 6.6, y: 2.32, w: 2.6, h: 0.2, fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body });
1295
- slide.addText(props.unit, { x: 6.6, y: 2.52, w: 2.6, h: 0.35, fontSize: 18, color: COLORS.primary, bold: true, fontFace: FONTS.heading });
1296
- }
1297
- if (hasConclusion) {
1298
- const panelY = props.unit ? 3.05 : 2.25;
1299
- const panelH = props.unit ? 2.2 : 3.0;
1300
- addTheme07Card(slide, 6.45, panelY, 2.9, panelH, 0.08, COLORS.white);
1301
- slide.addText(props.conclusion, {
1302
- x: 6.65, y: panelY + 0.18, w: 2.5, h: panelH - 0.36,
1303
- fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1304
- });
1305
- }
1306
- addTheme07GlowLine(slide);
1307
- }
1308
- export function renderTheme07OutlookV1(slide, props) {
1309
- if (props.kicker) {
1310
- addTheme07Kicker(slide, props.kicker);
1311
- }
1312
- slide.addText(props.title ?? '', {
1313
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
1314
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1315
- });
1316
- if (props.subtitle) {
1317
- slide.addText(props.subtitle, {
1318
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
1319
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1320
- });
1321
- }
1322
- slide.addShape('line', {
1323
- x1: 0.65, y1: 2.15, x2: 1.5, y2: 2.15,
1324
- line: { color: COLORS.accent, width: 3 },
1325
- });
1326
- const points = (props.points || []).slice(0, 4);
1327
- points.forEach((p, idx) => {
1328
- const y = 2.45 + idx * 0.55;
1329
- slide.addShape('ellipse', {
1330
- x: 0.72, y: y + 0.06, w: 0.18, h: 0.18,
1331
- fill: { color: COLORS.accent },
1332
- });
1333
- slide.addText(String(idx + 1), {
1334
- x: 0.72, y: y + 0.06, w: 0.18, h: 0.18,
1335
- fontSize: 10, color: COLORS.white, bold: true, align: 'center', valign: 'middle', fontFace: FONTS.mono,
1336
- });
1337
- slide.addText(p.text ?? '', {
1338
- x: 1.05, y: y, w: 4.0, h: 0.45,
1339
- fontSize: 12, color: COLORS.primary, fontFace: FONTS.body, valign: 'top',
1340
- });
1341
- });
1342
- const steps = (props.steps || []).slice(0, 4);
1343
- steps.forEach((step, idx) => {
1344
- const x = 5.55;
1345
- const y = 2.25 + idx * 0.82;
1346
- addTheme07Card(slide, x, y, 3.8, 0.74, 0.08, COLORS.white);
1347
- slide.addText(step.date ?? '', {
1348
- x: x + 0.1, y: y + 0.08, w: 0.8, h: 0.22,
1349
- fontSize: 10, color: COLORS.accent, bold: true, fontFace: FONTS.mono,
1350
- });
1351
- slide.addText(step.title ?? '', {
1352
- x: x + 0.95, y: y + 0.06, w: 2.7, h: 0.25,
1353
- fontSize: 12, color: COLORS.primary, bold: true, fontFace: FONTS.heading,
1354
- });
1355
- slide.addText(step.description ?? '', {
1356
- x: x + 0.95, y: y + 0.32, w: 2.7, h: 0.35,
1357
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1358
- });
1359
- });
1360
- addTheme07GlowLine(slide);
1361
- }
1362
- export function renderTheme07RepricingV1(slide, props) {
1363
- if (props.kicker) {
1364
- addTheme07Kicker(slide, props.kicker);
1365
- }
1366
- slide.addText(props.title ?? '', {
1367
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
1368
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1369
- });
1370
- if (props.subtitle) {
1371
- slide.addText(props.subtitle, {
1372
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
1373
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1374
- });
1375
- }
1376
- const phases = (props.phases || []).slice(0, 5);
1377
- if (phases.length > 0) {
1378
- const cardW = 8.7 / phases.length - 0.15;
1379
- phases.forEach((phase, idx) => {
1380
- const x = 0.65 + idx * (cardW + 0.15);
1381
- const y = 2.25;
1382
- addTheme07Card(slide, x, y, cardW, 2.6, 0.08, COLORS.white);
1383
- slide.addShape('ellipse', {
1384
- x: x + 0.12, y: y + 0.12, w: 0.2, h: 0.2,
1385
- fill: { color: COLORS.accent },
1386
- });
1387
- slide.addText(phase.date ?? '', {
1388
- x: x + 0.4, y: y + 0.1, w: cardW - 0.55, h: 0.25,
1389
- fontSize: 11, color: COLORS.accent, bold: true, fontFace: FONTS.mono,
1390
- });
1391
- slide.addText(phase.title ?? '', {
1392
- x: x + 0.12, y: y + 0.45, w: cardW - 0.24, h: 0.35,
1393
- fontSize: 14, color: COLORS.primary, bold: true, fontFace: FONTS.heading,
1394
- });
1395
- slide.addText(phase.description ?? '', {
1396
- x: x + 0.12, y: y + 0.85, w: cardW - 0.24, h: 1.6,
1397
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1398
- });
1399
- if (idx < phases.length - 1) {
1400
- slide.addText('→', {
1401
- x: x + cardW + 0.02, y: y + 1.1, w: 0.13, h: 0.3,
1402
- fontSize: 12, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center',
1403
- });
1404
- }
1405
- });
1406
- }
1407
- if (props.footnote) {
1408
- slide.addText(props.footnote, {
1409
- x: 0.65, y: 5.05, w: 8.7, h: 0.25,
1410
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1411
- });
1412
- }
1413
- addTheme07GlowLine(slide);
1414
- }
1415
- export function renderTheme07DealMapV1(slide, props) {
1416
- if (props.kicker) {
1417
- addTheme07Kicker(slide, props.kicker);
1418
- }
1419
- slide.addText(props.title ?? '', {
1420
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
1421
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1422
- });
1423
- if (props.subtitle) {
1424
- slide.addText(props.subtitle, {
1425
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
1426
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1427
- });
1428
- }
1429
- const hasConclusion = !!props.conclusion;
1430
- const chartW = hasConclusion ? 5.6 : 8.7;
1431
- const nodes = (props.nodes || []).slice(0, 16).filter((n) => n && n.name);
1432
- if (nodes.length > 0) {
1433
- const labels = nodes.map((n) => n.name);
1434
- const values = nodes.map((n) => Number(n.value ?? 50) || 50);
1435
- slide.addChart('line', [{ name: '节点', labels, values }], {
1436
- x: 0.65, y: 2.25, w: chartW, h: 3.0,
1437
- chartColors: ['2E5C8A'],
1438
- showValue: false,
1439
- });
1440
- }
1441
- if (hasConclusion) {
1442
- addTheme07Card(slide, 6.45, 2.25, 2.9, 3.0, 0.08, COLORS.white);
1443
- slide.addText(props.conclusion, {
1444
- x: 6.65, y: 2.43, w: 2.5, h: 2.64,
1445
- fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1446
- });
1447
- }
1448
- addTheme07GlowLine(slide);
1449
- }
1450
- export function renderTheme07ColdStartV1(slide, props) {
1451
- if (props.kicker) {
1452
- addTheme07Kicker(slide, props.kicker);
1453
- }
1454
- if (props.title) {
1455
- slide.addText(props.title, {
1456
- x: 0.65, y: 0.95, w: 8.7, h: 0.6,
1457
- fontSize: 32, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1458
- });
1459
- }
1460
- slide.addText(props.number ?? '', {
1461
- x: 0.65, y: 1.85, w: 5.5, h: 1.2,
1462
- fontSize: 72, bold: true, fontFace: FONTS.heading, color: COLORS.accent, valign: 'top',
1463
- });
1464
- if (props.unit) {
1465
- slide.addText(props.unit, {
1466
- x: 3.9, y: 2.25, w: 2.0, h: 0.5,
1467
- fontSize: 24, bold: true, fontFace: FONTS.heading, color: COLORS.secondary, valign: 'top',
1468
- });
1469
- }
1470
- if (props.label) {
1471
- slide.addText(props.label, {
1472
- x: 0.65, y: 3.15, w: 5.5, h: 0.3,
1473
- fontSize: 13, color: COLORS.secondary, fontFace: FONTS.mono, valign: 'top',
1474
- });
1475
- }
1476
- if (props.description) {
1477
- slide.addText(props.description, {
1478
- x: 0.65, y: 3.55, w: 5.2, h: 0.8,
1479
- fontSize: 12, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1480
- });
1481
- }
1482
- const supporting = (props.supporting || []).slice(0, 3);
1483
- supporting.forEach((item, idx) => {
1484
- const y = 1.7 + idx * 1.05;
1485
- addTheme07Card(slide, 6.1, y, 3.25, 0.9, 0.08, COLORS.white);
1486
- slide.addText(item.value ?? '', {
1487
- x: 6.25, y: y + 0.1, w: 2.95, h: 0.45,
1488
- fontSize: 24, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
1489
- });
1490
- slide.addText(item.label ?? '', {
1491
- x: 6.25, y: y + 0.55, w: 2.95, h: 0.3,
1492
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.mono,
1493
- });
1494
- });
1495
- addTheme07GlowLine(slide);
1496
- }
1497
- export function renderTheme07AccelerateV1(slide, props) {
1498
- if (props.kicker) {
1499
- addTheme07Kicker(slide, props.kicker);
1500
- }
1501
- slide.addText(props.title ?? '', {
1502
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
1503
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, align: 'center', valign: 'top',
1504
- });
1505
- if (props.subtitle) {
1506
- slide.addText(props.subtitle, {
1507
- x: 1.5, y: 1.7, w: 7, h: 0.35,
1508
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, align: 'center', valign: 'top',
1509
- });
1510
- }
1511
- slide.addText(`${props.value ?? ''}${props.unit ?? ''}`, {
1512
- x: 0.65, y: 2.35, w: 8.7, h: 1.1,
1513
- fontSize: 72, bold: true, fontFace: FONTS.heading, color: COLORS.accent, align: 'center', valign: 'top',
1514
- });
1515
- if (props.change || props.changeLabel) {
1516
- const text = [props.change ?? '', props.changeLabel ?? ''].filter(Boolean).join(' · ');
1517
- addTheme07Card(slide, 3.35, 3.6, 3.3, 0.65, 0.08, COLORS.white);
1518
- slide.addText(text, {
1519
- x: 3.5, y: 3.75, w: 3.0, h: 0.35,
1520
- fontSize: 14, color: COLORS.accent2, bold: true, fontFace: FONTS.mono, align: 'center',
1521
- });
1522
- }
1523
- addTheme07GlowLine(slide);
1524
- }
1525
- export function renderTheme07InvestorV1(slide, props) {
1526
- if (props.kicker) {
1527
- addTheme07Kicker(slide, props.kicker);
1528
- }
1529
- slide.addText(props.title ?? '', {
1530
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
1531
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1532
- });
1533
- if (props.subtitle) {
1534
- slide.addText(props.subtitle, {
1535
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
1536
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1537
- });
1538
- }
1539
- const investors = (props.investors || []).slice(0, 9).filter((i) => i && i.name);
1540
- if (investors.length > 0) {
1541
- const colW = 2.8;
1542
- const rowH = 0.9;
1543
- const cols = 3;
1544
- investors.forEach((inv, idx) => {
1545
- const col = idx % cols;
1546
- const row = Math.floor(idx / cols);
1547
- const x = 0.65 + col * (colW + 0.12);
1548
- const y = 2.25 + row * (rowH + 0.12);
1549
- const isFocus = idx === 0;
1550
- addTheme07Card(slide, x, y, colW, rowH, 0.08, isFocus ? COLORS.accent : COLORS.white);
1551
- const textColor = isFocus ? COLORS.white : COLORS.primary;
1552
- const subColor = isFocus ? 'D0E0F0' : COLORS.secondary;
1553
- slide.addText(inv.name, {
1554
- x: x + 0.12, y: y + 0.12, w: colW - 0.24, h: 0.35,
1555
- fontSize: 15, color: textColor, bold: true, fontFace: FONTS.heading,
1556
- });
1557
- slide.addText([inv.type, inv.stage].filter(Boolean).join(' · '), {
1558
- x: x + 0.12, y: y + 0.5, w: colW - 0.24, h: 0.28,
1559
- fontSize: 9, color: subColor, fontFace: FONTS.mono,
1560
- });
1561
- });
1562
- }
1563
- addTheme07GlowLine(slide);
1564
- }
1565
- export function renderTheme07ActiveCapitalV1(slide, props) {
1566
- if (props.kicker) {
1567
- addTheme07Kicker(slide, props.kicker);
1568
- }
1569
- slide.addText(props.title ?? '', {
1570
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
1571
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1572
- });
1573
- if (props.subtitle) {
1574
- slide.addText(props.subtitle, {
1575
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
1576
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1577
- });
1578
- }
1579
- if (props.company) {
1580
- slide.addText(props.company, {
1581
- x: 0.65, y: 2.15, w: 4.0, h: 0.45,
1582
- fontSize: 22, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
1583
- });
1584
- }
1585
- if (props.tagline) {
1586
- slide.addText(props.tagline, {
1587
- x: 0.65, y: 2.6, w: 4.0, h: 0.25,
1588
- fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body,
1589
- });
1590
- }
1591
- const rounds = (props.rounds || []).slice(0, 6).filter((r) => r && r.round);
1592
- if (rounds.length > 0) {
1593
- const cardW = 8.7 / rounds.length - 0.15;
1594
- rounds.forEach((r, idx) => {
1595
- const x = 0.65 + idx * (cardW + 0.15);
1596
- const y = 3.1;
1597
- const isFocus = idx === rounds.length - 1;
1598
- addTheme07Card(slide, x, y, cardW, 1.9, 0.08, isFocus ? COLORS.accent : COLORS.white);
1599
- const textColor = isFocus ? COLORS.white : COLORS.primary;
1600
- const subColor = isFocus ? 'D0E0F0' : COLORS.secondary;
1601
- slide.addText(r.date ?? '', {
1602
- x: x + 0.1, y: y + 0.1, w: cardW - 0.2, h: 0.2,
1603
- fontSize: 10, color: isFocus ? COLORS.white : COLORS.accent, fontFace: FONTS.mono,
1604
- });
1605
- slide.addText(r.round, {
1606
- x: x + 0.1, y: y + 0.35, w: cardW - 0.2, h: 0.35,
1607
- fontSize: 14, color: textColor, bold: true, fontFace: FONTS.heading,
1608
- });
1609
- slide.addText(r.amount ?? '', {
1610
- x: x + 0.1, y: y + 0.75, w: cardW - 0.2, h: 0.45,
1611
- fontSize: 22, color: textColor, bold: true, fontFace: FONTS.heading,
1612
- });
1613
- slide.addText(r.investor ?? '', {
1614
- x: x + 0.1, y: y + 1.25, w: cardW - 0.2, h: 0.45,
1615
- fontSize: 9, color: subColor, fontFace: FONTS.mono, valign: 'top',
1616
- });
1617
- });
1618
- }
1619
- addTheme07GlowLine(slide);
1620
- }
1621
- export function renderTheme07ConcentrationV1(slide, props) {
1622
- if (props.kicker) {
1623
- addTheme07Kicker(slide, props.kicker);
1624
- }
1625
- slide.addText(props.title ?? '', {
1626
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
1627
- fontSize: 36, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1628
- });
1629
- if (props.subtitle) {
1630
- slide.addText(props.subtitle, {
1631
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
1632
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1633
- });
1634
- }
1635
- const rows = (props.rows || []).slice(0, 10).filter((r) => r && r.name);
1636
- if (rows.length > 0) {
1637
- const rowH = 0.52;
1638
- const startY = 2.25;
1639
- rows.forEach((row, idx) => {
1640
- const y = startY + idx * rowH;
1641
- const isFocus = row.focus === true || idx === Number(props.focusIndex ?? 0);
1642
- if (isFocus) {
1643
- slide.addShape('roundRect', {
1644
- x: 0.65, y, w: 8.7, h: rowH - 0.06,
1645
- fill: { color: COLORS.accent }, rectRadius: 0.06,
1646
- });
1647
- }
1648
- else {
1649
- addTheme07Card(slide, 0.65, y, 8.7, rowH - 0.06, 0.06, COLORS.white);
1650
- }
1651
- const textColor = isFocus ? COLORS.white : COLORS.primary;
1652
- const valueColor = isFocus ? COLORS.white : COLORS.accent;
1653
- slide.addText(String(idx + 1).padStart(2, '0'), {
1654
- x: 0.85, y: y + 0.08, w: 0.5, h: 0.3,
1655
- fontSize: 15, color: valueColor, bold: true, fontFace: FONTS.heading,
1656
- });
1657
- slide.addText(row.name, {
1658
- x: 1.45, y: y + 0.1, w: 4.5, h: 0.3,
1659
- fontSize: 14, color: textColor, bold: true, fontFace: FONTS.body,
1660
- });
1661
- slide.addText(row.value ?? '', {
1662
- x: 6.8, y: y + 0.08, w: 1.2, h: 0.32,
1663
- fontSize: 16, color: valueColor, bold: true, fontFace: FONTS.mono, align: 'right',
1664
- });
1665
- if (row.change) {
1666
- slide.addText(row.change, {
1667
- x: 8.1, y: y + 0.1, w: 0.9, h: 0.28,
1668
- fontSize: 11, color: row.change.startsWith('-') ? COLORS.accentCool : COLORS.accent2, fontFace: FONTS.mono, align: 'right',
1669
- });
1670
- }
1671
- });
1672
- }
1673
- addTheme07GlowLine(slide);
1674
- }
1675
- export function renderTheme07SyndicateV1(slide, props) {
1676
- if (props.kicker) {
1677
- addTheme07Kicker(slide, props.kicker);
1678
- }
1679
- slide.addText(props.statement ?? '', {
1680
- x: 0.65, y: 1.15, w: 8.7, h: 1.0,
1681
- fontSize: 28, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1682
- });
1683
- if (props.subtitle) {
1684
- slide.addText(props.subtitle, {
1685
- x: 0.65, y: 2.25, w: 8.7, h: 0.3,
1686
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1687
- });
1688
- }
1689
- const points = (props.points || []).slice(0, 4);
1690
- points.forEach((p, idx) => {
1691
- const y = 2.75 + idx * 0.55;
1692
- slide.addShape('ellipse', {
1693
- x: 0.72, y: y + 0.06, w: 0.18, h: 0.18,
1694
- fill: { color: COLORS.accent },
1695
- });
1696
- slide.addText(String(idx + 1), {
1697
- x: 0.72, y: y + 0.06, w: 0.18, h: 0.18,
1698
- fontSize: 10, color: COLORS.white, bold: true, align: 'center', valign: 'middle', fontFace: FONTS.mono,
1699
- });
1700
- slide.addText(p.text ?? '', {
1701
- x: 1.05, y: y, w: 8.0, h: 0.45,
1702
- fontSize: 12, color: COLORS.primary, fontFace: FONTS.body, valign: 'top',
1703
- });
1704
- });
1705
- if (props.source) {
1706
- slide.addText(props.source, {
1707
- x: 0.65, y: 5.05, w: 8.7, h: 0.25,
1708
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, valign: 'top',
1709
- });
1710
- }
1711
- addTheme07GlowLine(slide);
1712
- }
1713
- export function renderTheme07SectorV1(slide, props) {
1714
- if (props.kicker) {
1715
- addTheme07Kicker(slide, props.kicker);
1716
- }
1717
- slide.addText(props.title ?? '', {
1718
- x: 0.65, y: 0.9, w: 8.7, h: 0.75,
1719
- fontSize: 34, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1720
- });
1721
- if (props.subtitle) {
1722
- slide.addText(props.subtitle, {
1723
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
1724
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1725
- });
1726
- }
1727
- slide.addShape('line', {
1728
- x1: 0.65, y1: 2.1, x2: 1.5, y2: 2.1,
1729
- line: { color: COLORS.accent, width: 3 },
1730
- });
1731
- const leftW = 4.7;
1732
- const rightX = 5.6;
1733
- const rightW = 3.75;
1734
- // 左侧描述
1735
- if (props.description) {
1736
- slide.addText(props.description, {
1737
- x: 0.65, y: 2.35, w: leftW, h: 0.7,
1738
- fontSize: 12, color: COLORS.primary, fontFace: FONTS.body, valign: 'top',
1739
- });
1740
- }
1741
- // 左侧要点
1742
- const bullets = (props.bullets || [])
1743
- .map((b) => (typeof b === 'string' ? { title: b, desc: '' } : b))
1744
- .filter((b) => b && (b.title || b.desc));
1745
- if (bullets.length > 0) {
1746
- const startY = props.description ? 3.15 : 2.35;
1747
- const itemH = 0.55;
1748
- bullets.slice(0, 6).forEach((item, idx) => {
1749
- const y = startY + idx * itemH;
1750
- slide.addText(String(idx + 1).padStart(2, '0'), {
1751
- x: 0.65, y, w: 0.45, h: 0.35,
1752
- fontSize: 16, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
1753
- });
1754
- if (item.title) {
1755
- slide.addText(item.title, {
1756
- x: 1.15, y, w: leftW - 0.5, h: 0.25,
1757
- fontSize: 12, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
1758
- });
1759
- }
1760
- if (item.desc) {
1761
- slide.addText(item.desc, {
1762
- x: 1.15, y: y + 0.22, w: leftW - 0.5, h: 0.28,
1763
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1764
- });
1765
- }
1766
- });
1767
- }
1768
- // 右侧图表占位
1769
- const hasChart = props.chartType && props.chartOption && Object.keys(props.chartOption).length > 0;
1770
- let rightY = 0.9;
1771
- if (hasChart) {
1772
- addTheme07Card(slide, rightX, rightY, rightW, 1.9, 0.08, COLORS.white);
1773
- slide.addText('CHART', {
1774
- x: rightX, y: rightY + 0.75, w: rightW, h: 0.3,
1775
- fontSize: 11, color: COLORS.secondary, bold: true, fontFace: FONTS.mono, align: 'center',
1776
- charSpacing: 2,
1777
- });
1778
- rightY += 2.05;
1779
- }
1780
- // 右侧指标
1781
- const metrics = (props.metrics || []).filter((m) => m && (m.value || m.label));
1782
- if (metrics.length > 0) {
1783
- const cardH = 0.85;
1784
- const cols = Math.min(metrics.length, 2);
1785
- metrics.slice(0, 4).forEach((m, idx) => {
1786
- const col = idx % cols;
1787
- const row = Math.floor(idx / cols);
1788
- const cardW = (rightW - (cols - 1) * 0.1) / cols;
1789
- const x = rightX + col * (cardW + 0.1);
1790
- const y = rightY + row * (cardH + 0.1);
1791
- addTheme07Card(slide, x, y, cardW, cardH, 0.06, m.accent ? COLORS.accent : COLORS.white);
1792
- const valueColor = m.accent ? COLORS.white : COLORS.accent;
1793
- const labelColor = m.accent ? COLORS.white : COLORS.secondary;
1794
- slide.addText(m.value ?? '', {
1795
- x: x + 0.08, y: y + 0.1, w: cardW - 0.16, h: 0.35,
1796
- fontSize: 20, color: valueColor, bold: true, fontFace: FONTS.heading,
1797
- });
1798
- if (m.label) {
1799
- slide.addText(m.label, {
1800
- x: x + 0.08, y: y + 0.48, w: cardW - 0.16, h: 0.25,
1801
- fontSize: 9, color: labelColor, fontFace: FONTS.mono,
1802
- });
1803
- }
1804
- });
1805
- rightY += Math.ceil(metrics.length / cols) * 0.95 + 0.1;
1806
- }
1807
- // 右侧洞察
1808
- if (props.showInsight !== false && props.insight && (props.insight.value || props.insight.label || props.insight.description)) {
1809
- const ins = props.insight;
1810
- const insightH = ins.description ? 1.2 : 0.75;
1811
- addTheme07Card(slide, rightX, rightY, rightW, insightH, 0.08, COLORS.white);
1812
- slide.addShape('rect', {
1813
- x: rightX, y: rightY, w: 0.06, h: insightH,
1814
- fill: { color: COLORS.accent },
1815
- });
1816
- let dy = 0.12;
1817
- if (ins.value) {
1818
- slide.addText(ins.value, {
1819
- x: rightX + 0.15, y: rightY + dy, w: rightW - 0.3, h: 0.35,
1820
- fontSize: 24, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
1821
- });
1822
- dy += 0.35;
1823
- }
1824
- if (ins.label) {
1825
- slide.addText(ins.label, {
1826
- x: rightX + 0.15, y: rightY + dy, w: rightW - 0.3, h: 0.2,
1827
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono,
1828
- });
1829
- dy += 0.22;
1830
- }
1831
- if (ins.description) {
1832
- slide.addText(ins.description, {
1833
- x: rightX + 0.15, y: rightY + dy, w: rightW - 0.3, h: 0.55,
1834
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1835
- });
1836
- }
1837
- }
1838
- if (props.footnote) {
1839
- slide.addText(props.footnote, {
1840
- x: 0.65, y: 4.95, w: 8.7, h: 0.3,
1841
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1842
- });
1843
- }
1844
- addTheme07GlowLine(slide);
1845
- }
1846
- export function renderTheme07TableV1(slide, props) {
1847
- if (props.kicker) {
1848
- addTheme07Kicker(slide, props.kicker);
1849
- }
1850
- slide.addText(props.title ?? '', {
1851
- x: 0.65, y: 0.9, w: 8.7, h: 0.75,
1852
- fontSize: 34, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1853
- });
1854
- if (props.subtitle) {
1855
- slide.addText(props.subtitle, {
1856
- x: 0.65, y: 1.65, w: 8.7, h: 0.35,
1857
- fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1858
- });
1859
- }
1860
- if (props.intro) {
1861
- slide.addText(props.intro, {
1862
- x: 0.65, y: props.subtitle ? 2.05 : 1.65, w: 8.7, h: 0.35,
1863
- fontSize: 11, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1864
- });
1865
- }
1866
- const headers = (props.headers || [])
1867
- .map((h) => (typeof h === 'string' ? h : h?.item))
1868
- .filter(Boolean);
1869
- const rows = (props.rows || [])
1870
- .map((r) => {
1871
- const cells = (r.cells || [])
1872
- .slice(0, headers.length || 6)
1873
- .map((c) => (typeof c === 'string' ? { value: c, accent: false } : { value: c?.value ?? '', accent: !!c?.accent }));
1874
- return { cells, accent: !!r.accent };
1875
- })
1876
- .filter((r) => r.cells.some((c) => c.value));
1877
- if (headers.length > 0 && rows.length > 0) {
1878
- const tableW = 8.7;
1879
- const startY = props.intro ? 2.45 : props.subtitle ? 2.1 : 1.65;
1880
- const colW = tableW / headers.length;
1881
- const rowH = Math.min(0.55, 3.0 / Math.max(rows.length, 5));
1882
- const maxRows = Math.floor(3.4 / rowH);
1883
- // 表头
1884
- addTheme07Card(slide, 0.65, startY, tableW, rowH, 0.04, COLORS.accent);
1885
- headers.forEach((h, idx) => {
1886
- slide.addText(h, {
1887
- x: 0.65 + idx * colW, y: startY + 0.08, w: colW, h: rowH - 0.16,
1888
- fontSize: 11, color: COLORS.white, bold: true, fontFace: FONTS.mono, align: 'center', valign: 'middle',
1889
- });
1890
- });
1891
- // 表格行
1892
- rows.slice(0, maxRows).forEach((row, rIdx) => {
1893
- const y = startY + (rIdx + 1) * rowH;
1894
- const fill = row.accent ? COLORS.accent : COLORS.white;
1895
- addTheme07Card(slide, 0.65, y, tableW, rowH - 0.02, 0.02, fill);
1896
- row.cells.forEach((cell, cIdx) => {
1897
- const textColor = row.accent ? COLORS.white : cell.accent ? COLORS.accent : COLORS.primary;
1898
- slide.addText(cell.value, {
1899
- x: 0.65 + cIdx * colW + 0.06, y: y + 0.06, w: colW - 0.12, h: rowH - 0.14,
1900
- fontSize: 10, color: textColor, fontFace: FONTS.body, align: cIdx === 0 ? 'left' : 'center', valign: 'middle',
1901
- });
1902
- });
1903
- });
1904
- }
1905
- if (props.conclusion) {
1906
- slide.addText(props.conclusion, {
1907
- x: 0.65, y: 4.7, w: 8.7, h: 0.5,
1908
- fontSize: 12, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
1909
- });
1910
- }
1911
- if (props.footnote) {
1912
- slide.addText(props.footnote, {
1913
- x: 0.65, y: 5.0, w: 8.7, h: 0.25,
1914
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1915
- });
1916
- }
1917
- addTheme07GlowLine(slide);
1918
- }
1919
- export function renderTheme07CompanyV1(slide, props) {
1920
- if (props.kicker) {
1921
- addTheme07Kicker(slide, props.kicker);
1922
- }
1923
- slide.addText(props.name ?? '', {
1924
- x: 0.65, y: 0.9, w: 4.7, h: 0.75,
1925
- fontSize: 38, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
1926
- });
1927
- if (props.tagline) {
1928
- slide.addText(props.tagline, {
1929
- x: 0.65, y: 1.6, w: 4.7, h: 0.35,
1930
- fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
1931
- });
1932
- }
1933
- if (props.description) {
1934
- slide.addText(props.description, {
1935
- x: 0.65, y: props.tagline ? 2.05 : 1.6, w: 4.7, h: 1.1,
1936
- fontSize: 12, color: COLORS.primary, fontFace: FONTS.body, valign: 'top',
1937
- });
1938
- }
1939
- const tags = (props.tags || [])
1940
- .map((t) => (typeof t === 'string' ? t : t?.item))
1941
- .filter(Boolean);
1942
- if (tags.length > 0) {
1943
- let tagX = 0.65;
1944
- const tagY = 3.35;
1945
- tags.slice(0, 6).forEach((tag) => {
1946
- const w = Math.min(1.8, 0.25 + tag.length * 0.12);
1947
- addTheme07Card(slide, tagX, tagY, w, 0.32, 0.04, COLORS.white);
1948
- slide.addText(tag, {
1949
- x: tagX + 0.05, y: tagY + 0.05, w: w - 0.1, h: 0.22,
1950
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center',
1951
- });
1952
- tagX += w + 0.1;
1953
- });
1954
- }
1955
- const metrics = (props.metrics || []).filter((m) => m && (m.value || m.label));
1956
- if (metrics.length > 0) {
1957
- const metricY = tags.length > 0 ? 3.85 : 3.35;
1958
- const metricW = 4.7 / Math.min(metrics.length, 4);
1959
- metrics.slice(0, 4).forEach((m, idx) => {
1960
- const x = 0.65 + idx * metricW;
1961
- slide.addText(m.value ?? '', {
1962
- x, y: metricY, w: metricW - 0.12, h: 0.35,
1963
- fontSize: 20, color: COLORS.accent, bold: true, fontFace: FONTS.heading,
1964
- });
1965
- if (m.label) {
1966
- slide.addText(m.label, {
1967
- x, y: metricY + 0.32, w: metricW - 0.12, h: 0.2,
1968
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.mono,
1969
- });
1970
- }
1971
- });
1972
- }
1973
- // 右侧图片区域(匹配参考 PPT DROP MEDIA 设计)
1974
- const rightX = 5.6;
1975
- const rightW = 3.75;
1976
- const imgH = 2.6;
1977
- const imgY = 0.85;
1978
- addTheme07Card(slide, rightX, imgY, rightW, imgH, 0.08, COLORS.white);
1979
- if (props.imageUrl) {
1980
- addImageMaybe(slide, props.imageUrl, rightX + 0.08, imgY + 0.08, rightW - 0.16, imgH - 0.16);
1981
- }
1982
- else {
1983
- // DROP MEDIA 占位:标签 pill + 居中文字 + 底部名称
1984
- if (props.imageTag) {
1985
- slide.addShape('roundRect', {
1986
- x: rightX + 0.15, y: imgY + 0.15, w: 1.1, h: 0.28,
1987
- fill: { color: '#7B9E7B' }, rectRadius: 0.14,
1988
- });
1989
- slide.addText(props.imageTag, {
1990
- x: rightX + 0.15, y: imgY + 0.17, w: 1.1, h: 0.24,
1991
- fontSize: 9, color: COLORS.white, bold: true, fontFace: FONTS.mono, align: 'center',
1992
- });
1993
- }
1994
- slide.addText('DROP MEDIA', {
1995
- x: rightX, y: imgY + imgH * 0.38, w: rightW, h: 0.3,
1996
- fontSize: 11, color: '#90B090', fontFace: FONTS.mono, align: 'center', charSpacing: 2,
1997
- });
1998
- slide.addText(props.name ?? '', {
1999
- x: rightX + 0.15, y: imgY + imgH - 0.45, w: rightW - 0.3, h: 0.32,
2000
- fontSize: 16, bold: true, fontFace: FONTS.heading, color: COLORS.primary,
2001
- });
2002
- }
2003
- if (props.quote) {
2004
- addTheme07Card(slide, rightX, 3.35, rightW, 1.25, 0.08, COLORS.white);
2005
- slide.addText(`“${props.quote}”`, {
2006
- x: rightX + 0.12, y: 3.45, w: rightW - 0.24, h: 0.75,
2007
- fontSize: 12, color: COLORS.primary, fontFace: FONTS.body, valign: 'top',
2008
- });
2009
- if (props.author) {
2010
- slide.addText(`— ${props.author}`, {
2011
- x: rightX + 0.12, y: 4.2, w: rightW - 0.24, h: 0.25,
2012
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, valign: 'top',
2013
- });
2014
- }
2015
- }
2016
- if (props.footnote) {
2017
- slide.addText(props.footnote, {
2018
- x: 0.65, y: 4.95, w: 8.7, h: 0.3,
2019
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2020
- });
2021
- }
2022
- addTheme07GlowLine(slide);
2023
- }
2024
- export function renderTheme07GeoV1(slide, props) {
2025
- if (props.kicker) {
2026
- addTheme07Kicker(slide, props.kicker);
2027
- }
2028
- slide.addText(props.title ?? '', {
2029
- x: 0.65, y: 0.9, w: 8.7, h: 0.75,
2030
- fontSize: 34, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
2031
- });
2032
- if (props.subtitle) {
2033
- slide.addText(props.subtitle, {
2034
- x: 0.65, y: 1.65, w: 8.7, h: 0.35,
2035
- fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2036
- });
2037
- }
2038
- const regions = (props.regions || [])
2039
- .filter((r) => r && r.name)
2040
- .slice(0, 8);
2041
- const leftW = 4.7;
2042
- const rightX = 5.6;
2043
- const rightW = 3.75;
2044
- if (regions.length > 0) {
2045
- const startY = 2.15;
2046
- const rowH = 0.55;
2047
- regions.forEach((r, idx) => {
2048
- const y = startY + idx * rowH;
2049
- slide.addText(r.name, {
2050
- x: 0.65, y, w: 1.5, h: 0.28,
2051
- fontSize: 12, color: COLORS.primary, bold: true, fontFace: FONTS.body, valign: 'top',
2052
- });
2053
- const meta = [r.value, r.note].filter(Boolean).join(' · ');
2054
- if (meta) {
2055
- slide.addText(meta, {
2056
- x: 2.2, y, w: 2.15, h: 0.28,
2057
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, align: 'right', valign: 'top',
2058
- });
2059
- }
2060
- // 进度条背景
2061
- slide.addShape('roundRect', {
2062
- x: 0.65, y: y + 0.3, w: leftW, h: 0.08,
2063
- fill: { color: COLORS.light },
2064
- rectRadius: 0.04,
2065
- });
2066
- const pct = Math.max(0, Math.min(100, r.percent ?? 0));
2067
- if (pct > 0) {
2068
- slide.addShape('roundRect', {
2069
- x: 0.65, y: y + 0.3, w: leftW * (pct / 100), h: 0.08,
2070
- fill: { color: COLORS.accent },
2071
- rectRadius: 0.04,
2072
- });
2073
- }
2074
- });
2075
- }
2076
- // 右侧装饰圆圈(匹配参考 PPT page053)
2077
- if (props.imageUrl) {
2078
- addImageMaybe(slide, props.imageUrl, rightX + 0.1, 1.5, rightW - 0.2, 3.0);
2079
- }
2080
- else {
2081
- // 装饰性半透明圆圈(pptxgenjs 仅支持 6 位 hex)
2082
- slide.addShape('ellipse', {
2083
- x: rightX + 0.3, y: 1.3, w: 2.4, h: 2.4,
2084
- fill: { color: 'C8DCC8' }, line: { color: 'B8CCB8', width: 0.5 },
2085
- });
2086
- slide.addShape('ellipse', {
2087
- x: rightX + 0.8, y: 2.2, w: 1.8, h: 1.8,
2088
- fill: { color: 'D8E8D8' }, line: { color: 'B8CCB8', width: 0.5 },
2089
- });
2090
- slide.addShape('ellipse', {
2091
- x: rightX + 1.6, y: 2.7, w: 1.3, h: 1.3,
2092
- fill: { color: 'E8F2E8' }, line: { color: 'C8DCC8', width: 0.5 },
2093
- });
2094
- }
2095
- if (props.footnote) {
2096
- slide.addText(props.footnote, {
2097
- x: 0.65, y: 4.95, w: 8.7, h: 0.3,
2098
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2099
- });
2100
- }
2101
- addTheme07GlowLine(slide);
2102
- }
2103
- export function renderTheme07ClosingV1(slide, props) {
2104
- // v2 坐标系统:从 theme07-layout-coordinates.json 读取,缺失时回退到默认值
2105
- const coords = getLayoutCoordinates('theme07_closing_v1');
2106
- const c = coords; // 简写
2107
- const _x = (val, fallback) => (val ?? fallback);
2108
- const _cx = (key, fallback) => _x(c[key]?.x, fallback);
2109
- const _cy = (key, fallback) => _x(c[key]?.y, fallback);
2110
- if (props.kicker) {
2111
- addTheme07Kicker(slide, props.kicker);
2112
- }
2113
- const images = (props.images || []).slice(0, 3);
2114
- const hasImages = images.length > 0;
2115
- // 有图片时内容区变窄,右侧留出图片区
2116
- const contentW = hasImages ? 5.4 : 8.7;
2117
- const imgAreaX = _cx('image', 6.2);
2118
- const imgAreaW = _x(c.image?.w, 3.2);
2119
- slide.addText(props.statement ?? '', {
2120
- x: _cx('heading', 0.65), y: _cy('heading', 1.4), w: contentW, h: hasImages ? 1.2 : 1.6,
2121
- fontSize: hasImages ? 32 : 40, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
2122
- });
2123
- if (props.subtitle) {
2124
- slide.addText(props.subtitle, {
2125
- x: _cx('title', 0.65), y: _cy('title', hasImages ? 2.55 : 3.1), w: contentW, h: 0.45,
2126
- fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2127
- });
2128
- }
2129
- const points = (props.points || [])
2130
- .map((p) => (typeof p === 'string' ? { text: p } : p))
2131
- .filter((p) => p && p.text)
2132
- .slice(0, 4);
2133
- if (points.length > 0) {
2134
- const startY = props.subtitle ? (hasImages ? 3.05 : 3.7) : (hasImages ? 2.55 : 3.1);
2135
- const pointH = 0.42;
2136
- points.forEach((p, idx) => {
2137
- const y = startY + idx * pointH;
2138
- slide.addShape('ellipse', {
2139
- x: _cx('list', 0.65), y: y + 0.08, w: 0.1, h: 0.1,
2140
- fill: { color: COLORS.accent },
2141
- });
2142
- slide.addText(p.text, {
2143
- x: 0.9, y, w: contentW - 0.35, h: 0.38,
2144
- fontSize: 11, color: COLORS.primary, fontFace: FONTS.body, valign: 'top',
2145
- });
2146
- });
2147
- }
2148
- // 右侧图片区域(匹配参考 PPT page071)
2149
- if (hasImages) {
2150
- const imgH = 1.7;
2151
- const imgGap = 0.15;
2152
- images.forEach((img, idx) => {
2153
- const iy = 1.0 + idx * (imgH + imgGap);
2154
- addTheme07Card(slide, imgAreaX, iy, imgAreaW, imgH, 0.06, '#F0F4EF');
2155
- if (img.url) {
2156
- addImageMaybe(slide, img.url, imgAreaX + 0.06, iy + 0.06, imgAreaW - 0.12, imgH - 0.12);
2157
- }
2158
- else {
2159
- // DROP MEDIA 占位
2160
- if (img.tag) {
2161
- slide.addShape('roundRect', {
2162
- x: imgAreaX + 0.12, y: iy + 0.12, w: 0.9, h: 0.24,
2163
- fill: { color: '#7B9E7B' }, rectRadius: 0.12,
2164
- });
2165
- slide.addText(img.tag, {
2166
- x: imgAreaX + 0.12, y: iy + 0.14, w: 0.9, h: 0.2,
2167
- fontSize: 8, color: COLORS.white, bold: true, fontFace: FONTS.mono, align: 'center',
2168
- });
2169
- }
2170
- slide.addText('DROP MEDIA', {
2171
- x: imgAreaX, y: iy + imgH * 0.42, w: imgAreaW, h: 0.22,
2172
- fontSize: 10, color: '#A0C0A0', fontFace: FONTS.mono, align: 'center', charSpacing: 1.5,
2173
- });
2174
- if (img.label) {
2175
- slide.addText(img.label, {
2176
- x: imgAreaX + 0.12, y: iy + imgH - 0.35, w: imgAreaW - 0.24, h: 0.25,
2177
- fontSize: 11, bold: true, fontFace: FONTS.heading, color: COLORS.primary,
2178
- });
2179
- }
2180
- }
2181
- });
2182
- }
2183
- if (props.source) {
2184
- slide.addText(`— ${props.source}`, {
2185
- x: _cx('footer', 0.65), y: _cy('footer', 4.85), w: 8.7, h: 0.25,
2186
- fontSize: 11, color: COLORS.secondary, fontFace: FONTS.mono, valign: 'top',
2187
- });
2188
- }
2189
- if (props.footnoteLeft || props.footnoteRight) {
2190
- addTheme07Footer(slide, props);
2191
- }
2192
- addTheme07GlowLine(slide);
2193
- }
2194
- export function renderTheme07ClosingQuoteV1(slide, props) {
2195
- if (props.kicker) {
2196
- addTheme07Kicker(slide, props.kicker);
2197
- }
2198
- // 巨号引号装饰
2199
- slide.addText('“', {
2200
- x: 4.35, y: 0.45, w: 1.3, h: 1.1,
2201
- fontSize: 80, bold: true, color: COLORS.accent, fontFace: FONTS.heading, align: 'center',
2202
- });
2203
- // 引语正文:将 [[关键词]] 渲染为强调色高亮 run
2204
- const stmt = String(props.statement ?? '');
2205
- const parts = stmt.split(/(\[\[.+?\]\])/g).filter(Boolean);
2206
- const runs = parts.map((p) => {
2207
- const m = p.match(/^\[\[(.+)\]\]$/);
2208
- if (m)
2209
- return { text: m[1], options: { color: COLORS.accent, bold: true } };
2210
- return { text: p, options: { color: COLORS.primary } };
2211
- });
2212
- slide.addText(runs, {
2213
- x: 0.8, y: 1.55, w: 8.4, h: 2.2,
2214
- fontSize: 30, bold: true, fontFace: FONTS.heading, align: 'center', valign: 'top',
2215
- });
2216
- if (props.subtitle) {
2217
- slide.addText(props.subtitle, {
2218
- x: 1.5, y: 3.95, w: 7.0, h: 0.5,
2219
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, align: 'center',
2220
- });
2221
- }
2222
- if (props.source) {
2223
- slide.addText(`— ${props.source}`, {
2224
- x: 1.5, y: 4.55, w: 7.0, h: 0.3,
2225
- fontSize: 12, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center',
2226
- });
2227
- }
2228
- if (props.footnoteLeft || props.footnoteRight) {
2229
- addTheme07Footer(slide, props);
2230
- }
2231
- addTheme07GlowLine(slide);
2232
- }
2233
- export function renderTheme07StatHeroV1(slide, props) {
2234
- if (props.kicker) {
2235
- addTheme07Kicker(slide, props.kicker);
2236
- }
2237
- slide.addText(props.title ?? '', {
2238
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
2239
- fontSize: 32, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
2240
- });
2241
- if (props.subtitle) {
2242
- slide.addText(props.subtitle, {
2243
- x: 0.65, y: 1.65, w: 8.7, h: 0.4,
2244
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2245
- });
2246
- }
2247
- const hasImage = !!props.imageUrl;
2248
- const numberW = hasImage ? 6.2 : 8.7;
2249
- slide.addText(props.number ?? '', {
2250
- x: 0.65, y: 2.35, w: numberW, h: 1.9,
2251
- fontSize: 120, bold: true, fontFace: FONTS.heading, color: COLORS.accent, valign: 'top',
2252
- });
2253
- if (props.unit) {
2254
- slide.addText(props.unit, {
2255
- x: 0.65, y: 4.25, w: numberW, h: 0.5,
2256
- fontSize: 24, bold: true, fontFace: FONTS.heading, color: COLORS.secondary, valign: 'top',
2257
- });
2258
- }
2259
- if (props.caption) {
2260
- slide.addText(props.caption, {
2261
- x: 0.65, y: 4.8, w: numberW, h: 0.35,
2262
- fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2263
- });
2264
- }
2265
- // 分隔标尺(纯装饰,仿 .lp-theme07-stat-hero-rule)
2266
- if (props.showRule !== false) {
2267
- slide.addShape('line', {
2268
- x1: 0.65, y1: 4.15, x2: hasImage ? 6.85 : 5.0, y2: 4.15,
2269
- line: { color: COLORS.accent, width: 1, transparency: 70 },
2270
- });
2271
- }
2272
- if (hasImage) {
2273
- addImageMaybe(slide, props.imageUrl, 7.1, 2.35, 2.25, 2.35);
2274
- }
2275
- addTheme07GlowLine(slide);
2276
- }
2277
- export function renderTheme07StatRowV1(slide, props) {
2278
- if (props.kicker) {
2279
- addTheme07Kicker(slide, props.kicker);
2280
- }
2281
- slide.addText(props.title ?? '', {
2282
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
2283
- fontSize: 32, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
2284
- });
2285
- if (props.subtitle) {
2286
- slide.addText(props.subtitle, {
2287
- x: 0.65, y: 1.6, w: 8.7, h: 0.4,
2288
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2289
- });
2290
- }
2291
- const stats = (props.stats || [])
2292
- .filter((s) => s && s.value)
2293
- .slice(0, 4);
2294
- if (stats.length > 0) {
2295
- const cols = stats.length;
2296
- const gap = 0.2;
2297
- const cardW = (8.7 - (cols - 1) * gap) / cols;
2298
- const cardY = 2.3;
2299
- const cardH = 2.05;
2300
- const valueSize = cols >= 4 ? 38 : cols === 3 ? 44 : 52;
2301
- stats.forEach((stat, idx) => {
2302
- const x = 0.65 + idx * (cardW + gap);
2303
- const focus = idx === (props.focusIndex ?? 0);
2304
- addTheme07Card(slide, x, cardY, cardW, cardH, 0.08, focus ? COLORS.accent : COLORS.white);
2305
- const valueColor = focus ? COLORS.white : COLORS.accent;
2306
- const subColor = focus ? 'D0E0F0' : COLORS.secondary;
2307
- const labelColor = focus ? COLORS.white : COLORS.primary;
2308
- slide.addText(`${stat.value ?? ''}`, {
2309
- x: x + 0.15, y: cardY + 0.28, w: cardW - 0.3, h: 0.8,
2310
- fontSize: valueSize, bold: true, fontFace: FONTS.heading, color: valueColor, valign: 'top',
2311
- });
2312
- if (stat.unit) {
2313
- slide.addText(stat.unit, {
2314
- x: x + 0.15, y: cardY + 1.05, w: cardW - 0.3, h: 0.3,
2315
- fontSize: 13, color: subColor, fontFace: FONTS.mono, valign: 'top',
2316
- });
2317
- }
2318
- if (stat.label) {
2319
- slide.addText(stat.label, {
2320
- x: x + 0.15, y: cardY + 1.4, w: cardW - 0.3, h: 0.3,
2321
- fontSize: 12, bold: true, color: labelColor, fontFace: FONTS.body, valign: 'top',
2322
- });
2323
- }
2324
- if (props.showNotes !== false && stat.note) {
2325
- slide.addText(stat.note, {
2326
- x: x + 0.15, y: cardY + 1.72, w: cardW - 0.3, h: 0.25,
2327
- fontSize: 10, color: subColor, fontFace: FONTS.mono, valign: 'top',
2328
- });
2329
- }
2330
- });
2331
- }
2332
- if (props.footnote) {
2333
- slide.addText(props.footnote, {
2334
- x: 0.65, y: 4.6, w: 8.7, h: 0.3,
2335
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, valign: 'top',
2336
- });
2337
- }
2338
- addTheme07GlowLine(slide);
2339
- }
2340
- export function renderTheme07StatChartV1(slide, props) {
2341
- if (props.kicker) {
2342
- addTheme07Kicker(slide, props.kicker);
2343
- }
2344
- slide.addText(props.title ?? '', {
2345
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
2346
- fontSize: 32, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
2347
- });
2348
- if (props.subtitle) {
2349
- slide.addText(props.subtitle, {
2350
- x: 0.65, y: 1.6, w: 8.7, h: 0.4,
2351
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2352
- });
2353
- }
2354
- // 左侧结论数字
2355
- slide.addText(props.number ?? '', {
2356
- x: 0.65, y: 2.35, w: 3.4, h: 1.8,
2357
- fontSize: 96, bold: true, fontFace: FONTS.heading, color: COLORS.accent, valign: 'top',
2358
- });
2359
- if (props.unit) {
2360
- slide.addText(props.unit, {
2361
- x: 0.65, y: 4.1, w: 3.4, h: 0.4,
2362
- fontSize: 20, bold: true, fontFace: FONTS.heading, color: COLORS.secondary, valign: 'top',
2363
- });
2364
- }
2365
- if (props.label) {
2366
- slide.addText(props.label, {
2367
- x: 0.65, y: 4.55, w: 3.4, h: 0.4,
2368
- fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2369
- });
2370
- }
2371
- // 右侧柱状序列
2372
- const series = (props.series || [])
2373
- .filter((s) => s && s.label != null)
2374
- .slice(0, 8);
2375
- if (series.length > 0) {
2376
- const chartX = 4.5;
2377
- const chartY = 2.45;
2378
- const chartW = 4.5;
2379
- const chartH = 2.0;
2380
- const maxValue = Math.max(1, ...series.map((s) => Number(s.value) || 0));
2381
- const slotW = chartW / series.length;
2382
- const barW = slotW * 0.6;
2383
- const points = [];
2384
- series.forEach((item, idx) => {
2385
- const value = Number(item.value) || 0;
2386
- const h = Math.max(0.05, (value / maxValue) * chartH);
2387
- const x = chartX + idx * slotW + (slotW - barW) / 2;
2388
- const y = chartY + chartH - h;
2389
- const focus = idx === (props.focusIndex ?? 0);
2390
- slide.addShape('rect', {
2391
- x, y, w: barW, h,
2392
- fill: { color: focus ? resolveTheme07CssColor('var(--lp-accent-cool)', COLORS.accent) : COLORS.accent, transparency: focus ? 0 : 25 },
2393
- line: { type: 'none' },
2394
- });
2395
- points.push({ x: x + barW / 2, y });
2396
- if (props.showValues !== false) {
2397
- slide.addText(`${item.value ?? 0}`, {
2398
- x, y: y - 0.32, w: barW, h: 0.3,
2399
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center', valign: 'top',
2400
- });
2401
- }
2402
- slide.addText(`${item.label ?? ''}`, {
2403
- x: chartX + idx * slotW, y: chartY + chartH + 0.06, w: slotW, h: 0.3,
2404
- fontSize: 9, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center', valign: 'top',
2405
- });
2406
- });
2407
- // 趋势线(连接柱顶中点)
2408
- if (props.showTrend !== false && points.length > 1) {
2409
- const trendColor = resolveTheme07CssColor('var(--lp-accent-2)', COLORS.accent2);
2410
- for (let i = 0; i < points.length - 1; i++) {
2411
- slide.addShape('line', {
2412
- x1: points[i].x, y1: points[i].y, x2: points[i + 1].x, y2: points[i + 1].y,
2413
- line: { color: trendColor, width: 1.5, transparency: 20 },
2414
- });
2415
- }
2416
- }
2417
- if (props.chartCaption) {
2418
- slide.addText(props.chartCaption, {
2419
- x: chartX, y: chartY + chartH + 0.4, w: chartW, h: 0.3,
2420
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, valign: 'top',
2421
- });
2422
- }
2423
- }
2424
- addTheme07GlowLine(slide);
2425
- }
2426
- export function renderTheme07StatCompareV1(slide, props) {
2427
- if (props.kicker) {
2428
- addTheme07Kicker(slide, props.kicker);
2429
- }
2430
- slide.addText(props.title ?? '', {
2431
- x: 0.65, y: 0.95, w: 8.7, h: 0.7,
2432
- fontSize: 32, bold: true, fontFace: FONTS.heading, color: COLORS.primary, valign: 'top',
2433
- });
2434
- if (props.subtitle) {
2435
- slide.addText(props.subtitle, {
2436
- x: 0.65, y: 1.6, w: 8.7, h: 0.4,
2437
- fontSize: 14, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2438
- });
2439
- }
2440
- const focusSide = props.focusSide ?? 'right';
2441
- const cardY = 2.3;
2442
- const cardW = 3.7;
2443
- const cardH = 2.0;
2444
- const sides = [
2445
- { data: props.left ?? {}, focus: focusSide === 'left', x: 0.65 },
2446
- { data: props.right ?? {}, focus: focusSide === 'right', x: 5.65 },
2447
- ];
2448
- sides.forEach((side) => {
2449
- addTheme07Card(slide, side.x, cardY, cardW, cardH, 0.08, side.focus ? COLORS.accent : COLORS.white);
2450
- const valueColor = side.focus ? COLORS.white : COLORS.accent;
2451
- const subColor = side.focus ? 'D0E0F0' : COLORS.secondary;
2452
- const labelColor = side.focus ? COLORS.white : COLORS.primary;
2453
- slide.addText(`${side.data.value ?? ''}`, {
2454
- x: side.x + 0.2, y: cardY + 0.35, w: cardW - 0.4, h: 0.9,
2455
- fontSize: 58, bold: true, fontFace: FONTS.heading, color: valueColor, valign: 'top',
2456
- });
2457
- if (side.data.unit) {
2458
- slide.addText(side.data.unit, {
2459
- x: side.x + 0.2, y: cardY + 1.25, w: cardW - 0.4, h: 0.3,
2460
- fontSize: 16, color: subColor, fontFace: FONTS.mono, valign: 'top',
2461
- });
2462
- }
2463
- if (side.data.label) {
2464
- slide.addText(side.data.label, {
2465
- x: side.x + 0.2, y: cardY + 1.6, w: cardW - 0.4, h: 0.3,
2466
- fontSize: 12, bold: true, color: labelColor, fontFace: FONTS.body, valign: 'top',
2467
- });
2468
- }
2469
- });
2470
- // 中轴
2471
- if (props.showAxis !== false) {
2472
- slide.addShape('line', {
2473
- x1: 5.0, y1: cardY, x2: 5.0, y2: cardY + cardH,
2474
- line: { color: COLORS.border, width: 1, transparency: 50 },
2475
- });
2476
- }
2477
- // 差值徽标
2478
- if (props.showDelta !== false && props.delta) {
2479
- slide.addText(props.delta, {
2480
- x: 4.25, y: 2.95, w: 1.5, h: 0.5,
2481
- fontSize: 24, bold: true, color: resolveTheme07CssColor('var(--lp-accent-cool)', COLORS.accentCool ?? COLORS.accent), fontFace: FONTS.heading, align: 'center', valign: 'middle',
2482
- });
2483
- if (props.deltaLabel) {
2484
- slide.addText(props.deltaLabel, {
2485
- x: 4.2, y: 3.5, w: 1.6, h: 0.3,
2486
- fontSize: 10, color: COLORS.secondary, fontFace: FONTS.mono, align: 'center', valign: 'top',
2487
- });
2488
- }
2489
- }
2490
- if (props.caption) {
2491
- slide.addText(props.caption, {
2492
- x: 0.65, y: 4.55, w: 8.7, h: 0.35,
2493
- fontSize: 13, color: COLORS.secondary, fontFace: FONTS.body, valign: 'top',
2494
- });
2495
- }
2496
- addTheme07GlowLine(slide);
2497
- }
2498
- export function registerTheme07Renderers(registerPptxLayoutRenderer) {
2499
- registerPptxLayoutRenderer('theme07_cover_v1', renderTheme07CoverV1);
2500
- registerPptxLayoutRenderer('theme07_table_of_contents_v1', renderTheme07TableOfContentsV1);
2501
- registerPptxLayoutRenderer('theme07_chapter_v1', renderTheme07ChapterV1);
2502
- registerPptxLayoutRenderer('theme07_chapter_capital_v1', renderTheme07ChapterCapitalV1);
2503
- registerPptxLayoutRenderer('theme07_chapter_risk_v1', renderTheme07ChapterRiskV1);
2504
- registerPptxLayoutRenderer('theme07_chapter_appendix_v1', renderTheme07ChapterAppendixV1);
2505
- registerPptxLayoutRenderer('theme07_content_v1', renderTheme07ContentV1);
2506
- registerPptxLayoutRenderer('theme07_summary_v1', renderTheme07SummaryV1);
2507
- registerPptxLayoutRenderer('theme07_ranking_v1', renderTheme07RankingV1);
2508
- registerPptxLayoutRenderer('theme07_case_v1', renderTheme07CaseV1);
2509
- registerPptxLayoutRenderer('theme07_case_grid_v1', renderTheme07CaseV1);
2510
- registerPptxLayoutRenderer('theme07_sources_v1', renderTheme07SourcesV1);
2511
- registerPptxLayoutRenderer('theme07_method_v1', renderTheme07MethodV1);
2512
- registerPptxLayoutRenderer('theme07_monthly_v1', renderTheme07MonthlyV1);
2513
- registerPptxLayoutRenderer('theme07_waterfall_v1', renderTheme07WaterfallV1);
2514
- registerPptxLayoutRenderer('theme07_matrix_v1', renderTheme07MatrixV1);
2515
- registerPptxLayoutRenderer('theme07_risk_v1', renderTheme07RiskV1);
2516
- registerPptxLayoutRenderer('theme07_cover_lean_v1', renderTheme07CoverLeanV1);
2517
- registerPptxLayoutRenderer('theme07_cover_supply_chain_v1', renderTheme07CoverSupplyChainV1);
2518
- registerPptxLayoutRenderer('theme07_cover_retail_trend_v1', renderTheme07CoverRetailTrendV1);
2519
- registerPptxLayoutRenderer('theme07_cover_supply_strategy_v1', renderTheme07CoverSupplyStrategyV1);
2520
- registerPptxLayoutRenderer('theme07_peak_v1', renderTheme07PeakV1);
2521
- registerPptxLayoutRenderer('theme07_cooldown_v1', renderTheme07CooldownV1);
2522
- registerPptxLayoutRenderer('theme07_peak_trough_v1', renderTheme07PeakTroughV1);
2523
- registerPptxLayoutRenderer('theme07_deal_size_v1', renderTheme07DealSizeV1);
2524
- registerPptxLayoutRenderer('theme07_avg_ticket_v1', renderTheme07AvgTicketV1);
2525
- registerPptxLayoutRenderer('theme07_outlook_v1', renderTheme07OutlookV1);
2526
- registerPptxLayoutRenderer('theme07_repricing_v1', renderTheme07RepricingV1);
2527
- registerPptxLayoutRenderer('theme07_deal_map_v1', renderTheme07DealMapV1);
2528
- registerPptxLayoutRenderer('theme07_cold_start_v1', renderTheme07ColdStartV1);
2529
- registerPptxLayoutRenderer('theme07_accelerate_v1', renderTheme07AccelerateV1);
2530
- registerPptxLayoutRenderer('theme07_investor_v1', renderTheme07InvestorV1);
2531
- registerPptxLayoutRenderer('theme07_active_capital_v1', renderTheme07ActiveCapitalV1);
2532
- registerPptxLayoutRenderer('theme07_concentration_v1', renderTheme07ConcentrationV1);
2533
- registerPptxLayoutRenderer('theme07_syndicate_v1', renderTheme07SyndicateV1);
2534
- registerPptxLayoutRenderer('theme07_company_openai_v1', renderTheme07CompanyV1);
2535
- registerPptxLayoutRenderer('theme07_company_figure_v1', renderTheme07CompanyV1);
2536
- registerPptxLayoutRenderer('theme07_company_ssi_v1', renderTheme07CompanyV1);
2537
- registerPptxLayoutRenderer('theme07_geo_center_v1', renderTheme07GeoV1);
2538
- registerPptxLayoutRenderer('theme07_region_cluster_v1', renderTheme07GeoV1);
2539
- registerPptxLayoutRenderer('theme07_resource_triad_v1', renderTheme07GeoV1);
2540
- registerPptxLayoutRenderer('theme07_quote_v1', renderTheme07ClosingV1);
2541
- registerPptxLayoutRenderer('theme07_closing_v1', renderTheme07ClosingV1);
2542
- registerPptxLayoutRenderer('theme07_closing_quote_v1', renderTheme07ClosingQuoteV1);
2543
- registerPptxLayoutRenderer('theme07_forward_v1', renderTheme07ClosingV1);
2544
- registerPptxLayoutRenderer('theme07_about_lab_v1', renderTheme07ClosingV1);
2545
- registerPptxLayoutRenderer('theme07_stat_hero_v1', renderTheme07StatHeroV1);
2546
- registerPptxLayoutRenderer('theme07_stat_row_v1', renderTheme07StatRowV1);
2547
- registerPptxLayoutRenderer('theme07_stat_chart_v1', renderTheme07StatChartV1);
2548
- registerPptxLayoutRenderer('theme07_stat_compare_v1', renderTheme07StatCompareV1);
2549
- // 行业纵深版式(P 批次):统一复用通用 sector 渲染器,字段与 Theme07SectorLayoutProps 一致
2550
- registerPptxLayoutRenderer('theme07_knowledge_v1', renderTheme07SectorV1);
2551
- registerPptxLayoutRenderer('theme07_legal_v1', renderTheme07SectorV1);
2552
- registerPptxLayoutRenderer('theme07_healthcare_v1', renderTheme07SectorV1);
2553
- registerPptxLayoutRenderer('theme07_finance_v1', renderTheme07SectorV1);
2554
- registerPptxLayoutRenderer('theme07_compute_v1', renderTheme07SectorV1);
2555
- registerPptxLayoutRenderer('theme07_chip_v1', renderTheme07SectorV1);
2556
- registerPptxLayoutRenderer('theme07_robotics_v1', renderTheme07SectorV1);
2557
- registerPptxLayoutRenderer('theme07_autonomy_v1', renderTheme07SectorV1);
2558
- registerPptxLayoutRenderer('theme07_safety_v1', renderTheme07SectorV1);
2559
- registerPptxLayoutRenderer('theme07_content_gen_v1', renderTheme07SectorV1);
2560
- registerPptxLayoutRenderer('theme07_education_v1', renderTheme07SectorV1);
2561
- registerPptxLayoutRenderer('theme07_support_v1', renderTheme07SectorV1);
2562
- registerPptxLayoutRenderer('theme07_sales_v1', renderTheme07SectorV1);
2563
- registerPptxLayoutRenderer('theme07_low_code_v1', renderTheme07SectorV1);
2564
- registerPptxLayoutRenderer('theme07_open_source_v1', renderTheme07SectorV1);
2565
- registerPptxLayoutRenderer('theme07_alignment_v1', renderTheme07SectorV1);
2566
- registerPptxLayoutRenderer('theme07_early_stage_v1', renderTheme07SectorV1);
2567
- registerPptxLayoutRenderer('theme07_deal_structure_v1', renderTheme07SectorV1);
2568
- registerPptxLayoutRenderer('theme07_investor_mix_v1', renderTheme07SectorV1);
2569
- registerPptxLayoutRenderer('theme07_resource_v1', renderTheme07SectorV1);
2570
- registerPptxLayoutRenderer('theme07_alliance_v1', renderTheme07SectorV1);
2571
- registerPptxLayoutRenderer('theme07_ecosystem_v1', renderTheme07SectorV1);
2572
- registerPptxLayoutRenderer('theme07_revenue_v1', renderTheme07SectorV1);
2573
- registerPptxLayoutRenderer('theme07_compliance_v1', renderTheme07SectorV1);
2574
- registerPptxLayoutRenderer('theme07_margin_v1', renderTheme07SectorV1);
2575
- registerPptxLayoutRenderer('theme07_moat_v1', renderTheme07SectorV1);
2576
- registerPptxLayoutRenderer('theme07_strategy_infra_v1', renderTheme07SectorV1);
2577
- registerPptxLayoutRenderer('theme07_strategy_vertical_v1', renderTheme07SectorV1);
2578
- }
2579
- //# sourceMappingURL=theme07-pptx.js.map