@heylemon/lemonade 0.1.6 → 0.1.7
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.
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/package.json +1 -1
- package/skills/docx/SKILL.md +595 -22
- package/skills/docx/references/templates.md +669 -33
- package/skills/docx/scripts/create_doc.py +289 -52
- package/skills/docx/scripts/validate.py +237 -0
- package/skills/docx/scripts/validate_doc.py +103 -22
- package/skills/pptx/SKILL.md +169 -12
- package/skills/pptx/editing.md +270 -0
- package/skills/pptx/pptxgenjs.md +624 -0
- package/skills/pptx/references/spec-format.md +106 -31
- package/skills/pptx/scripts/create_pptx.js +419 -186
- package/skills/xlsx/SKILL.md +502 -14
- package/skills/xlsx/references/spec-format.md +238 -40
- package/skills/xlsx/scripts/create_xlsx.py +130 -54
- package/skills/xlsx/scripts/recalc.py +157 -147
- package/skills/xlsx/scripts/validate_xlsx.py +31 -6
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Validate a .docx file for quality, consistency, and standards compliance.
|
|
4
|
+
|
|
5
|
+
Checks for:
|
|
6
|
+
- XML well-formedness and schema validity
|
|
7
|
+
- Font consistency (warns if > 3 fonts)
|
|
8
|
+
- Paragraph length and structure
|
|
9
|
+
- Empty headings
|
|
10
|
+
- Manual bullet characters (should use List APIs)
|
|
11
|
+
- Table consistency
|
|
12
|
+
- Page setup
|
|
13
|
+
|
|
14
|
+
Usage: python validate.py document.docx
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import sys
|
|
18
|
+
import os
|
|
19
|
+
import zipfile
|
|
20
|
+
import xml.etree.ElementTree as ET
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from docx import Document
|
|
25
|
+
from docx.shared import Pt
|
|
26
|
+
except ImportError:
|
|
27
|
+
print("ERROR: python-docx not installed. Run: pip install python-docx --break-system-packages")
|
|
28
|
+
sys.exit(1)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def validate_xml_wellformedness(docx_path):
|
|
32
|
+
"""Check that the document XML is well-formed."""
|
|
33
|
+
issues = []
|
|
34
|
+
try:
|
|
35
|
+
with zipfile.ZipFile(docx_path, 'r') as zip_ref:
|
|
36
|
+
try:
|
|
37
|
+
xml_content = zip_ref.read('word/document.xml')
|
|
38
|
+
ET.fromstring(xml_content)
|
|
39
|
+
except ET.ParseError as e:
|
|
40
|
+
issues.append(f"XML Parse Error: {str(e)}")
|
|
41
|
+
except zipfile.BadZipFile:
|
|
42
|
+
issues.append("File is not a valid ZIP archive (docx must be ZIP)")
|
|
43
|
+
except Exception as e:
|
|
44
|
+
issues.append(f"Could not read document.xml: {str(e)}")
|
|
45
|
+
return issues
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def validate_document(docx_path):
|
|
49
|
+
"""Validate document structure, content, and formatting."""
|
|
50
|
+
issues = []
|
|
51
|
+
warnings = []
|
|
52
|
+
info = []
|
|
53
|
+
|
|
54
|
+
if not os.path.exists(docx_path):
|
|
55
|
+
print(f"ERROR: File not found: {docx_path}")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
# Check XML well-formedness first
|
|
59
|
+
xml_issues = validate_xml_wellformedness(docx_path)
|
|
60
|
+
if xml_issues:
|
|
61
|
+
issues.extend(xml_issues)
|
|
62
|
+
return issues, warnings, info
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
doc = Document(docx_path)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
issues.append(f"Could not open document: {str(e)}")
|
|
68
|
+
return issues, warnings, info
|
|
69
|
+
|
|
70
|
+
# ─── Page setup checks ───
|
|
71
|
+
section = doc.sections[0] if doc.sections else None
|
|
72
|
+
if section:
|
|
73
|
+
from docx.shared import Inches
|
|
74
|
+
if section.page_width and section.page_width < Inches(7):
|
|
75
|
+
warnings.append("Page width is unusually narrow (< 7 inches)")
|
|
76
|
+
if section.left_margin and section.left_margin < Inches(0.5):
|
|
77
|
+
warnings.append("Left margin is very small (< 0.5 inches) — may look cramped")
|
|
78
|
+
if section.right_margin and section.right_margin < Inches(0.5):
|
|
79
|
+
warnings.append("Right margin is very small (< 0.5 inches) — may look cramped")
|
|
80
|
+
|
|
81
|
+
# ─── Paragraph checks ───
|
|
82
|
+
paragraph_count = 0
|
|
83
|
+
heading_count = 0
|
|
84
|
+
empty_paragraphs = 0
|
|
85
|
+
consecutive_empty = 0
|
|
86
|
+
max_consecutive_empty = 0
|
|
87
|
+
fonts_used = set()
|
|
88
|
+
sizes_used = set()
|
|
89
|
+
manual_bullets = []
|
|
90
|
+
|
|
91
|
+
# Unicode bullet characters
|
|
92
|
+
bullet_chars = {'•', '◦', '▪', '●', '○', '■', '–', '—', '-', '*'}
|
|
93
|
+
|
|
94
|
+
for p in doc.paragraphs:
|
|
95
|
+
paragraph_count += 1
|
|
96
|
+
text = p.text.strip()
|
|
97
|
+
|
|
98
|
+
# Track empty paragraphs
|
|
99
|
+
if not text:
|
|
100
|
+
empty_paragraphs += 1
|
|
101
|
+
consecutive_empty += 1
|
|
102
|
+
max_consecutive_empty = max(max_consecutive_empty, consecutive_empty)
|
|
103
|
+
else:
|
|
104
|
+
consecutive_empty = 0
|
|
105
|
+
|
|
106
|
+
# Check headings
|
|
107
|
+
if p.style.name.startswith("Heading"):
|
|
108
|
+
heading_count += 1
|
|
109
|
+
# Heading shouldn't end with a period
|
|
110
|
+
if text.endswith("."):
|
|
111
|
+
warnings.append(f"Heading ends with period: \"{text[:50]}\"")
|
|
112
|
+
# Heading shouldn't be all caps (unless very short)
|
|
113
|
+
if text == text.upper() and len(text) > 3:
|
|
114
|
+
if "1" not in p.style.name:
|
|
115
|
+
warnings.append(f"Heading is ALL CAPS (use Title Case): \"{text[:50]}\"")
|
|
116
|
+
# Check for empty headings
|
|
117
|
+
if not text:
|
|
118
|
+
issues.append(f"Empty heading found in style {p.style.name}")
|
|
119
|
+
|
|
120
|
+
# Detect manual bullet characters
|
|
121
|
+
if not p.style.name.startswith("List"):
|
|
122
|
+
# Check if text starts with bullet-like characters
|
|
123
|
+
for bullet_char in bullet_chars:
|
|
124
|
+
if text.startswith(bullet_char):
|
|
125
|
+
manual_bullets.append(f"Manual bullet '{bullet_char}' in: \"{text[:50]}\"")
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
# Track fonts and sizes
|
|
129
|
+
for run in p.runs:
|
|
130
|
+
if run.font.name:
|
|
131
|
+
fonts_used.add(run.font.name)
|
|
132
|
+
if run.font.size:
|
|
133
|
+
sizes_used.add(run.font.size)
|
|
134
|
+
|
|
135
|
+
# Check for very long paragraphs (over 500 chars)
|
|
136
|
+
if len(text) > 500:
|
|
137
|
+
word_count = len(text.split())
|
|
138
|
+
warnings.append(f"Very long paragraph ({len(text)} chars, {word_count} words): \"{text[:50]}...\" — consider breaking up")
|
|
139
|
+
|
|
140
|
+
# Check for excessive spacing/empty paragraphs
|
|
141
|
+
if len(text) == 0 and paragraph_count > 1:
|
|
142
|
+
# This is tracked separately
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
# Report manual bullet issues
|
|
146
|
+
if manual_bullets:
|
|
147
|
+
for bullet_issue in manual_bullets[:5]: # Show first 5
|
|
148
|
+
issues.append(f"Manual bullet character detected: {bullet_issue} — use List Bullet style instead")
|
|
149
|
+
if len(manual_bullets) > 5:
|
|
150
|
+
issues.append(f"... and {len(manual_bullets) - 5} more manual bullets")
|
|
151
|
+
|
|
152
|
+
# ─── Table checks ───
|
|
153
|
+
table_issues = 0
|
|
154
|
+
for ti, table in enumerate(doc.tables, 1):
|
|
155
|
+
if len(table.rows) == 0:
|
|
156
|
+
issues.append(f"Table {ti} has no rows")
|
|
157
|
+
table_issues += 1
|
|
158
|
+
if len(table.columns) == 0:
|
|
159
|
+
issues.append(f"Table {ti} has no columns")
|
|
160
|
+
table_issues += 1
|
|
161
|
+
# Check for empty cells in header row
|
|
162
|
+
if table.rows:
|
|
163
|
+
first_row = table.rows[0]
|
|
164
|
+
for ci, cell in enumerate(first_row.cells):
|
|
165
|
+
if not cell.text.strip():
|
|
166
|
+
warnings.append(f"Table {ti}: empty header cell in column {ci+1}")
|
|
167
|
+
|
|
168
|
+
# ─── Font consistency checks ───
|
|
169
|
+
if len(fonts_used) > 3:
|
|
170
|
+
warnings.append(f"Too many fonts ({len(fonts_used)}): {', '.join(sorted(fonts_used))} — keep to 2 fonts max (heading + body)")
|
|
171
|
+
if len(sizes_used) > 8:
|
|
172
|
+
warnings.append(f"Many different font sizes used ({len(sizes_used)}) — use a consistent typography scale")
|
|
173
|
+
|
|
174
|
+
# ─── Structure checks ───
|
|
175
|
+
if max_consecutive_empty > 2:
|
|
176
|
+
warnings.append(f"Found {max_consecutive_empty} consecutive empty paragraphs — use spacing instead")
|
|
177
|
+
|
|
178
|
+
if heading_count == 0 and paragraph_count > 15:
|
|
179
|
+
warnings.append("No headings found in a longer document — add headings for structure")
|
|
180
|
+
|
|
181
|
+
if paragraph_count < 3:
|
|
182
|
+
warnings.append("Document is very short — consider if this is complete")
|
|
183
|
+
|
|
184
|
+
# ─── Summary info ───
|
|
185
|
+
info.append(f"Paragraphs: {paragraph_count}")
|
|
186
|
+
info.append(f"Headings: {heading_count}")
|
|
187
|
+
info.append(f"Tables: {len(doc.tables)}")
|
|
188
|
+
info.append(f"Sections: {len(doc.sections)}")
|
|
189
|
+
info.append(f"Fonts: {', '.join(sorted(fonts_used)) if fonts_used else 'default only'}")
|
|
190
|
+
if empty_paragraphs > 0:
|
|
191
|
+
info.append(f"Empty paragraphs: {empty_paragraphs}")
|
|
192
|
+
|
|
193
|
+
return issues, warnings, info
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def main():
|
|
197
|
+
if len(sys.argv) < 2:
|
|
198
|
+
print("Usage: python validate.py document.docx")
|
|
199
|
+
sys.exit(1)
|
|
200
|
+
|
|
201
|
+
docx_path = sys.argv[1]
|
|
202
|
+
issues, warnings, info = validate_document(docx_path)
|
|
203
|
+
|
|
204
|
+
# ─── Report ───
|
|
205
|
+
print(f"\n{'═' * 60}")
|
|
206
|
+
print(f" Document Validation: {os.path.basename(docx_path)}")
|
|
207
|
+
print(f"{'═' * 60}\n")
|
|
208
|
+
|
|
209
|
+
for item in info:
|
|
210
|
+
print(f" ℹ {item}")
|
|
211
|
+
print()
|
|
212
|
+
|
|
213
|
+
if issues:
|
|
214
|
+
print(f" ✘ {len(issues)} ISSUE(S):")
|
|
215
|
+
for issue in issues:
|
|
216
|
+
print(f" • {issue}")
|
|
217
|
+
print()
|
|
218
|
+
|
|
219
|
+
if warnings:
|
|
220
|
+
print(f" ⚠ {len(warnings)} WARNING(S):")
|
|
221
|
+
for w in warnings:
|
|
222
|
+
print(f" • {w}")
|
|
223
|
+
print()
|
|
224
|
+
|
|
225
|
+
if not issues and not warnings:
|
|
226
|
+
print(" ✔ No issues found — document looks clean!\n")
|
|
227
|
+
return 0
|
|
228
|
+
elif not issues:
|
|
229
|
+
print(" ✔ No critical issues (warnings only)\n")
|
|
230
|
+
return 0
|
|
231
|
+
else:
|
|
232
|
+
print(f" ✘ {len(issues)} critical issue(s) need fixing\n")
|
|
233
|
+
return 1
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
if __name__ == "__main__":
|
|
237
|
+
sys.exit(main())
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
Validate a .docx file for common formatting issues.
|
|
4
|
+
Checks typography, spacing, structure, and consistency.
|
|
5
|
+
|
|
6
|
+
Usage: python validate_doc.py document.docx
|
|
4
7
|
"""
|
|
5
8
|
|
|
6
|
-
import os
|
|
7
9
|
import sys
|
|
10
|
+
import os
|
|
8
11
|
|
|
9
12
|
try:
|
|
10
13
|
from docx import Document
|
|
11
|
-
from docx.shared import Inches
|
|
14
|
+
from docx.shared import Pt, Inches
|
|
12
15
|
except ImportError:
|
|
13
16
|
print("ERROR: python-docx not installed. Run: pip install python-docx --break-system-packages")
|
|
14
17
|
sys.exit(1)
|
|
@@ -17,55 +20,133 @@ except ImportError:
|
|
|
17
20
|
def validate(path):
|
|
18
21
|
issues = []
|
|
19
22
|
warnings = []
|
|
23
|
+
info = []
|
|
20
24
|
|
|
21
25
|
if not os.path.exists(path):
|
|
22
26
|
print(f"ERROR: File not found: {path}")
|
|
23
|
-
|
|
27
|
+
sys.exit(1)
|
|
24
28
|
|
|
25
29
|
doc = Document(path)
|
|
26
30
|
|
|
31
|
+
# ── Page setup checks ──
|
|
27
32
|
section = doc.sections[0]
|
|
33
|
+
if section.page_width and section.page_width < Inches(7):
|
|
34
|
+
warnings.append("Page width is unusually narrow (< 7 inches)")
|
|
28
35
|
if section.left_margin and section.left_margin < Inches(0.5):
|
|
29
|
-
warnings.append("Left margin is very small (< 0.5 inches)")
|
|
36
|
+
warnings.append("Left margin is very small (< 0.5 inches) — may look cramped")
|
|
30
37
|
if section.right_margin and section.right_margin < Inches(0.5):
|
|
31
|
-
warnings.append("Right margin is very small (< 0.5 inches)")
|
|
38
|
+
warnings.append("Right margin is very small (< 0.5 inches) — may look cramped")
|
|
32
39
|
|
|
40
|
+
# ── Paragraph checks ──
|
|
41
|
+
paragraph_count = 0
|
|
33
42
|
heading_count = 0
|
|
43
|
+
empty_paragraphs = 0
|
|
44
|
+
consecutive_empty = 0
|
|
45
|
+
max_consecutive_empty = 0
|
|
34
46
|
fonts_used = set()
|
|
47
|
+
sizes_used = set()
|
|
35
48
|
|
|
36
49
|
for p in doc.paragraphs:
|
|
50
|
+
paragraph_count += 1
|
|
51
|
+
|
|
52
|
+
# Track empty paragraphs
|
|
53
|
+
if not p.text.strip():
|
|
54
|
+
empty_paragraphs += 1
|
|
55
|
+
consecutive_empty += 1
|
|
56
|
+
max_consecutive_empty = max(max_consecutive_empty, consecutive_empty)
|
|
57
|
+
else:
|
|
58
|
+
consecutive_empty = 0
|
|
59
|
+
|
|
60
|
+
# Check headings
|
|
37
61
|
if p.style.name.startswith("Heading"):
|
|
38
62
|
heading_count += 1
|
|
63
|
+
# Heading shouldn't end with a period
|
|
39
64
|
if p.text.strip().endswith("."):
|
|
40
|
-
warnings.append(f
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
warnings.append(f"Heading ends with period: \"{p.text.strip()[:50]}\"")
|
|
66
|
+
# Heading shouldn't be all caps (unless H1)
|
|
67
|
+
if p.text.strip() == p.text.strip().upper() and len(p.text.strip()) > 3:
|
|
68
|
+
if "1" not in p.style.name:
|
|
69
|
+
warnings.append(f"Heading is all caps (consider title case): \"{p.text.strip()[:50]}\"")
|
|
45
70
|
|
|
71
|
+
# Track fonts and sizes
|
|
46
72
|
for run in p.runs:
|
|
47
73
|
if run.font.name:
|
|
48
74
|
fonts_used.add(run.font.name)
|
|
75
|
+
if run.font.size:
|
|
76
|
+
sizes_used.add(run.font.size)
|
|
77
|
+
|
|
78
|
+
# Check for very long paragraphs (over 300 words)
|
|
79
|
+
word_count = len(p.text.split())
|
|
80
|
+
if word_count > 300:
|
|
81
|
+
warnings.append(f"Very long paragraph ({word_count} words): \"{p.text.strip()[:50]}...\" — consider breaking up")
|
|
49
82
|
|
|
83
|
+
# Check for unicode bullet characters (should use list styles instead)
|
|
84
|
+
if p.text.strip().startswith(("•", "◦", "▪", "●", "○", "■", "–", "—")):
|
|
85
|
+
if not p.style.name.startswith("List"):
|
|
86
|
+
issues.append(f"Manual bullet character detected: \"{p.text.strip()[:50]}\" — use List Bullet style instead")
|
|
87
|
+
|
|
88
|
+
# ── Table checks ──
|
|
89
|
+
for ti, table in enumerate(doc.tables):
|
|
90
|
+
if len(table.rows) == 0:
|
|
91
|
+
issues.append(f"Table {ti+1} has no rows")
|
|
92
|
+
if len(table.columns) == 0:
|
|
93
|
+
issues.append(f"Table {ti+1} has no columns")
|
|
94
|
+
# Check for empty cells in header row
|
|
95
|
+
if table.rows:
|
|
96
|
+
first_row = table.rows[0]
|
|
97
|
+
for ci, cell in enumerate(first_row.cells):
|
|
98
|
+
if not cell.text.strip():
|
|
99
|
+
warnings.append(f"Table {ti+1}: empty header cell in column {ci+1}")
|
|
100
|
+
|
|
101
|
+
# ── Consistency checks ──
|
|
50
102
|
if len(fonts_used) > 3:
|
|
51
|
-
warnings.append(f"Too many fonts used ({len(fonts_used)}): {', '.join(sorted(fonts_used))}")
|
|
52
|
-
if
|
|
53
|
-
warnings.append("
|
|
103
|
+
warnings.append(f"Too many fonts used ({len(fonts_used)}): {', '.join(sorted(fonts_used))} — keep to 2 max")
|
|
104
|
+
if len(sizes_used) > 6:
|
|
105
|
+
warnings.append(f"Many font sizes used ({len(sizes_used)}) — consider a tighter typography scale")
|
|
106
|
+
|
|
107
|
+
if max_consecutive_empty > 2:
|
|
108
|
+
warnings.append(f"Found {max_consecutive_empty} consecutive empty paragraphs — use spacing instead")
|
|
109
|
+
|
|
110
|
+
if heading_count == 0 and paragraph_count > 10:
|
|
111
|
+
warnings.append("No headings found in a long document — add headings for structure")
|
|
112
|
+
|
|
113
|
+
# ── Summary ──
|
|
114
|
+
info.append(f"Paragraphs: {paragraph_count}")
|
|
115
|
+
info.append(f"Headings: {heading_count}")
|
|
116
|
+
info.append(f"Tables: {len(doc.tables)}")
|
|
117
|
+
info.append(f"Sections: {len(doc.sections)}")
|
|
118
|
+
info.append(f"Fonts: {', '.join(sorted(fonts_used)) if fonts_used else 'default only'}")
|
|
119
|
+
|
|
120
|
+
# ── Report ──
|
|
121
|
+
print(f"\n{'═' * 50}")
|
|
122
|
+
print(f" Document Validation: {os.path.basename(path)}")
|
|
123
|
+
print(f"{'═' * 50}\n")
|
|
124
|
+
|
|
125
|
+
for i in info:
|
|
126
|
+
print(f" ℹ {i}")
|
|
127
|
+
print()
|
|
54
128
|
|
|
55
129
|
if issues:
|
|
56
|
-
print("
|
|
130
|
+
print(f" ✖ {len(issues)} ISSUE(S):")
|
|
57
131
|
for issue in issues:
|
|
58
|
-
print(f"
|
|
59
|
-
|
|
132
|
+
print(f" • {issue}")
|
|
133
|
+
print()
|
|
60
134
|
|
|
61
135
|
if warnings:
|
|
62
|
-
print("
|
|
63
|
-
for
|
|
64
|
-
print(f"
|
|
65
|
-
|
|
136
|
+
print(f" ⚠ {len(warnings)} WARNING(S):")
|
|
137
|
+
for w in warnings:
|
|
138
|
+
print(f" • {w}")
|
|
139
|
+
print()
|
|
66
140
|
|
|
67
|
-
|
|
68
|
-
|
|
141
|
+
if not issues and not warnings:
|
|
142
|
+
print(" ✔ No issues found — document looks clean!\n")
|
|
143
|
+
return 0
|
|
144
|
+
elif not issues:
|
|
145
|
+
print(" ✔ No critical issues (warnings only)\n")
|
|
146
|
+
return 0
|
|
147
|
+
else:
|
|
148
|
+
print(f" ✖ {len(issues)} issue(s) need fixing\n")
|
|
149
|
+
return 1
|
|
69
150
|
|
|
70
151
|
|
|
71
152
|
if __name__ == "__main__":
|
package/skills/pptx/SKILL.md
CHANGED
|
@@ -1,24 +1,181 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: "Create professional, polished slide decks
|
|
4
|
-
license: Proprietary. LICENSE.txt has complete terms
|
|
2
|
+
name: pro-slides
|
|
3
|
+
description: "Create professional, visually polished slide decks — pitch decks, board updates, keynotes, team presentations, and sales decks. Use this skill whenever the user wants to create a presentation, slide deck, pitch deck, keynote, or slides. Also trigger when someone says 'make me a deck', 'create a presentation', 'build slides', 'pitch deck', 'board update deck', or needs any visual presentation deliverable. Trigger for 'pptx', 'powerpoint', 'slides', 'deck', 'presentation'."
|
|
5
4
|
---
|
|
6
5
|
|
|
7
|
-
# Pro Slides
|
|
6
|
+
# Pro Slides — Professional Presentation Creation
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
This skill creates polished slide decks using PptxGenJS, a powerful JavaScript presentation engine. It generates visually designed presentations with consistent color themes, typography, spacing, and data visualization — not plain text slides.
|
|
10
9
|
|
|
10
|
+
## Quick Reference
|
|
11
|
+
|
|
12
|
+
| Task | Documentation | Command |
|
|
13
|
+
|------|---|---|
|
|
14
|
+
| **Analyze existing presentation** | — | `python -m markitdown presentation.pptx` |
|
|
15
|
+
| **Edit slides from a template** | Read `editing.md` | Unpack → Edit → Pack |
|
|
16
|
+
| **Create presentation from scratch** | Read `pptxgenjs.md` | Write PptxGenJS code inline |
|
|
17
|
+
| **Convert to images for QA** | See QA section | `soffice --headless --convert-to pdf output.pptx && pdftoppm -jpeg -r 150 output.pdf slide` |
|
|
18
|
+
|
|
19
|
+
## Design-First Approach
|
|
20
|
+
|
|
21
|
+
**Do not create boring slides.** The difference between great presentations and forgettable ones comes down to deliberate design choices: color, contrast, hierarchy, and visual consistency.
|
|
22
|
+
|
|
23
|
+
### Before Starting
|
|
24
|
+
|
|
25
|
+
Before writing any code, make these decisions:
|
|
26
|
+
|
|
27
|
+
1. **Pick a bold color palette** — Don't default to blue. Match the topic. Sustainability reports should feel natural (greens). Sales decks should feel urgent (warm reds/oranges). Finance should feel trustworthy (teals/navy). See palette table below.
|
|
28
|
+
|
|
29
|
+
2. **Establish visual dominance** — Don't create visual equality between elements. One color should dominate 60-70% of the slides (usually the accent color in callouts and key numbers). Everything else recedes.
|
|
30
|
+
|
|
31
|
+
3. **Design the contrast sandwich** — Title slides and closing slides have dark backgrounds with light text. Content slides have light backgrounds with dark text. This creates visual rhythm and keeps viewers engaged.
|
|
32
|
+
|
|
33
|
+
4. **Commit to a visual motif** — Every slide should have at least one deliberate visual element: an accent bar, a stat card with color, an icon, or a highlighted quote. No empty slides.
|
|
34
|
+
|
|
35
|
+
### Color Palettes
|
|
36
|
+
|
|
37
|
+
Choose one palette for the entire deck. Consistency beats variety.
|
|
38
|
+
|
|
39
|
+
| Palette Name | Primary | Secondary | Accent | Best For |
|
|
40
|
+
|---|---|---|---|---|
|
|
41
|
+
| **Midnight Executive** | `1E2761` | `CADCFC` | `F5A623` | Board decks, investor pitches, formal settings |
|
|
42
|
+
| **Forest & Moss** | `2C5F2D` | `97BC62` | `F5F5F5` | Sustainability, growth, environmental topics |
|
|
43
|
+
| **Coral Energy** | `F96167` | `F9E795` | `2F3C7E` | Marketing, launches, startups, excitement |
|
|
44
|
+
| **Warm Terracotta** | `B85042` | `E7E8D1` | `A7BEAE` | Culture, HR, team building, warmth |
|
|
45
|
+
| **Ocean Gradient** | `065A82` | `1C7293` | `21295C` | Tech, product, data, trustworthiness |
|
|
46
|
+
| **Charcoal Minimal** | `36454F` | `F2F2F2` | `212121` | Design, minimal aesthetics, agency work |
|
|
47
|
+
| **Teal Trust** | `028090` | `00A896` | `02C39A` | Healthcare, finance, security, reliability |
|
|
48
|
+
| **Cherry Bold** | `990011` | `FCF6F5` | `2F3C7E` | Sales, urgency, high-stakes decisions |
|
|
49
|
+
|
|
50
|
+
### For Each Slide: Design Checklist
|
|
51
|
+
|
|
52
|
+
Every content slide needs visual structure. Choose from these approaches:
|
|
53
|
+
|
|
54
|
+
**Layout Options:**
|
|
55
|
+
- **Two-column layout** — Left text, right callout. Left bullets, right image. Left data, right quote.
|
|
56
|
+
- **Icon + text rows** — Small icons on left, supporting text on right. Great for feature lists or process steps.
|
|
57
|
+
- **2x2 grid** — Four quadrants for options, scenarios, or related concepts.
|
|
58
|
+
- **Half-bleed image** — Image on right, text on left. Image extends to slide edge for drama.
|
|
59
|
+
- **Large stat + context** — Big number top-center, supporting bullets below.
|
|
60
|
+
|
|
61
|
+
**Data Display:**
|
|
62
|
+
- **Large stat callouts** — Use color (from palette accent). 60pt numbers, 12pt labels.
|
|
63
|
+
- **Comparison columns** — Two cards, one accent color each, bullets inside.
|
|
64
|
+
- **Timeline** — Vertical or horizontal bars with milestones.
|
|
65
|
+
- **Simple table** — Only 2-3 columns. Striped rows for readability.
|
|
66
|
+
|
|
67
|
+
**Visual Polish:**
|
|
68
|
+
- **Icons in circles** — Accent color background, white icons inside.
|
|
69
|
+
- **Italic accents** — Key phrases in italic to break up text uniformity.
|
|
70
|
+
- **Subtle shadows** — Cards have soft drop shadows (blur 6, offset 2, opacity 0.08).
|
|
71
|
+
- **Accent bars** — Thin colored rectangles (0.06 inches) above card titles.
|
|
72
|
+
- **Breathing room** — 0.5" margins minimum. 0.3-0.5" gap between content blocks.
|
|
73
|
+
|
|
74
|
+
### Typography
|
|
75
|
+
|
|
76
|
+
Use one font pairing for the entire deck. Consistency.
|
|
77
|
+
|
|
78
|
+
| Pairing | Heading Font | Body Font | Best For |
|
|
79
|
+
|---|---|---|---|
|
|
80
|
+
| Professional | Arial Black | Arial | Corporate, finance, formal |
|
|
81
|
+
| Friendly | Trebuchet | Calibri | Tech, startups, modern |
|
|
82
|
+
| Classic | Georgia | Calibri | Editorial, thoughtful content |
|
|
83
|
+
| Modern | Arial Black | Helvetica | Design, tech, minimal |
|
|
84
|
+
|
|
85
|
+
**Size Guide:**
|
|
86
|
+
- Slide titles: 20pt (content slides) or 28pt (section slides)
|
|
87
|
+
- Section headings: 18pt
|
|
88
|
+
- Body text: 14pt
|
|
89
|
+
- Captions / footnotes: 10-11pt
|
|
90
|
+
- Stat numbers: 60pt
|
|
91
|
+
- Stat labels: 12pt
|
|
92
|
+
|
|
93
|
+
**Spacing:**
|
|
94
|
+
- 0.5" minimum margins on all sides
|
|
95
|
+
- 0.3-0.5" vertical gap between content blocks
|
|
96
|
+
- Never center body text (left-align only)
|
|
97
|
+
- Use `paraSpaceAfter: 6` for bullet spacing, never `lineSpacing`
|
|
98
|
+
|
|
99
|
+
### What to Avoid
|
|
100
|
+
|
|
101
|
+
- **Don't repeat the same layout** — Two content slides in a row can feel monotonous. Vary between text-heavy, stat cards, comparisons, and two-column layouts.
|
|
102
|
+
- **Don't center body text** — Always left-align body copy. Centers work only for titles, stats, and closing slides.
|
|
103
|
+
- **Don't skimp on size contrast** — If you have a headline and body text, the headline must be significantly larger (at least 1.4x). Avoid small variations that create visual confusion.
|
|
104
|
+
- **Don't default to blue** — Blue is overdone. It's the safe choice. Pick a palette that matches the content's emotional tone.
|
|
105
|
+
- **Never add accent lines under titles** — This is the hallmark of AI-generated slides. If you add an underline, make it purposeful (color bar on stat cards) not decorative.
|
|
106
|
+
- **Don't create text-only slides** — Every slide needs at least one visual anchor: a stat card, an accent color, an icon, or a highlighted quote.
|
|
107
|
+
|
|
108
|
+
## QA Process
|
|
109
|
+
|
|
110
|
+
**Assume there are problems. Your job is to find them.**
|
|
111
|
+
|
|
112
|
+
The QA process is not optional. It's the difference between generating slides and delivering presentation-ready decks.
|
|
113
|
+
|
|
114
|
+
### Content QA
|
|
115
|
+
|
|
116
|
+
After generation, extract and verify the text:
|
|
11
117
|
```bash
|
|
12
|
-
|
|
13
|
-
node scripts/create_pptx.js spec.json output.pptx
|
|
118
|
+
python -m markitdown output.pptx > extracted.txt
|
|
14
119
|
```
|
|
120
|
+
Check:
|
|
121
|
+
- All intended content is present
|
|
122
|
+
- No text truncation or overflow
|
|
123
|
+
- No garbled Unicode or smart quotes breaking bullets
|
|
124
|
+
- Slide order matches the intended narrative
|
|
125
|
+
- Speaker notes are complete
|
|
15
126
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Recommended QA:
|
|
127
|
+
### Visual QA
|
|
19
128
|
|
|
129
|
+
Convert to images and inspect with your eyes:
|
|
20
130
|
```bash
|
|
21
|
-
|
|
22
|
-
python scripts/office/soffice.py --headless --convert-to pdf output.pptx
|
|
131
|
+
soffice --headless --convert-to pdf output.pptx
|
|
23
132
|
pdftoppm -jpeg -r 150 output.pdf slide
|
|
24
133
|
```
|
|
134
|
+
|
|
135
|
+
This generates `slide-1.jpg`, `slide-2.jpg`, etc. Open each image and check:
|
|
136
|
+
- **Text overflow** — Is any text cut off at slide edges?
|
|
137
|
+
- **Element overlap** — Are shapes, text, or images on top of each other?
|
|
138
|
+
- **Spacing consistency** — Do margins and gaps look balanced?
|
|
139
|
+
- **Color contrast** — Can you read all text easily? Light text on light background?
|
|
140
|
+
- **Missing elements** — Should there be an icon, image, or accent color that's not there?
|
|
141
|
+
- **Typography** — Do font sizes and weights feel right?
|
|
142
|
+
- **Alignment** — Are elements left-aligned where they should be?
|
|
143
|
+
|
|
144
|
+
### Verification Loop
|
|
145
|
+
|
|
146
|
+
1. **Generate** — Write PptxGenJS code, save presentation
|
|
147
|
+
2. **Convert** — Run soffice + pdftoppm to get images
|
|
148
|
+
3. **Inspect** — Open slide images in an image viewer
|
|
149
|
+
4. **Fix** — Identify problems and update the code
|
|
150
|
+
5. **Re-verify** — Regenerate and check again
|
|
151
|
+
6. **Repeat** — Continue until at least one fix-and-verify cycle is complete
|
|
152
|
+
|
|
153
|
+
**Do not declare success until you've completed at least one fix-and-verify cycle.** Even a "simple" request often has spacing issues, contrast problems, or missing visual elements that only appear when you see the actual slides.
|
|
154
|
+
|
|
155
|
+
## Dependencies
|
|
156
|
+
|
|
157
|
+
- **Node.js** (for running PptxGenJS)
|
|
158
|
+
- **pptxgenjs** — JavaScript presentation generation library
|
|
159
|
+
- **Python** with `markitdown` — For text extraction and QA
|
|
160
|
+
- **LibreOffice** (`soffice`) — For PDF conversion
|
|
161
|
+
- **pdftoppm** — For PDF to image conversion (part of poppler-utils)
|
|
162
|
+
|
|
163
|
+
Installation (first time only):
|
|
164
|
+
```bash
|
|
165
|
+
npm install -g pptxgenjs
|
|
166
|
+
pip install markitdown
|
|
167
|
+
apt-get install libreoffice poppler-utils # Linux
|
|
168
|
+
brew install libreoffice poppler # macOS
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Workflow Summary
|
|
172
|
+
|
|
173
|
+
1. **Understand** — Who, what, how long?
|
|
174
|
+
2. **Design** — Pick a palette, decide on visual approach, sketch slide structure
|
|
175
|
+
3. **Write** — Create PptxGenJS code inline (see `pptxgenjs.md`)
|
|
176
|
+
4. **Generate** — Save .pptx file
|
|
177
|
+
5. **Convert** — Create .jpg images for visual inspection
|
|
178
|
+
6. **Inspect** — Open images, look for problems
|
|
179
|
+
7. **Fix** — Update code based on findings
|
|
180
|
+
8. **Re-verify** — Regenerate and confirm fixes
|
|
181
|
+
9. **Deliver** — Provide final .pptx to user
|