canva-pptx 1.0.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.
Files changed (87) hide show
  1. package/README.md +1147 -0
  2. package/package.json +63 -0
  3. package/src/AdvancedComponents/accordionList.js +39 -0
  4. package/src/AdvancedComponents/calendarGrid.js +50 -0
  5. package/src/AdvancedComponents/certificateFrame.js +35 -0
  6. package/src/AdvancedComponents/deviceMockup.js +59 -0
  7. package/src/AdvancedComponents/featureGrid.js +70 -0
  8. package/src/AdvancedComponents/funnelDiagram.js +49 -0
  9. package/src/AdvancedComponents/ganttChart.js +35 -0
  10. package/src/AdvancedComponents/invoiceTable.js +32 -0
  11. package/src/AdvancedComponents/matrixGrid.js +43 -0
  12. package/src/AdvancedComponents/mindMap.js +43 -0
  13. package/src/AdvancedComponents/orgChart.js +76 -0
  14. package/src/AdvancedComponents/personaCard.js +44 -0
  15. package/src/AdvancedComponents/processCycle.js +47 -0
  16. package/src/AdvancedComponents/pyramidHierarchy.js +38 -0
  17. package/src/AdvancedComponents/saasFeatureBlock.js +38 -0
  18. package/src/AdvancedComponents/swotMatrix.js +55 -0
  19. package/src/AdvancedComponents/teamMemberProfile.js +75 -0
  20. package/src/AdvancedComponents/trafficLight.js +35 -0
  21. package/src/AdvancedComponents/vennDiagram.js +35 -0
  22. package/src/AdvancedComponents/winLossChart.js +43 -0
  23. package/src/components/StatMetric.js +43 -0
  24. package/src/components/alertBox.js +46 -0
  25. package/src/components/avatarGroup.js +56 -0
  26. package/src/components/badge.js +40 -0
  27. package/src/components/breadcrumbNav.js +31 -0
  28. package/src/components/browserWindow.js +86 -0
  29. package/src/components/brushStroke.js +23 -0
  30. package/src/components/callToAction.js +27 -0
  31. package/src/components/card.js +64 -0
  32. package/src/components/chart.js +19 -0
  33. package/src/components/codeBlock.js +40 -0
  34. package/src/components/codeDiff.js +51 -0
  35. package/src/components/comparisonTable.js +43 -0
  36. package/src/components/cornerAccent.js +32 -0
  37. package/src/components/dotPattern.js +30 -0
  38. package/src/components/geometricConfetti.js +34 -0
  39. package/src/components/gradientMesh.js +32 -0
  40. package/src/components/iconList.js +43 -0
  41. package/src/components/image.js +11 -0
  42. package/src/components/kanbanColumn.js +38 -0
  43. package/src/components/link.js +23 -0
  44. package/src/components/organicBlob.js +45 -0
  45. package/src/components/pricingColumn.js +53 -0
  46. package/src/components/progressBar.js +55 -0
  47. package/src/components/ratingStars.js +25 -0
  48. package/src/components/shape.js +12 -0
  49. package/src/components/slide.js +5 -0
  50. package/src/components/socialBar.js +59 -0
  51. package/src/components/squiggleLine.js +26 -0
  52. package/src/components/stepProcess.js +39 -0
  53. package/src/components/table.js +23 -0
  54. package/src/components/tagCloud.js +39 -0
  55. package/src/components/testimonialCard.js +54 -0
  56. package/src/components/text.js +14 -0
  57. package/src/components/theme.js +7 -0
  58. package/src/components/timeline.js +73 -0
  59. package/src/components/waveDecoration.js +35 -0
  60. package/src/core/PPTManager.js +17 -0
  61. package/src/index.js +225 -0
  62. package/src/layout/bento.js +47 -0
  63. package/src/layout/checkerboard.js +36 -0
  64. package/src/layout/filmStrip.js +29 -0
  65. package/src/layout/gallery.js +50 -0
  66. package/src/layout/grid.js +37 -0
  67. package/src/layout/hero.js +30 -0
  68. package/src/layout/magazine.js +39 -0
  69. package/src/layout/radial.js +34 -0
  70. package/src/layout/sidebar.js +26 -0
  71. package/src/layout/splitScreen.js +36 -0
  72. package/src/layout/zPattern.js +29 -0
  73. package/src/system/animation.js +36 -0
  74. package/src/system/contrastChecker.js +35 -0
  75. package/src/system/dataAdapter.js +36 -0
  76. package/src/system/layoutDebugger.js +40 -0
  77. package/src/system/markdownEngine.js +45 -0
  78. package/src/system/masterOverlay.js +50 -0
  79. package/src/system/sectionDivider.js +41 -0
  80. package/src/system/smartIcon.js +33 -0
  81. package/src/system/smartText.js +44 -0
  82. package/src/system/speakerNotes.js +12 -0
  83. package/src/system/syntaxHighlighter.js +71 -0
  84. package/src/system/themeGenerator.js +25 -0
  85. package/src/system/tocGenerator.js +68 -0
  86. package/src/system/watermark.js +26 -0
  87. package/src/themes/index.js +93 -0
@@ -0,0 +1,38 @@
1
+ function addPyramidHierarchy(slide, levels = [], options = {}) {
2
+ const {
3
+ x = 3, y = 1, w = 4, h = 4,
4
+ colors = ["FFCDD2", "EF9A9A", "E57373", "EF5350", "F44336"] // Reds
5
+ } = options;
6
+
7
+ const count = levels.length;
8
+ const layerH = h / count;
9
+
10
+ // Draw from bottom to top so the widest is at the bottom?
11
+ // Standard Pyramids usually have Level 1 at top or bottom depending on context.
12
+ // We'll render Top-Down (Index 0 at top).
13
+
14
+ levels.forEach((level, i) => {
15
+ const curY = y + i * layerH;
16
+
17
+ // Width increases as we go down
18
+ const factor = (i + 1) / count;
19
+ const curW = w * factor;
20
+ const curX = x + (w - curW) / 2;
21
+
22
+ slide.addShape("rect", {
23
+ x: curX,
24
+ y: curY,
25
+ w: curW,
26
+ h: layerH,
27
+ fill: colors[i % colors.length],
28
+ line: { color: "FFFFFF", width: 1 } // Separator
29
+ });
30
+
31
+ slide.addText(level, {
32
+ x: curX, y: curY, w: curW, h: layerH,
33
+ align: "center", valign: "middle", fontSize: 10, bold: true, color: "333333"
34
+ });
35
+ });
36
+ }
37
+
38
+ module.exports = { addPyramidHierarchy };
@@ -0,0 +1,38 @@
1
+ function addSaaSFeatureBlock(slide, title, desc, imagePath, options = {}) {
2
+ const {
3
+ x = 1, y = 1, w = 8, h = 3,
4
+ imagePosition = "left" // or 'right'
5
+ } = options;
6
+
7
+ const halfW = w / 2;
8
+ const imgX = imagePosition === "left" ? x : x + halfW;
9
+ const txtX = imagePosition === "left" ? x + halfW : x;
10
+
11
+ // Image Placeholder
12
+ if (imagePath) {
13
+ slide.addImage({
14
+ path: imagePath,
15
+ x: imgX + 0.2, y: y + 0.2, w: halfW - 0.4, h: h - 0.4,
16
+ sizing: { type: "contain" }
17
+ });
18
+ } else {
19
+ slide.addShape("rect", {
20
+ x: imgX + 0.2, y: y + 0.2, w: halfW - 0.4, h: h - 0.4,
21
+ fill: "EEEEEE"
22
+ });
23
+ slide.addText("Image", { x: imgX, y: y, w: halfW, h, align: "center", valign: "middle" });
24
+ }
25
+
26
+ // Text Content
27
+ slide.addText(title, {
28
+ x: txtX + 0.2, y: y + 0.5, w: halfW - 0.4, h: 0.5,
29
+ bold: true, fontSize: 18, color: "333333"
30
+ });
31
+
32
+ slide.addText(desc, {
33
+ x: txtX + 0.2, y: y + 1.2, w: halfW - 0.4, h: h - 1.2,
34
+ fontSize: 12, color: "666666", valign: "top"
35
+ });
36
+ }
37
+
38
+ module.exports = { addSaaSFeatureBlock };
@@ -0,0 +1,55 @@
1
+ function addSWOTMatrix(slide, data, options = {}) {
2
+ const {
3
+ x = 1, y = 1, w = 8, h = 5,
4
+ gap = 0.1
5
+ } = options;
6
+
7
+ const halfW = (w - gap) / 2;
8
+ const halfH = (h - gap) / 2;
9
+
10
+ // Definitions for quadrants
11
+ const quads = [
12
+ { type: 'Strengths', ox: 0, oy: 0, color: "E8F5E9", header: "S" },
13
+ { type: 'Weaknesses', ox: halfW+gap, oy: 0, color: "FFEBEE", header: "W" },
14
+ { type: 'Opportunities', ox: 0, oy: halfH+gap, color: "E3F2FD", header: "O" },
15
+ { type: 'Threats', ox: halfW+gap, oy: halfH+gap, color: "FFF3E0", header: "T" }
16
+ ];
17
+
18
+ quads.forEach(q => {
19
+ const qX = x + q.ox;
20
+ const qY = y + q.oy;
21
+ const items = data[q.type.toLowerCase()] || [];
22
+
23
+ // Background
24
+ slide.addShape("rect", {
25
+ x: qX, y: qY, w: halfW, h: halfH,
26
+ fill: q.color
27
+ });
28
+
29
+ // Header Letter (Background Circle)
30
+ slide.addShape("oval", {
31
+ x: qX + 0.2, y: qY + 0.2, w: 0.5, h: 0.5, fill: "FFFFFF"
32
+ });
33
+ // Header Letter (Text)
34
+ slide.addText(q.header, {
35
+ x: qX + 0.2, y: qY + 0.2, w: 0.5, h: 0.5,
36
+ bold: true, align: "center", valign: "middle", fontSize: 14
37
+ });
38
+
39
+ // Title
40
+ slide.addText(q.type, {
41
+ x: qX + 0.8, y: qY + 0.2, w: halfW - 1, h: 0.5,
42
+ bold: true, fontSize: 14, valign: "middle"
43
+ });
44
+
45
+ // List
46
+ if (items.length > 0) {
47
+ slide.addText(items.map(i => `• ${i}`).join("\n"), {
48
+ x: qX + 0.3, y: qY + 0.9, w: halfW - 0.6, h: halfH - 1,
49
+ fontSize: 11, color: "333333", valign: "top"
50
+ });
51
+ }
52
+ });
53
+ }
54
+
55
+ module.exports = { addSWOTMatrix };
@@ -0,0 +1,75 @@
1
+ function addTeamMemberProfile(slide, profile, options = {}) {
2
+ const {
3
+ x = 1, y = 1, w = 2.5, h = 4,
4
+ bgColor = "FFFFFF",
5
+ borderColor = "E0E0E0"
6
+ } = options;
7
+
8
+ // Card Container
9
+ slide.addShape("rect", {
10
+ x, y, w, h,
11
+ fill: bgColor,
12
+ line: { color: borderColor }
13
+ });
14
+
15
+ // Avatar Area
16
+ const imgSize = w * 0.5;
17
+ const imgX = x + (w - imgSize) / 2;
18
+ const imgY = y + 0.3;
19
+
20
+ if (profile.image) {
21
+ slide.addImage({
22
+ path: profile.image,
23
+ x: imgX,
24
+ y: imgY,
25
+ w: imgSize,
26
+ h: imgSize,
27
+ sizing: { type: "cover", w: imgSize, h: imgSize },
28
+ rounding: true
29
+ });
30
+ } else {
31
+ slide.addShape("oval", {
32
+ x: imgX, y: imgY, w: imgSize, h: imgSize, fill: "CCCCCC"
33
+ });
34
+ }
35
+
36
+ // Name
37
+ slide.addText(profile.name, {
38
+ x: x,
39
+ y: imgY + imgSize + 0.2,
40
+ w: w,
41
+ h: 0.4,
42
+ bold: true,
43
+ fontSize: 14,
44
+ align: "center",
45
+ color: "333333"
46
+ });
47
+
48
+ // Role
49
+ slide.addText(profile.role, {
50
+ x: x,
51
+ y: imgY + imgSize + 0.6,
52
+ w: w,
53
+ h: 0.3,
54
+ fontSize: 11,
55
+ italic: true,
56
+ align: "center",
57
+ color: "007AFF"
58
+ });
59
+
60
+ // Bio
61
+ if (profile.bio) {
62
+ slide.addText(profile.bio, {
63
+ x: x + 0.2,
64
+ y: imgY + imgSize + 1.0,
65
+ w: w - 0.4,
66
+ h: h - (imgSize + 1.2),
67
+ fontSize: 10,
68
+ align: "center",
69
+ color: "666666",
70
+ valign: "top"
71
+ });
72
+ }
73
+ }
74
+
75
+ module.exports = { addTeamMemberProfile };
@@ -0,0 +1,35 @@
1
+ function addTrafficLight(slide, status = "green", options = {}) {
2
+ const { x = 1, y = 1, w = 1, h = 3 } = options;
3
+
4
+ // Housing
5
+ slide.addShape("roundRect", {
6
+ x, y, w, h,
7
+ fill: "333333",
8
+ rounding: 0.2
9
+ });
10
+
11
+ const lights = [
12
+ { color: "FF0000", id: "red" },
13
+ { color: "FFA500", id: "amber" },
14
+ { color: "00FF00", id: "green" }
15
+ ];
16
+
17
+ const lightSize = w * 0.6;
18
+ const gap = (h - (lights.length * lightSize)) / 4;
19
+
20
+ lights.forEach((light, i) => {
21
+ const isActive = light.id === status;
22
+ const curY = y + gap + i * (lightSize + gap);
23
+
24
+ slide.addShape("oval", {
25
+ x: x + (w - lightSize) / 2,
26
+ y: curY,
27
+ w: lightSize,
28
+ h: lightSize,
29
+ fill: isActive ? light.color : "#555555", // Bright or Dim
30
+ line: { color: "000000", width: 1 }
31
+ });
32
+ });
33
+ }
34
+
35
+ module.exports = { addTrafficLight };
@@ -0,0 +1,35 @@
1
+ function addVennDiagram(slide, labels = ["A", "B"], options = {}) {
2
+ const {
3
+ x = 2, y = 2, w = 6, h = 3,
4
+ colors = ["FF0000", "0000FF", "00FF00"],
5
+ opacity = 50
6
+ } = options;
7
+
8
+ const circleSize = 2.5;
9
+ const overlap = 0.8;
10
+
11
+ labels.forEach((label, i) => {
12
+ // Arrange horizontally with overlap
13
+ const curX = x + i * (circleSize - overlap);
14
+
15
+ // For 3rd item, maybe center it below?
16
+ // This is a simple linear overlap logic for now.
17
+ const curY = y;
18
+
19
+ slide.addShape("oval", {
20
+ x: curX,
21
+ y: curY,
22
+ w: circleSize,
23
+ h: circleSize,
24
+ fill: { color: colors[i % colors.length], transparency: 100 - opacity },
25
+ line: { color: "FFFFFF", width: 1 }
26
+ });
27
+
28
+ slide.addText(label, {
29
+ x: curX, y: curY + circleSize/2 - 0.2, w: circleSize, h: 0.4,
30
+ align: "center", bold: true, fontSize: 14
31
+ });
32
+ });
33
+ }
34
+
35
+ module.exports = { addVennDiagram };
@@ -0,0 +1,43 @@
1
+ function addWinLossChart(slide, data = [], options = {}) {
2
+ const { x = 1, y = 1, w = 6, h = 3 } = options;
3
+
4
+ // data = [{ label: "Q1", value: 10 }, { label: "Q2", value: -5 }]
5
+
6
+ const maxVal = Math.max(...data.map(d => Math.abs(d.value)));
7
+ const zeroY = y + h / 2; // Center line
8
+
9
+ const barW = (w / data.length) * 0.6;
10
+ const gap = (w / data.length) * 0.4;
11
+
12
+ // Zero Line
13
+ slide.addShape("line", { x, y: zeroY, w, h: 0, line: { color: "333333" } });
14
+
15
+ data.forEach((d, i) => {
16
+ const barH = (Math.abs(d.value) / maxVal) * (h / 2 - 0.2);
17
+ const curX = x + (gap / 2) + i * (barW + gap);
18
+
19
+ let curY;
20
+ let color;
21
+
22
+ if (d.value >= 0) {
23
+ curY = zeroY - barH;
24
+ color = "4CAF50"; // Green
25
+ } else {
26
+ curY = zeroY;
27
+ color = "F44336"; // Red
28
+ }
29
+
30
+ slide.addShape("rect", {
31
+ x: curX, y: curY, w: barW, h: barH,
32
+ fill: color
33
+ });
34
+
35
+ // Label
36
+ slide.addText(d.label, {
37
+ x: curX, y: zeroY + (d.value >= 0 ? 0.1 : -0.3), w: barW, h: 0.3,
38
+ fontSize: 9, align: "center", color: "FFFFFF" // Text inside? or outside
39
+ });
40
+ });
41
+ }
42
+
43
+ module.exports = { addWinLossChart };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Renders a large statistic with a label
3
+ */
4
+ function addStatMetric(slide, options = {}) {
5
+ const {
6
+ x = 1,
7
+ y = 1,
8
+ w = 3,
9
+ h = 2,
10
+ value = "0",
11
+ label = "Metric",
12
+ valueColor = "000000",
13
+ labelColor = "666666",
14
+ align = "center",
15
+ } = options;
16
+
17
+ // The Big Number
18
+ slide.addText(value, {
19
+ x: x,
20
+ y: y,
21
+ w: w,
22
+ h: h * 0.6, // Takes up top 60%
23
+ fontSize: 48,
24
+ bold: true,
25
+ color: valueColor,
26
+ align: align,
27
+ valign: "bottom", // Align to bottom of its box so it sits near label
28
+ });
29
+
30
+ // The Label
31
+ slide.addText(label, {
32
+ x: x,
33
+ y: y + h * 0.6,
34
+ w: w,
35
+ h: h * 0.4, // Takes up bottom 40%
36
+ fontSize: 14,
37
+ color: labelColor,
38
+ align: align,
39
+ valign: "top",
40
+ });
41
+ }
42
+
43
+ module.exports = { addStatMetric };
@@ -0,0 +1,46 @@
1
+ function addAlertBox(slide, text, type = "info", options = {}) {
2
+ const { x = 1, y = 1, w = 8, h = 0.8 } = options;
3
+
4
+ let bg, border, icon;
5
+ switch (type) {
6
+ case "success": bg = "E8F5E9"; border = "4CAF50"; icon = "✓"; break;
7
+ case "warning": bg = "FFF3E0"; border = "FF9800"; icon = "!"; break;
8
+ case "error": bg = "FFEBEE"; border = "F44336"; icon = "✕"; break;
9
+ default: bg = "E3F2FD"; border = "2196F3"; icon = "i"; break;
10
+ }
11
+
12
+ // Box
13
+ slide.addShape("rect", {
14
+ x, y, w, h,
15
+ fill: bg,
16
+ line: { color: border, width: 2 }
17
+ });
18
+
19
+ // Icon area
20
+ slide.addShape("rect", {
21
+ x, y, w: 0.6, h,
22
+ fill: border // Darker side strip
23
+ });
24
+
25
+ slide.addText(icon, {
26
+ x, y, w: 0.6, h,
27
+ color: "FFFFFF",
28
+ align: "center",
29
+ valign: "middle",
30
+ bold: true,
31
+ fontSize: 18
32
+ });
33
+
34
+ // Message
35
+ slide.addText(text, {
36
+ x: x + 0.7,
37
+ y,
38
+ w: w - 0.8,
39
+ h,
40
+ color: "333333",
41
+ valign: "middle",
42
+ fontSize: 12
43
+ });
44
+ }
45
+
46
+ module.exports = { addAlertBox };
@@ -0,0 +1,56 @@
1
+ function addAvatarGroup(slide, avatars = [], options = {}) {
2
+ const {
3
+ x = 1,
4
+ y = 1,
5
+ radius = 0.5,
6
+ offset = 0.3, // overlap amount
7
+ borderColor = "FFFFFF",
8
+ } = options;
9
+
10
+ avatars.forEach((avatar, index) => {
11
+ const curX = x + index * (radius - offset);
12
+
13
+ // 1. Circle Border (for the overlap effect)
14
+ slide.addShape("oval", {
15
+ x: curX - 0.02,
16
+ y: y - 0.02,
17
+ w: radius + 0.04,
18
+ h: radius + 0.04,
19
+ fill: borderColor,
20
+ });
21
+
22
+ // 2. Avatar Fill (Image or Initials)
23
+ if (avatar.image) {
24
+ slide.addImage({
25
+ path: avatar.image,
26
+ x: curX,
27
+ y: y,
28
+ w: radius,
29
+ h: radius,
30
+ sizing: { type: "cover", w: radius, h: radius },
31
+ rounding: true, // makes image circular in some viewers
32
+ });
33
+ } else {
34
+ // Fallback: Colored circle with initials
35
+ slide.addShape("oval", {
36
+ x: curX,
37
+ y: y,
38
+ w: radius,
39
+ h: radius,
40
+ fill: avatar.color || "CCCCCC",
41
+ });
42
+ slide.addText(avatar.initials || "U", {
43
+ x: curX,
44
+ y: y,
45
+ w: radius,
46
+ h: radius,
47
+ align: "center",
48
+ valign: "middle",
49
+ fontSize: 10,
50
+ color: "FFFFFF",
51
+ });
52
+ }
53
+ });
54
+ }
55
+
56
+ module.exports = { addAvatarGroup };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Renders a text badge (pill shape)
3
+ */
4
+ function addBadge(slide, text, options = {}) {
5
+ const {
6
+ x = 1,
7
+ y = 1,
8
+ w = 1.5,
9
+ h = 0.4,
10
+ backgroundColor = "E1F5FE", // Light Blue
11
+ color = "0277BD", // Dark Blue
12
+ fontSize = 10,
13
+ } = options;
14
+
15
+ // Background Pill
16
+ slide.addShape("roundRect", {
17
+ x,
18
+ y,
19
+ w,
20
+ h,
21
+ fill: backgroundColor,
22
+ line: { color: backgroundColor }, // Seamless look
23
+ rounding: 0.5, // Fully rounded
24
+ });
25
+
26
+ // Text
27
+ slide.addText(text, {
28
+ x,
29
+ y,
30
+ w,
31
+ h,
32
+ fontSize,
33
+ color,
34
+ align: "center",
35
+ valign: "middle",
36
+ bold: true,
37
+ });
38
+ }
39
+
40
+ module.exports = { addBadge };
@@ -0,0 +1,31 @@
1
+ function addBreadcrumbNav(slide, path = [], options = {}) {
2
+ const { x = 0.5, y = 0.3, fontSize = 10, color = "888888" } = options;
3
+
4
+ let currentX = x;
5
+
6
+ path.forEach((item, index) => {
7
+ const isLast = index === path.length - 1;
8
+
9
+ // Calculate approximate width (rough estimate: char count * 0.08)
10
+ const textW = item.length * 0.09;
11
+
12
+ slide.addText(item, {
13
+ x: currentX, y, w: textW, h: 0.3,
14
+ fontSize,
15
+ color: isLast ? "000000" : color,
16
+ bold: isLast
17
+ });
18
+
19
+ currentX += textW;
20
+
21
+ if (!isLast) {
22
+ slide.addText(">", {
23
+ x: currentX, y, w: 0.2, h: 0.3,
24
+ fontSize, color, align: "center"
25
+ });
26
+ currentX += 0.2;
27
+ }
28
+ });
29
+ }
30
+
31
+ module.exports = { addBreadcrumbNav };
@@ -0,0 +1,86 @@
1
+ function addBrowserWindow(slide, options = {}) {
2
+ const {
3
+ x = 1,
4
+ y = 1,
5
+ w = 6,
6
+ h = 3.5,
7
+ url = "https://example.com",
8
+ imagePath = null, // Optional screenshot to fill the body
9
+ barColor = "E0E0E0",
10
+ borderColor = "D0D0D0"
11
+ } = options;
12
+
13
+ const headerHeight = 0.4;
14
+ const btnSize = 0.12;
15
+ const btnGap = 0.2;
16
+
17
+ // 1. Main Frame (Border)
18
+ slide.addShape("roundRect", {
19
+ x, y, w, h,
20
+ fill: "FFFFFF",
21
+ line: { color: borderColor },
22
+ rounding: 0.1
23
+ });
24
+
25
+ // 2. Top Bar Background
26
+ // We mask the top area. For a perfect "top-rounded" rect, we usually just draw over the main rect.
27
+ // PptxGenJS doesn't easily support "top-only" rounding, so we overlay a rect.
28
+ slide.addShape("rect", {
29
+ x, y, w, h: headerHeight,
30
+ fill: barColor,
31
+ rectRadius: 0.1 // Try to match rounding if library supports, or just Rect
32
+ });
33
+
34
+ // 3. Traffic Light Buttons (Red, Yellow, Green)
35
+ const colors = ["FF5F56", "FFBD2E", "27C93F"];
36
+ colors.forEach((c, i) => {
37
+ slide.addShape("oval", {
38
+ x: x + 0.15 + i * (btnSize + 0.08),
39
+ y: y + (headerHeight - btnSize) / 2,
40
+ w: btnSize,
41
+ h: btnSize,
42
+ fill: c,
43
+ line: { color: undefined }
44
+ });
45
+ });
46
+
47
+ // 4. Address Bar (White pill)
48
+ const addrX = x + 0.8;
49
+ const addrW = w - 1;
50
+ const addrH = headerHeight * 0.6;
51
+ slide.addShape("roundRect", {
52
+ x: addrX,
53
+ y: y + (headerHeight - addrH) / 2,
54
+ w: addrW,
55
+ h: addrH,
56
+ fill: "FFFFFF",
57
+ line: { color: "CCCCCC", width: 1 },
58
+ rounding: 0.5
59
+ });
60
+
61
+ // 5. URL Text
62
+ slide.addText(url, {
63
+ x: addrX + 0.1,
64
+ y: y + (headerHeight - addrH) / 2,
65
+ w: addrW - 0.2,
66
+ h: addrH,
67
+ fontSize: 8,
68
+ color: "666666",
69
+ valign: "middle"
70
+ });
71
+
72
+ // 6. Content / Screenshot
73
+ if (imagePath) {
74
+ // Render image inside the frame, below the header
75
+ slide.addImage({
76
+ path: imagePath,
77
+ x: x + 0.05,
78
+ y: y + headerHeight + 0.05,
79
+ w: w - 0.1,
80
+ h: h - headerHeight - 0.1,
81
+ sizing: { type: "contain", w: w - 0.1, h: h - headerHeight - 0.1 }
82
+ });
83
+ }
84
+ }
85
+
86
+ module.exports = { addBrowserWindow };
@@ -0,0 +1,23 @@
1
+ function addBrushStroke(slide, options = {}) {
2
+ const {
3
+ x = 1,
4
+ y = 1,
5
+ w = 4,
6
+ h = 0.5,
7
+ color = "FFEB3B", // Highlighter Yellow
8
+ opacity = 50 // Semi-transparent
9
+ } = options;
10
+
11
+ // We use a rounded rect with full rounding to look like a marker stroke
12
+ slide.addShape("rect", {
13
+ x: x,
14
+ y: y,
15
+ w: w,
16
+ h: h,
17
+ fill: { color: color, transparency: 100 - opacity },
18
+ line: { color: undefined },
19
+ rounding: 1 // Max rounding
20
+ });
21
+ }
22
+
23
+ module.exports = { addBrushStroke };
@@ -0,0 +1,27 @@
1
+ function addCallToAction(slide, text, link, options = {}) {
2
+ const {
3
+ x = 4, y = 3, w = 3, h = 0.6,
4
+ color = "007AFF"
5
+ } = options;
6
+
7
+ // Button Shape
8
+ slide.addShape("roundRect", {
9
+ x, y, w, h,
10
+ fill: color,
11
+ line: { color: color },
12
+ rounding: 0.5
13
+ });
14
+
15
+ // Text with Link
16
+ slide.addText(text, {
17
+ x, y, w, h,
18
+ color: "FFFFFF",
19
+ bold: true,
20
+ fontSize: 14,
21
+ align: "center",
22
+ valign: "middle",
23
+ hyperlink: { url: link }
24
+ });
25
+ }
26
+
27
+ module.exports = { addCallToAction };