@lemonppt/renderer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,845 @@
1
+ import PptxGenJS from 'pptxgenjs';
2
+ const COLORS = {
3
+ primary: '0F172A',
4
+ secondary: '64748B',
5
+ accent: '3B82F6',
6
+ white: 'FFFFFF',
7
+ light: 'F1F5F9',
8
+ border: 'E2E8F0',
9
+ };
10
+ const FONTS = {
11
+ heading: 'Inter',
12
+ body: 'Inter',
13
+ mono: 'JetBrains Mono',
14
+ };
15
+ export async function exportDeckToPptx(goal, options) {
16
+ const { outFile, title = goal.title, subject, author } = options;
17
+ const pptx = new PptxGenJS();
18
+ pptx.layout = 'LAYOUT_16x9';
19
+ pptx.title = title;
20
+ if (subject)
21
+ pptx.subject = subject;
22
+ if (author)
23
+ pptx.author = author;
24
+ for (const slide of goal.slides) {
25
+ const pptxSlide = pptx.addSlide();
26
+ renderSlideToPptx(pptxSlide, slide);
27
+ }
28
+ await pptx.writeFile({ fileName: outFile });
29
+ }
30
+ function renderSlideToPptx(pptxSlide, slide) {
31
+ switch (slide.layout) {
32
+ case 'cover_v1':
33
+ renderCoverV1(pptxSlide, slide.props);
34
+ break;
35
+ case 'table_of_contents_v1':
36
+ renderTableOfContentsV1(pptxSlide, slide.props);
37
+ break;
38
+ case 'metric_v1':
39
+ renderMetricV1(pptxSlide, slide.props);
40
+ break;
41
+ case 'metric_v2':
42
+ renderMetricV2(pptxSlide, slide.props);
43
+ break;
44
+ case 'stats_v1':
45
+ renderStatsV1(pptxSlide, slide.props);
46
+ break;
47
+ case 'chart_v1':
48
+ renderChartV1(pptxSlide, slide.props);
49
+ break;
50
+ case 'content_v1':
51
+ renderContentV1(pptxSlide, slide.props);
52
+ break;
53
+ case 'content_v2':
54
+ renderContentV2(pptxSlide, slide.props);
55
+ break;
56
+ case 'content_v3':
57
+ renderContentV3(pptxSlide, slide.props);
58
+ break;
59
+ case 'split_v1':
60
+ renderSplitV1(pptxSlide, slide.props);
61
+ break;
62
+ case 'comparison_v1':
63
+ renderComparisonV1(pptxSlide, slide.props);
64
+ break;
65
+ case 'comparison_v2':
66
+ renderComparisonV2(pptxSlide, slide.props);
67
+ break;
68
+ case 'process_v1':
69
+ renderProcessV1(pptxSlide, slide.props);
70
+ break;
71
+ case 'process_v2':
72
+ renderProcessV2(pptxSlide, slide.props);
73
+ break;
74
+ case 'timeline_v1':
75
+ renderTimelineV1(pptxSlide, slide.props);
76
+ break;
77
+ case 'roadmap_v1':
78
+ renderRoadmapV1(pptxSlide, slide.props);
79
+ break;
80
+ case 'quote_v1':
81
+ renderQuoteV1(pptxSlide, slide.props);
82
+ break;
83
+ case 'quote_v2':
84
+ renderQuoteV2(pptxSlide, slide.props);
85
+ break;
86
+ case 'testimonial_v1':
87
+ renderTestimonialV1(pptxSlide, slide.props);
88
+ break;
89
+ case 'faq_v1':
90
+ renderFaqV1(pptxSlide, slide.props);
91
+ break;
92
+ case 'feature_v1':
93
+ renderFeatureV1(pptxSlide, slide.props);
94
+ break;
95
+ case 'team_v1':
96
+ renderTeamV1(pptxSlide, slide.props);
97
+ break;
98
+ case 'partners_v1':
99
+ renderPartnersV1(pptxSlide, slide.props);
100
+ break;
101
+ case 'pricing_v1':
102
+ renderPricingV1(pptxSlide, slide.props);
103
+ break;
104
+ case 'gallery_v1':
105
+ renderGalleryV1(pptxSlide, slide.props);
106
+ break;
107
+ case 'image_v1':
108
+ renderImageV1(pptxSlide, slide.props);
109
+ break;
110
+ case 'swot_v1':
111
+ renderSwotV1(pptxSlide, slide.props);
112
+ break;
113
+ case 'pest_v1':
114
+ renderPestV1(pptxSlide, slide.props);
115
+ break;
116
+ case 'closing_v1':
117
+ renderClosingV1(pptxSlide, slide.props);
118
+ break;
119
+ case 'closing_v2':
120
+ renderClosingV2(pptxSlide, slide.props);
121
+ break;
122
+ default:
123
+ pptxSlide.addText(`Unknown layout: ${slide.layout}`, {
124
+ x: 0.5, y: 3.5, w: 9, h: 1,
125
+ fontSize: 18, color: 'EF4444', align: 'center',
126
+ });
127
+ }
128
+ }
129
+ // ---- Shared helpers --------------------------------------------------------
130
+ function addKicker(slide, kicker, x = 0.8, y = 0.8) {
131
+ if (!kicker)
132
+ return;
133
+ slide.addText(kicker, {
134
+ x, y, w: 8.4, h: 0.4,
135
+ fontSize: 14, color: COLORS.accent, align: 'left',
136
+ fontFace: FONTS.mono,
137
+ });
138
+ }
139
+ function addTitle(slide, title, x = 0.8, y = 1.3, w = 8.4, h = 0.9, fontSize = 44) {
140
+ slide.addText(title, {
141
+ x, y, w, h,
142
+ fontSize, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
143
+ fontFace: FONTS.heading,
144
+ });
145
+ }
146
+ function addBulletList(slide, items, x, y, w, maxItems = 8) {
147
+ const list = items.slice(0, maxItems);
148
+ let cy = y;
149
+ for (const item of list) {
150
+ slide.addShape('ellipse', {
151
+ x: x + 0.05, y: cy + 0.16, w: 0.1, h: 0.1,
152
+ fill: { color: COLORS.accent },
153
+ });
154
+ slide.addText(item, {
155
+ x: x + 0.3, y: cy, w: w - 0.3, h: 0.55,
156
+ fontSize: 20, color: COLORS.primary, align: 'left', valign: 'top',
157
+ fontFace: FONTS.body,
158
+ });
159
+ cy += 0.7;
160
+ }
161
+ return cy;
162
+ }
163
+ function addImageMaybe(slide, url, x, y, w, h) {
164
+ if (!url)
165
+ return;
166
+ // pptxgenjs 在 Node 环境无法直接下载远程 URL,仅当为本地文件或 base64 时可用。
167
+ try {
168
+ slide.addImage({ path: url, x, y, w, h, sizing: { type: 'crop', w, h } });
169
+ }
170
+ catch {
171
+ slide.addText('[图片]', { x, y, w, h, fontSize: 14, color: COLORS.secondary, align: 'center', valign: 'middle' });
172
+ }
173
+ }
174
+ function renderCoverV1(slide, props) {
175
+ if (props.image)
176
+ addImageMaybe(slide, props.image, 0, 0, 10, 5.625);
177
+ if (props.kicker) {
178
+ slide.addText(props.kicker, {
179
+ x: 1, y: 2.0, w: 8, h: 0.4,
180
+ fontSize: 14, color: COLORS.accent, align: 'center',
181
+ fontFace: FONTS.mono,
182
+ });
183
+ }
184
+ slide.addText(props.title, {
185
+ x: 1, y: 2.5, w: 8, h: 1.2,
186
+ fontSize: 54, color: COLORS.primary, bold: true, align: 'center',
187
+ fontFace: FONTS.heading,
188
+ });
189
+ if (props.subtitle) {
190
+ slide.addText(props.subtitle, {
191
+ x: 1.5, y: 3.8, w: 7, h: 0.8,
192
+ fontSize: 22, color: COLORS.secondary, align: 'center',
193
+ fontFace: FONTS.body,
194
+ });
195
+ }
196
+ if (props.date) {
197
+ slide.addText(props.date, {
198
+ x: 1, y: 5.0, w: 8, h: 0.3,
199
+ fontSize: 14, color: COLORS.secondary, align: 'center',
200
+ fontFace: FONTS.mono,
201
+ });
202
+ }
203
+ }
204
+ function renderTableOfContentsV1(slide, props) {
205
+ addKicker(slide, props.kicker);
206
+ addTitle(slide, props.title);
207
+ const items = props.items || [];
208
+ let y = 2.6;
209
+ items.forEach((item, index) => {
210
+ const number = String(index + 1).padStart(2, '0');
211
+ slide.addText(number, {
212
+ x: 0.8, y, w: 0.8, h: 0.5,
213
+ fontSize: 18, color: COLORS.accent, align: 'left', valign: 'top',
214
+ fontFace: FONTS.mono,
215
+ });
216
+ slide.addText(item, {
217
+ x: 1.7, y, w: 7.8, h: 0.5,
218
+ fontSize: 26, color: COLORS.primary, align: 'left', valign: 'top',
219
+ fontFace: FONTS.body,
220
+ });
221
+ y += 0.9;
222
+ });
223
+ }
224
+ function renderMetricV1(slide, props) {
225
+ if (props.label) {
226
+ slide.addText(props.label, {
227
+ x: 1, y: 1.8, w: 8, h: 0.4,
228
+ fontSize: 16, color: COLORS.secondary, align: 'center',
229
+ fontFace: FONTS.body,
230
+ });
231
+ }
232
+ const metricText = props.unit ? `${props.value} ${props.unit}` : props.value;
233
+ slide.addText(metricText, {
234
+ x: 1, y: 2.4, w: 8, h: 1.4,
235
+ fontSize: 90, color: COLORS.primary, bold: true, align: 'center',
236
+ fontFace: FONTS.heading,
237
+ });
238
+ if (props.description) {
239
+ slide.addText(props.description, {
240
+ x: 1.5, y: 4.1, w: 7, h: 0.8,
241
+ fontSize: 20, color: COLORS.secondary, align: 'center',
242
+ fontFace: FONTS.body,
243
+ });
244
+ }
245
+ }
246
+ function renderMetricV2(slide, props) {
247
+ if (props.kicker) {
248
+ slide.addText(props.kicker, {
249
+ x: 1, y: 1.8, w: 8, h: 0.4,
250
+ fontSize: 14, color: COLORS.accent, align: 'center',
251
+ fontFace: FONTS.mono,
252
+ });
253
+ }
254
+ const metricText = props.unit ? `${props.value} ${props.unit}` : props.value;
255
+ slide.addText(metricText, {
256
+ x: 1, y: 2.5, w: 8, h: 1.4,
257
+ fontSize: 80, color: COLORS.primary, bold: true, align: 'center',
258
+ fontFace: FONTS.heading,
259
+ });
260
+ if (props.description) {
261
+ slide.addText(props.description, {
262
+ x: 1.5, y: 4.1, w: 7, h: 0.8,
263
+ fontSize: 20, color: COLORS.secondary, align: 'center',
264
+ fontFace: FONTS.body,
265
+ });
266
+ }
267
+ }
268
+ function renderStatsV1(slide, props) {
269
+ addKicker(slide, props.kicker);
270
+ addTitle(slide, props.title);
271
+ const stats = (props.stats || []).slice(0, 4);
272
+ const positions = [
273
+ { x: 0.8, y: 2.5 },
274
+ { x: 5.2, y: 2.5 },
275
+ { x: 0.8, y: 4.0 },
276
+ { x: 5.2, y: 4.0 },
277
+ ];
278
+ stats.forEach((stat, index) => {
279
+ const pos = positions[index];
280
+ if (!pos)
281
+ return;
282
+ slide.addShape('rect', {
283
+ x: pos.x, y: pos.y, w: 4.2, h: 1.3,
284
+ fill: { color: COLORS.light },
285
+ line: { color: COLORS.border, width: 1 },
286
+ });
287
+ const valueText = [stat.value, stat.unit].filter(Boolean).join(' ');
288
+ slide.addText(valueText || '-', {
289
+ x: pos.x + 0.2, y: pos.y + 0.15, w: 3.8, h: 0.55,
290
+ fontSize: 30, color: COLORS.primary, bold: true, align: 'left',
291
+ fontFace: FONTS.heading,
292
+ });
293
+ slide.addText(stat.label || '', {
294
+ x: pos.x + 0.2, y: pos.y + 0.75, w: 2.6, h: 0.35,
295
+ fontSize: 14, color: COLORS.secondary, align: 'left',
296
+ fontFace: FONTS.body,
297
+ });
298
+ if (stat.change) {
299
+ slide.addText(stat.change, {
300
+ x: pos.x + 2.8, y: pos.y + 0.75, w: 1.2, h: 0.35,
301
+ fontSize: 14, color: COLORS.accent, align: 'right',
302
+ fontFace: FONTS.mono,
303
+ });
304
+ }
305
+ });
306
+ }
307
+ function renderChartV1(slide, props) {
308
+ addKicker(slide, props.kicker);
309
+ addTitle(slide, props.title);
310
+ const labels = props.labels || [];
311
+ const data = props.data || [];
312
+ if (labels.length === 0 || data.length === 0)
313
+ return;
314
+ const chartType = props.type || 'bar';
315
+ const chartData = [
316
+ {
317
+ name: props.title,
318
+ labels,
319
+ values: data,
320
+ },
321
+ ];
322
+ slide.addChart(chartType, chartData, {
323
+ x: 0.8, y: 2.3, w: 8.4, h: 3.2,
324
+ chartColors: [COLORS.accent],
325
+ showValue: true,
326
+ dataLabelColor: COLORS.primary,
327
+ dataLabelFontSize: 10,
328
+ });
329
+ }
330
+ function renderContentV1(slide, props) {
331
+ addKicker(slide, props.kicker);
332
+ addTitle(slide, props.title);
333
+ addBulletList(slide, props.points || [], 0.8, 2.8, 8.4);
334
+ }
335
+ function renderContentV2(slide, props) {
336
+ addKicker(slide, props.kicker);
337
+ addTitle(slide, props.title);
338
+ slide.addText('左栏', {
339
+ x: 0.8, y: 2.4, w: 4.0, h: 0.4,
340
+ fontSize: 18, color: COLORS.accent, bold: true, align: 'left',
341
+ fontFace: FONTS.heading,
342
+ });
343
+ slide.addText('右栏', {
344
+ x: 5.2, y: 2.4, w: 4.0, h: 0.4,
345
+ fontSize: 18, color: COLORS.accent, bold: true, align: 'left',
346
+ fontFace: FONTS.heading,
347
+ });
348
+ addBulletList(slide, props.leftPoints || [], 0.8, 2.9, 4.0);
349
+ addBulletList(slide, props.rightPoints || [], 5.2, 2.9, 4.0);
350
+ }
351
+ function renderContentV3(slide, props) {
352
+ addKicker(slide, props.kicker);
353
+ addTitle(slide, props.title, 0.8, 1.3, 4.5);
354
+ addBulletList(slide, props.points || [], 0.8, 2.5, 4.2);
355
+ addImageMaybe(slide, props.imageUrl, 5.3, 1.5, 4.0, 3.5);
356
+ }
357
+ function renderSplitV1(slide, props) {
358
+ renderContentV3(slide, props);
359
+ }
360
+ function renderComparisonV1(slide, props) {
361
+ addKicker(slide, props.kicker);
362
+ addTitle(slide, props.title);
363
+ renderComparisonColumns(slide, props.leftTitle || '方案 A', props.leftPoints || [], props.rightTitle || '方案 B', props.rightPoints || []);
364
+ }
365
+ function renderComparisonV2(slide, props) {
366
+ addKicker(slide, props.kicker);
367
+ addTitle(slide, props.title);
368
+ renderComparisonColumns(slide, props.leftTitle || '优点', props.leftPoints || [], props.rightTitle || '缺点', props.rightPoints || []);
369
+ }
370
+ function renderComparisonColumns(slide, leftTitle, leftPoints, rightTitle, rightPoints) {
371
+ slide.addText(leftTitle, {
372
+ x: 0.8, y: 2.4, w: 4.0, h: 0.5,
373
+ fontSize: 24, color: COLORS.primary, bold: true, align: 'left',
374
+ fontFace: FONTS.heading,
375
+ });
376
+ slide.addText(rightTitle, {
377
+ x: 5.2, y: 2.4, w: 4.0, h: 0.5,
378
+ fontSize: 24, color: COLORS.primary, bold: true, align: 'left',
379
+ fontFace: FONTS.heading,
380
+ });
381
+ addBulletList(slide, leftPoints, 0.8, 3.0, 4.0);
382
+ addBulletList(slide, rightPoints, 5.2, 3.0, 4.0);
383
+ }
384
+ function renderProcessV1(slide, props) {
385
+ addKicker(slide, props.kicker);
386
+ addTitle(slide, props.title);
387
+ const steps = props.steps || [];
388
+ const count = steps.length;
389
+ if (count === 0)
390
+ return;
391
+ const stepWidth = 8.4 / count;
392
+ steps.forEach((step, index) => {
393
+ const x = 0.8 + index * stepWidth;
394
+ slide.addShape('ellipse', {
395
+ x: x + stepWidth / 2 - 0.3, y: 2.6, w: 0.6, h: 0.6,
396
+ fill: { color: COLORS.accent },
397
+ });
398
+ slide.addText(String(index + 1), {
399
+ x: x + stepWidth / 2 - 0.3, y: 2.6, w: 0.6, h: 0.6,
400
+ fontSize: 22, color: COLORS.white, bold: true, align: 'center', valign: 'middle',
401
+ fontFace: FONTS.heading,
402
+ });
403
+ slide.addText(step, {
404
+ x: x + 0.1, y: 3.5, w: stepWidth - 0.2, h: 1.2,
405
+ fontSize: 18, color: COLORS.primary, align: 'center', valign: 'top',
406
+ fontFace: FONTS.body,
407
+ });
408
+ });
409
+ }
410
+ function renderProcessV2(slide, props) {
411
+ addKicker(slide, props.kicker);
412
+ addTitle(slide, props.title);
413
+ const steps = (props.steps || []).slice(0, 4);
414
+ let y = 2.5;
415
+ steps.forEach((step, index) => {
416
+ slide.addShape('ellipse', {
417
+ x: 0.9, y: y + 0.12, w: 0.4, h: 0.4,
418
+ fill: { color: COLORS.accent },
419
+ });
420
+ slide.addText(String(index + 1), {
421
+ x: 0.9, y: y + 0.12, w: 0.4, h: 0.4,
422
+ fontSize: 16, color: COLORS.white, bold: true, align: 'center', valign: 'middle',
423
+ fontFace: FONTS.heading,
424
+ });
425
+ slide.addText(step.title || '', {
426
+ x: 1.6, y, w: 7.6, h: 0.35,
427
+ fontSize: 20, color: COLORS.primary, bold: true, align: 'left',
428
+ fontFace: FONTS.heading,
429
+ });
430
+ slide.addText(step.description || '', {
431
+ x: 1.6, y: y + 0.38, w: 7.6, h: 0.5,
432
+ fontSize: 16, color: COLORS.secondary, align: 'left', valign: 'top',
433
+ fontFace: FONTS.body,
434
+ });
435
+ y += 1.0;
436
+ });
437
+ }
438
+ function renderTimelineV1(slide, props) {
439
+ addKicker(slide, props.kicker);
440
+ addTitle(slide, props.title);
441
+ const milestones = (props.milestones || []).slice(0, 4);
442
+ const count = milestones.length || 1;
443
+ const stepWidth = 8.4 / count;
444
+ milestones.forEach((m, index) => {
445
+ const cx = 0.8 + stepWidth * index + stepWidth / 2;
446
+ slide.addShape('ellipse', {
447
+ x: cx - 0.2, y: 3.0, w: 0.4, h: 0.4,
448
+ fill: { color: COLORS.accent },
449
+ });
450
+ slide.addShape('line', {
451
+ x1: 0.8 + stepWidth * index, y1: 3.2, x2: 0.8 + stepWidth * (index + 1), y2: 3.2,
452
+ line: { color: COLORS.border, width: 2 },
453
+ });
454
+ slide.addText(m.date || '', {
455
+ x: cx - stepWidth / 2 + 0.1, y: 2.4, w: stepWidth - 0.2, h: 0.4,
456
+ fontSize: 12, color: COLORS.accent, align: 'center', valign: 'top',
457
+ fontFace: FONTS.mono,
458
+ });
459
+ slide.addText(m.title || '', {
460
+ x: cx - stepWidth / 2 + 0.1, y: 3.6, w: stepWidth - 0.2, h: 0.4,
461
+ fontSize: 14, color: COLORS.primary, bold: true, align: 'center', valign: 'top',
462
+ fontFace: FONTS.heading,
463
+ });
464
+ slide.addText(m.description || '', {
465
+ x: cx - stepWidth / 2 + 0.1, y: 4.05, w: stepWidth - 0.2, h: 0.8,
466
+ fontSize: 12, color: COLORS.secondary, align: 'center', valign: 'top',
467
+ fontFace: FONTS.body,
468
+ });
469
+ });
470
+ }
471
+ function renderRoadmapV1(slide, props) {
472
+ addKicker(slide, props.kicker);
473
+ addTitle(slide, props.title);
474
+ const phases = (props.phases || []).slice(0, 4);
475
+ let y = 2.5;
476
+ phases.forEach((phase, index) => {
477
+ const statusColor = phase.status === '已完成' ? COLORS.accent : COLORS.secondary;
478
+ slide.addShape('rect', {
479
+ x: 0.8, y, w: 0.2, h: 0.7,
480
+ fill: { color: statusColor },
481
+ });
482
+ slide.addText(`${index + 1}. ${phase.title || ''}`, {
483
+ x: 1.1, y, w: 7.8, h: 0.35,
484
+ fontSize: 18, color: COLORS.primary, bold: true, align: 'left',
485
+ fontFace: FONTS.heading,
486
+ });
487
+ slide.addText(phase.description || '', {
488
+ x: 1.1, y: y + 0.38, w: 7.8, h: 0.35,
489
+ fontSize: 14, color: COLORS.secondary, align: 'left', valign: 'top',
490
+ fontFace: FONTS.body,
491
+ });
492
+ y += 1.0;
493
+ });
494
+ }
495
+ function renderQuoteV1(slide, props) {
496
+ slide.addText(`“${props.quote}”`, {
497
+ x: 1, y: 2.0, w: 8, h: 2.2,
498
+ fontSize: 36, color: COLORS.primary, bold: true, align: 'center', valign: 'middle',
499
+ fontFace: FONTS.heading,
500
+ });
501
+ const attribution = [props.author, props.source].filter(Boolean).join(' · ');
502
+ if (attribution) {
503
+ slide.addText(attribution, {
504
+ x: 1, y: 4.4, w: 8, h: 0.4,
505
+ fontSize: 18, color: COLORS.secondary, align: 'center',
506
+ fontFace: FONTS.mono,
507
+ });
508
+ }
509
+ }
510
+ function renderQuoteV2(slide, props) {
511
+ slide.addShape('rect', {
512
+ x: 0.6, y: 1.5, w: 0.15, h: 2.8,
513
+ fill: { color: COLORS.accent },
514
+ });
515
+ slide.addText(`“${props.quote}”`, {
516
+ x: 1.0, y: 1.8, w: 8.0, h: 2.0,
517
+ fontSize: 34, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
518
+ fontFace: FONTS.heading,
519
+ });
520
+ const attribution = [props.author, props.role].filter(Boolean).join(' · ');
521
+ if (attribution) {
522
+ slide.addText(attribution, {
523
+ x: 1.0, y: 4.0, w: 8.0, h: 0.4,
524
+ fontSize: 18, color: COLORS.secondary, align: 'left',
525
+ fontFace: FONTS.mono,
526
+ });
527
+ }
528
+ }
529
+ function renderTestimonialV1(slide, props) {
530
+ slide.addText(`“${props.quote}”`, {
531
+ x: 1, y: 1.8, w: 8, h: 2.2,
532
+ fontSize: 34, color: COLORS.primary, bold: true, align: 'center', valign: 'middle',
533
+ fontFace: FONTS.heading,
534
+ });
535
+ if (props.avatarUrl)
536
+ addImageMaybe(slide, props.avatarUrl, 4.5, 4.1, 1, 1);
537
+ const attribution = [props.author, props.role, props.company].filter(Boolean).join(' · ');
538
+ if (attribution) {
539
+ slide.addText(attribution, {
540
+ x: 1, y: 5.0, w: 8, h: 0.4,
541
+ fontSize: 16, color: COLORS.secondary, align: 'center',
542
+ fontFace: FONTS.mono,
543
+ });
544
+ }
545
+ }
546
+ function renderFaqV1(slide, props) {
547
+ addKicker(slide, props.kicker);
548
+ addTitle(slide, props.title);
549
+ const items = (props.items || []).slice(0, 4);
550
+ let y = 2.5;
551
+ items.forEach((item) => {
552
+ slide.addText(`Q: ${item.q || ''}`, {
553
+ x: 0.8, y, w: 8.4, h: 0.35,
554
+ fontSize: 18, color: COLORS.primary, bold: true, align: 'left', valign: 'top',
555
+ fontFace: FONTS.heading,
556
+ });
557
+ slide.addText(`A: ${item.a || ''}`, {
558
+ x: 0.8, y: y + 0.4, w: 8.4, h: 0.55,
559
+ fontSize: 16, color: COLORS.secondary, align: 'left', valign: 'top',
560
+ fontFace: FONTS.body,
561
+ });
562
+ y += 1.1;
563
+ });
564
+ }
565
+ function renderFeatureV1(slide, props) {
566
+ addKicker(slide, props.kicker);
567
+ addTitle(slide, props.title);
568
+ const features = (props.features || []).slice(0, 3);
569
+ const cardWidth = 2.6;
570
+ const gap = 0.3;
571
+ const startX = (10 - (features.length * cardWidth + (features.length - 1) * gap)) / 2;
572
+ features.forEach((feature, index) => {
573
+ const x = startX + index * (cardWidth + gap);
574
+ slide.addShape('rect', {
575
+ x, y: 2.8, w: cardWidth, h: 2.2,
576
+ fill: { color: COLORS.light },
577
+ line: { color: COLORS.border, width: 1 },
578
+ });
579
+ slide.addText(feature.title || '', {
580
+ x: x + 0.15, y: 3.0, w: cardWidth - 0.3, h: 0.5,
581
+ fontSize: 20, color: COLORS.primary, bold: true, align: 'center',
582
+ fontFace: FONTS.heading,
583
+ });
584
+ slide.addText(feature.description || '', {
585
+ x: x + 0.15, y: 3.6, w: cardWidth - 0.3, h: 1.2,
586
+ fontSize: 14, color: COLORS.secondary, align: 'center', valign: 'top',
587
+ fontFace: FONTS.body,
588
+ });
589
+ });
590
+ }
591
+ function renderTeamV1(slide, props) {
592
+ addKicker(slide, props.kicker);
593
+ addTitle(slide, props.title);
594
+ const members = (props.members || []).slice(0, 4);
595
+ const cardWidth = 2.0;
596
+ const gap = 0.4;
597
+ const startX = (10 - (members.length * cardWidth + (members.length - 1) * gap)) / 2;
598
+ members.forEach((member, index) => {
599
+ const x = startX + index * (cardWidth + gap);
600
+ if (member.imageUrl)
601
+ addImageMaybe(slide, member.imageUrl, x + 0.5, 2.7, 1, 1);
602
+ slide.addText(member.name || '', {
603
+ x, y: 3.85, w: cardWidth, h: 0.35,
604
+ fontSize: 16, color: COLORS.primary, bold: true, align: 'center',
605
+ fontFace: FONTS.heading,
606
+ });
607
+ slide.addText(member.role || '', {
608
+ x, y: 4.2, w: cardWidth, h: 0.3,
609
+ fontSize: 12, color: COLORS.accent, align: 'center',
610
+ fontFace: FONTS.mono,
611
+ });
612
+ slide.addText(member.bio || '', {
613
+ x, y: 4.55, w: cardWidth, h: 0.55,
614
+ fontSize: 11, color: COLORS.secondary, align: 'center', valign: 'top',
615
+ fontFace: FONTS.body,
616
+ });
617
+ });
618
+ }
619
+ function renderPartnersV1(slide, props) {
620
+ addKicker(slide, props.kicker);
621
+ addTitle(slide, props.title);
622
+ const partners = (props.partners || []).slice(0, 8);
623
+ const cols = 4;
624
+ const cellW = 2.0;
625
+ const cellH = 1.1;
626
+ const gapX = 0.4;
627
+ const gapY = 0.3;
628
+ const startX = (10 - (cols * cellW + (cols - 1) * gapX)) / 2;
629
+ partners.forEach((partner, index) => {
630
+ const row = Math.floor(index / cols);
631
+ const col = index % cols;
632
+ const x = startX + col * (cellW + gapX);
633
+ const y = 2.5 + row * (cellH + gapY);
634
+ slide.addShape('rect', {
635
+ x, y, w: cellW, h: cellH,
636
+ fill: { color: COLORS.light },
637
+ line: { color: COLORS.border, width: 1 },
638
+ });
639
+ if (partner.logoUrl)
640
+ addImageMaybe(slide, partner.logoUrl, x + 0.25, y + 0.15, cellW - 0.5, cellH - 0.5);
641
+ slide.addText(partner.name || '', {
642
+ x, y: y + cellH - 0.25, w: cellW, h: 0.25,
643
+ fontSize: 12, color: COLORS.secondary, align: 'center',
644
+ fontFace: FONTS.body,
645
+ });
646
+ });
647
+ }
648
+ function renderPricingV1(slide, props) {
649
+ addKicker(slide, props.kicker);
650
+ addTitle(slide, props.title);
651
+ const tiers = (props.tiers || []).slice(0, 3);
652
+ const cardWidth = 2.5;
653
+ const gap = 0.35;
654
+ const startX = (10 - (tiers.length * cardWidth + (tiers.length - 1) * gap)) / 2;
655
+ tiers.forEach((tier, index) => {
656
+ const x = startX + index * (cardWidth + gap);
657
+ const isHighlight = index === 1;
658
+ slide.addShape('rect', {
659
+ x, y: 2.6, w: cardWidth, h: 2.6,
660
+ fill: { color: isHighlight ? COLORS.accent : COLORS.light },
661
+ line: { color: COLORS.border, width: 1 },
662
+ });
663
+ slide.addText(tier.name || '', {
664
+ x, y: 2.8, w: cardWidth, h: 0.4,
665
+ fontSize: 18, color: isHighlight ? COLORS.white : COLORS.primary, bold: true, align: 'center',
666
+ fontFace: FONTS.heading,
667
+ });
668
+ slide.addText([tier.price, tier.period].filter(Boolean).join(' '), {
669
+ x, y: 3.25, w: cardWidth, h: 0.5,
670
+ fontSize: 24, color: isHighlight ? COLORS.white : COLORS.primary, bold: true, align: 'center',
671
+ fontFace: FONTS.heading,
672
+ });
673
+ const features = (tier.features || []).slice(0, 3);
674
+ let fy = 3.85;
675
+ features.forEach((feature) => {
676
+ slide.addText(feature, {
677
+ x: x + 0.15, y: fy, w: cardWidth - 0.3, h: 0.3,
678
+ fontSize: 12, color: isHighlight ? COLORS.white : COLORS.secondary, align: 'center',
679
+ fontFace: FONTS.body,
680
+ });
681
+ fy += 0.35;
682
+ });
683
+ if (tier.cta) {
684
+ slide.addText(tier.cta, {
685
+ x: x + 0.3, y: 4.8, w: cardWidth - 0.6, h: 0.3,
686
+ fontSize: 12, color: isHighlight ? COLORS.accent : COLORS.white,
687
+ fill: isHighlight ? { color: COLORS.white } : { color: COLORS.accent },
688
+ align: 'center', valign: 'middle',
689
+ fontFace: FONTS.body,
690
+ });
691
+ }
692
+ });
693
+ }
694
+ function renderGalleryV1(slide, props) {
695
+ addKicker(slide, props.kicker);
696
+ addTitle(slide, props.title);
697
+ const images = (props.images || []).slice(0, 4);
698
+ const positions = [
699
+ { x: 0.8, y: 2.5 },
700
+ { x: 5.2, y: 2.5 },
701
+ { x: 0.8, y: 4.0 },
702
+ { x: 5.2, y: 4.0 },
703
+ ];
704
+ images.forEach((image, index) => {
705
+ const pos = positions[index];
706
+ if (!pos)
707
+ return;
708
+ if (image.url)
709
+ addImageMaybe(slide, image.url, pos.x, pos.y, 4.0, 1.3);
710
+ if (image.caption) {
711
+ slide.addText(image.caption, {
712
+ x: pos.x, y: pos.y + 1.35, w: 4.0, h: 0.25,
713
+ fontSize: 12, color: COLORS.secondary, align: 'center',
714
+ fontFace: FONTS.body,
715
+ });
716
+ }
717
+ });
718
+ }
719
+ function renderImageV1(slide, props) {
720
+ if (props.imageUrl) {
721
+ addImageMaybe(slide, props.imageUrl, 0, 0, 10, 5.625);
722
+ slide.addShape('rect', {
723
+ x: 0, y: 0, w: 10, h: 5.625,
724
+ fill: { color: '000000', transparency: 40 },
725
+ });
726
+ }
727
+ slide.addText(props.title, {
728
+ x: 1, y: 2.4, w: 8, h: 1.0,
729
+ fontSize: 48, color: COLORS.white, bold: true, align: 'center',
730
+ fontFace: FONTS.heading,
731
+ });
732
+ if (props.subtitle) {
733
+ slide.addText(props.subtitle, {
734
+ x: 1.5, y: 3.5, w: 7, h: 0.7,
735
+ fontSize: 22, color: COLORS.white, align: 'center',
736
+ fontFace: FONTS.body,
737
+ });
738
+ }
739
+ }
740
+ function renderSwotV1(slide, props) {
741
+ addKicker(slide, props.kicker);
742
+ addTitle(slide, props.title);
743
+ render2x2Grid(slide, [
744
+ { title: '优势 Strengths', text: props.strength || '' },
745
+ { title: '劣势 Weaknesses', text: props.weakness || '' },
746
+ { title: '机会 Opportunities', text: props.opportunity || '' },
747
+ { title: '威胁 Threats', text: props.threat || '' },
748
+ ]);
749
+ }
750
+ function renderPestV1(slide, props) {
751
+ addKicker(slide, props.kicker);
752
+ addTitle(slide, props.title);
753
+ render2x2Grid(slide, [
754
+ { title: '政治 Political', text: props.political || '' },
755
+ { title: '经济 Economic', text: props.economic || '' },
756
+ { title: '社会 Social', text: props.social || '' },
757
+ { title: '技术 Technological', text: props.technological || '' },
758
+ ]);
759
+ }
760
+ function render2x2Grid(slide, cells) {
761
+ const positions = [
762
+ { x: 0.8, y: 2.5 },
763
+ { x: 5.2, y: 2.5 },
764
+ { x: 0.8, y: 4.1 },
765
+ { x: 5.2, y: 4.1 },
766
+ ];
767
+ cells.forEach((cell, index) => {
768
+ const pos = positions[index];
769
+ if (!pos)
770
+ return;
771
+ slide.addShape('rect', {
772
+ x: pos.x, y: pos.y, w: 4.0, h: 1.4,
773
+ fill: { color: COLORS.light },
774
+ line: { color: COLORS.border, width: 1 },
775
+ });
776
+ slide.addText(cell.title, {
777
+ x: pos.x + 0.15, y: pos.y + 0.1, w: 3.7, h: 0.35,
778
+ fontSize: 16, color: COLORS.accent, bold: true, align: 'left',
779
+ fontFace: FONTS.heading,
780
+ });
781
+ slide.addText(cell.text, {
782
+ x: pos.x + 0.15, y: pos.y + 0.5, w: 3.7, h: 0.8,
783
+ fontSize: 13, color: COLORS.primary, align: 'left', valign: 'top',
784
+ fontFace: FONTS.body,
785
+ });
786
+ });
787
+ }
788
+ function renderClosingV1(slide, props) {
789
+ if (props.kicker) {
790
+ slide.addText(props.kicker, {
791
+ x: 1, y: 2.0, w: 8, h: 0.4,
792
+ fontSize: 14, color: COLORS.accent, align: 'center',
793
+ fontFace: FONTS.mono,
794
+ });
795
+ }
796
+ slide.addText(props.title, {
797
+ x: 1, y: 2.5, w: 8, h: 1.0,
798
+ fontSize: 48, color: COLORS.primary, bold: true, align: 'center',
799
+ fontFace: FONTS.heading,
800
+ });
801
+ if (props.subtitle) {
802
+ slide.addText(props.subtitle, {
803
+ x: 1.5, y: 3.6, w: 7, h: 0.7,
804
+ fontSize: 22, color: COLORS.secondary, align: 'center',
805
+ fontFace: FONTS.body,
806
+ });
807
+ }
808
+ if (props.cta) {
809
+ slide.addShape('roundRect', {
810
+ x: 3.5, y: 4.6, w: 3, h: 0.6,
811
+ fill: { color: COLORS.accent },
812
+ rectRadius: 0.3,
813
+ });
814
+ slide.addText(props.cta, {
815
+ x: 3.5, y: 4.6, w: 3, h: 0.6,
816
+ fontSize: 16, color: COLORS.white, bold: true, align: 'center', valign: 'middle',
817
+ fontFace: FONTS.body,
818
+ });
819
+ }
820
+ }
821
+ function renderClosingV2(slide, props) {
822
+ slide.addText(props.title, {
823
+ x: 1, y: 2.2, w: 8, h: 1.0,
824
+ fontSize: 48, color: COLORS.primary, bold: true, align: 'center',
825
+ fontFace: FONTS.heading,
826
+ });
827
+ if (props.subtitle) {
828
+ slide.addText(props.subtitle, {
829
+ x: 1.5, y: 3.3, w: 7, h: 0.6,
830
+ fontSize: 22, color: COLORS.secondary, align: 'center',
831
+ fontFace: FONTS.body,
832
+ });
833
+ }
834
+ const lines = [props.contact, props.email, props.link].filter(Boolean);
835
+ let y = 4.2;
836
+ lines.forEach((line) => {
837
+ slide.addText(line, {
838
+ x: 1, y, w: 8, h: 0.35,
839
+ fontSize: 16, color: COLORS.accent, align: 'center',
840
+ fontFace: FONTS.mono,
841
+ });
842
+ y += 0.45;
843
+ });
844
+ }
845
+ //# sourceMappingURL=export-pptx.js.map