Skip to main content

max / goingson

16.3 KB · 458 lines History Blame Raw
1 #!/usr/bin/env python3
2 """Generate GoingsOn pitch PDF."""
3
4 from fpdf import FPDF
5
6 # Colors
7 BG = (232, 244, 248) # Light cyan background
8 TEXT = (27, 54, 93) # Dark blue text
9 ACCENT = (247, 209, 84) # Yellow accent
10 MUTED = (100, 120, 145) # Muted text
11 WHITE = (255, 255, 255)
12 DARK = (30, 40, 55)
13 GREEN = (92, 184, 92)
14 SECTION_BG = (245, 250, 252)
15
16
17 class PitchPDF(FPDF):
18 def header(self):
19 pass
20
21 def footer(self):
22 if self.page_no() > 1:
23 self.set_y(-15)
24 self.set_font("Helvetica", "I", 8)
25 self.set_text_color(*MUTED)
26 self.cell(0, 10, f"GoingsOn | goingson.app | Page {self.page_no()}", align="C")
27
28 def section_title(self, title):
29 self.set_font("Helvetica", "B", 16)
30 self.set_text_color(*TEXT)
31 self.cell(0, 10, title, new_x="LMARGIN", new_y="NEXT")
32 # Yellow underline
33 self.set_draw_color(*ACCENT)
34 self.set_line_width(1.5)
35 self.line(self.l_margin, self.get_y(), self.l_margin + 50, self.get_y())
36 self.ln(6)
37
38 def feature_block(self, icon, title, bullets):
39 y_start = self.get_y()
40 # Title
41 self.set_font("Helvetica", "B", 12)
42 self.set_text_color(*TEXT)
43 self.cell(0, 7, f"{icon} {title}", new_x="LMARGIN", new_y="NEXT")
44 self.ln(1)
45 # Bullets
46 self.set_font("Helvetica", "", 9.5)
47 self.set_text_color(*MUTED)
48 for bullet in bullets:
49 self.set_x(self.l_margin + 6)
50 self.multi_cell(self.epw - 6, 4.5, f"- {bullet}", new_x="LMARGIN", new_y="NEXT")
51 self.ln(3)
52
53 def two_col_features(self, left, right):
54 col_w = (self.epw - 8) / 2
55 y_start = self.get_y()
56
57 # Left column
58 self.set_x(self.l_margin)
59 self.feature_col(left, col_w)
60 y_left = self.get_y()
61
62 # Right column
63 self.set_y(y_start)
64 self.set_x(self.l_margin + col_w + 8)
65 self.feature_col(right, col_w)
66 y_right = self.get_y()
67
68 self.set_y(max(y_left, y_right))
69
70 def feature_col(self, items, width):
71 x_start = self.get_x()
72 for icon, title, bullets in items:
73 self.set_x(x_start)
74 self.set_font("Helvetica", "B", 11)
75 self.set_text_color(*TEXT)
76 self.cell(width, 7, f"{icon} {title}", new_x="LMARGIN", new_y="NEXT")
77 self.ln(1)
78 self.set_font("Helvetica", "", 9)
79 self.set_text_color(*MUTED)
80 for bullet in bullets:
81 self.set_x(x_start + 5)
82 self.multi_cell(width - 5, 4.2, f"- {bullet}", new_x="LMARGIN", new_y="NEXT")
83 self.ln(3)
84
85
86 def build_pdf():
87 pdf = PitchPDF()
88 pdf.set_auto_page_break(auto=True, margin=20)
89 pdf.set_margins(20, 15, 20)
90
91 # =========================================================
92 # PAGE 1 - Title / Hero
93 # =========================================================
94 pdf.add_page()
95
96 # Background fill
97 pdf.set_fill_color(*DARK)
98 pdf.rect(0, 0, 210, 297, "F")
99
100 # "GO" logo text
101 pdf.set_y(60)
102 pdf.set_font("Helvetica", "B", 72)
103 pdf.set_text_color(*ACCENT)
104 pdf.cell(0, 30, "GO", align="C", new_x="LMARGIN", new_y="NEXT")
105
106 # App name
107 pdf.set_font("Helvetica", "B", 36)
108 pdf.set_text_color(*WHITE)
109 pdf.cell(0, 16, "GoingsOn", align="C", new_x="LMARGIN", new_y="NEXT")
110
111 # Tagline
112 pdf.ln(6)
113 pdf.set_font("Helvetica", "", 14)
114 pdf.set_text_color(180, 195, 210)
115 pdf.cell(0, 8, "Tasks. Email. Calendar. Contacts.", align="C", new_x="LMARGIN", new_y="NEXT")
116 pdf.cell(0, 8, "One app. Offline-first. Native.", align="C", new_x="LMARGIN", new_y="NEXT")
117
118 # Divider
119 pdf.ln(12)
120 pdf.set_draw_color(*ACCENT)
121 pdf.set_line_width(1)
122 pdf.line(70, pdf.get_y(), 140, pdf.get_y())
123 pdf.ln(12)
124
125 # Value props
126 pdf.set_font("Helvetica", "", 11)
127 pdf.set_text_color(200, 210, 220)
128 props = [
129 "Everything in one place -- no more juggling 5 apps",
130 "Your data stays on your machine -- offline-first with local storage",
131 "Native performance -- built with Rust, not Electron",
132 "Free during alpha -- no subscriptions, no lock-in",
133 "macOS + iOS -- with Windows and Linux support",
134 ]
135 for prop in props:
136 pdf.cell(0, 7, prop, align="C", new_x="LMARGIN", new_y="NEXT")
137
138 # Bottom
139 pdf.set_y(255)
140 pdf.set_font("Helvetica", "I", 10)
141 pdf.set_text_color(120, 135, 155)
142 pdf.cell(0, 6, "Alpha Release -- March 2026", align="C", new_x="LMARGIN", new_y="NEXT")
143 pdf.cell(0, 6, "goingson.app", align="C", new_x="LMARGIN", new_y="NEXT")
144
145 # =========================================================
146 # PAGE 2 - Core Features
147 # =========================================================
148 pdf.add_page()
149
150 pdf.section_title("Core Features")
151
152 pdf.feature_block("TASKS", "Task Management", [
153 "Automatic urgency scoring based on priority, due date, and age",
154 "Quick-add with natural language: \"Fix bug due:tomorrow +urgent @Project\"",
155 "Subtasks, annotations, and milestone tracking",
156 "Recurrence (daily, weekly, monthly) with auto-creation on completion",
157 "Snooze tasks until later today, tomorrow, weekend, or next week",
158 "Bulk operations: complete, snooze, or delete multiple tasks at once",
159 "Saved views and named filters for any combination of criteria",
160 ])
161
162 pdf.feature_block("EMAIL", "Email Integration", [
163 "Full IMAP/SMTP client built in -- connect any email account",
164 "OAuth2 for Fastmail, Google, Microsoft, and Yahoo",
165 "Native JMAP protocol support for Fastmail",
166 "Threaded conversations with reader mode (strips HTML clutter)",
167 "Create tasks directly from emails with source linking",
168 "Auto-sync on configurable intervals (5/15/30/60 min)",
169 "Compose, reply, archive, snooze -- all without leaving the app",
170 ])
171
172 pdf.feature_block("CALENDAR", "Calendar & Events", [
173 "Create events with title, description, location, and time range",
174 "Link events to projects and contacts",
175 "Recurrence support (daily, weekly, monthly, yearly)",
176 "Time block types: Focus, Personal, Vacation, Free Time",
177 "Event reminders with configurable lead time",
178 "Conflict detection with existing time blocks",
179 ])
180
181 pdf.feature_block("CONTACTS", "Contacts", [
182 "Store names, companies, emails, phones, social handles, custom fields",
183 "Full-text search across all contact fields",
184 "Auto-suggest contacts from email senders",
185 "Link contacts to tasks, events, and email threads",
186 ])
187
188 # =========================================================
189 # PAGE 3 - Planning & Extras
190 # =========================================================
191 pdf.add_page()
192
193 pdf.section_title("Planning & Workflow")
194
195 pdf.feature_block("PLAN", "Day Planning & Time Blocking", [
196 "Visual hourly timeline for your day",
197 "Drag unscheduled tasks onto the timeline to create time blocks",
198 "Events display alongside tasks on the same timeline",
199 "Current time indicator and day navigation with keyboard shortcuts",
200 "Collapsible sidebar with unscheduled tasks",
201 ])
202
203 pdf.feature_block("REVIEW", "Weekly Review", [
204 "Guided review: see what you completed last week",
205 "Set focus tasks and projects for the current week",
206 "Track vacation days with visual banners",
207 "Monday nudge: gentle reminder when review is overdue",
208 ])
209
210 pdf.ln(4)
211 pdf.section_title("Power Features")
212
213 pdf.feature_block("SEARCH", "Search & Saved Views", [
214 "Full-text search across tasks, projects, emails, and contacts",
215 "Date range syntax: after:, before:, from:, to:",
216 "Save any filter as a named view; pin favorites for quick access",
217 ])
218
219 pdf.feature_block("KEYBOARD", "Keyboard-Driven", [
220 "Vim-style navigation (j/k for items, g+key to jump between views)",
221 "Quick-add from anywhere with 'q', shortcuts overlay with '?'",
222 "Native menu bar with standard macOS shortcuts",
223 ])
224
225 pdf.feature_block("EXPORT", "Export & Backup", [
226 "Export all data as JSON, tasks as CSV, events as ICS",
227 "Automated daily backups with configurable retention",
228 "Restore from any backup point",
229 ])
230
231 pdf.feature_block("THEMES", "Themes", [
232 "10 built-in themes: Catppuccin, Dracula, Nord, Tokyo Night, and more",
233 "Follow System for automatic light/dark switching",
234 ])
235
236 # =========================================================
237 # PAGE 4 - Why GoingsOn + Comparison
238 # =========================================================
239 pdf.add_page()
240
241 pdf.section_title("Why GoingsOn?")
242
243 pdf.set_font("Helvetica", "", 10.5)
244 pdf.set_text_color(*TEXT)
245 pdf.multi_cell(0, 5.5,
246 "Most productivity tools are cloud-only, subscription-based, and spread across "
247 "multiple apps. GoingsOn brings tasks, email, calendar, and contacts into one "
248 "native application that works offline and keeps your data on your machine."
249 )
250 pdf.ln(6)
251
252 # Comparison table
253 pdf.set_font("Helvetica", "B", 11)
254 pdf.set_text_color(*TEXT)
255 pdf.cell(0, 8, "How it compares:", new_x="LMARGIN", new_y="NEXT")
256 pdf.ln(2)
257
258 headers = ["", "GoingsOn", "Todoist", "Things 3", "Fantastical", "Spark"]
259 col_w = [38, 28, 28, 28, 28, 28]
260
261 # Header row
262 pdf.set_font("Helvetica", "B", 8)
263 pdf.set_fill_color(*DARK)
264 pdf.set_text_color(*WHITE)
265 for i, h in enumerate(headers):
266 pdf.cell(col_w[i], 7, h, border=1, fill=True, align="C")
267 pdf.ln()
268
269 rows = [
270 ["Tasks", "Yes", "Yes", "Yes", "No", "No"],
271 ["Email", "Yes", "No", "No", "No", "Yes"],
272 ["Calendar", "Yes", "No", "No", "Yes", "No"],
273 ["Contacts", "Yes", "No", "No", "No", "No"],
274 ["Weekly Review", "Yes", "No", "No", "No", "No"],
275 ["Offline-first", "Yes", "No", "Yes", "Yes", "No"],
276 ["Cross-platform","Yes", "Yes", "Apple","Apple","Yes"],
277 ["Free option", "Yes", "Ltd", "No", "Ltd", "Ltd"],
278 ]
279
280 pdf.set_font("Helvetica", "", 8)
281 for row in rows:
282 for i, val in enumerate(row):
283 if i == 0:
284 pdf.set_font("Helvetica", "B", 8)
285 pdf.set_text_color(*TEXT)
286 pdf.set_fill_color(*SECTION_BG)
287 else:
288 pdf.set_font("Helvetica", "", 8)
289 if val == "Yes" and i == 1:
290 pdf.set_text_color(*GREEN)
291 pdf.set_fill_color(*WHITE)
292 elif val == "Yes":
293 pdf.set_text_color(80, 80, 80)
294 pdf.set_fill_color(*WHITE)
295 elif val == "No":
296 pdf.set_text_color(180, 180, 180)
297 pdf.set_fill_color(*WHITE)
298 else:
299 pdf.set_text_color(*MUTED)
300 pdf.set_fill_color(*WHITE)
301 pdf.cell(col_w[i], 6, val, border=1, fill=True, align="C" if i > 0 else "L")
302 pdf.ln()
303
304 pdf.ln(8)
305
306 # Pricing note
307 pdf.set_font("Helvetica", "B", 12)
308 pdf.set_text_color(*TEXT)
309 pdf.cell(0, 8, "Pricing", new_x="LMARGIN", new_y="NEXT")
310 pdf.set_draw_color(*ACCENT)
311 pdf.set_line_width(1.5)
312 pdf.line(pdf.l_margin, pdf.get_y(), pdf.l_margin + 30, pdf.get_y())
313 pdf.ln(4)
314
315 pdf.set_font("Helvetica", "", 10.5)
316 pdf.set_text_color(*TEXT)
317 pdf.multi_cell(0, 5.5,
318 "GoingsOn is free during the alpha period. After launch, we plan to offer a "
319 "one-time purchase model -- no subscriptions. Your data, your app, forever."
320 )
321
322 pdf.ln(8)
323
324 # Tech
325 pdf.set_font("Helvetica", "B", 12)
326 pdf.set_text_color(*TEXT)
327 pdf.cell(0, 8, "Built With", new_x="LMARGIN", new_y="NEXT")
328 pdf.set_draw_color(*ACCENT)
329 pdf.line(pdf.l_margin, pdf.get_y(), pdf.l_margin + 30, pdf.get_y())
330 pdf.ln(4)
331
332 pdf.set_font("Helvetica", "", 10)
333 pdf.set_text_color(*MUTED)
334 tech_items = [
335 "Rust backend (Tauri 2) -- native performance, small binary, low memory",
336 "SQLite database -- your data in a single local file",
337 "Vanilla JavaScript frontend -- no framework bloat",
338 "205+ automated tests across the codebase",
339 ]
340 for item in tech_items:
341 pdf.cell(0, 6, f" {item}", new_x="LMARGIN", new_y="NEXT")
342
343 # =========================================================
344 # PAGE 5 - Installing via TestFlight
345 # =========================================================
346 pdf.add_page()
347
348 pdf.section_title("Get GoingsOn")
349
350 # macOS
351 pdf.set_font("Helvetica", "B", 13)
352 pdf.set_text_color(*TEXT)
353 pdf.cell(0, 8, "macOS", new_x="LMARGIN", new_y="NEXT")
354 pdf.ln(2)
355
356 pdf.set_font("Helvetica", "", 10.5)
357 pdf.set_text_color(*MUTED)
358 pdf.multi_cell(0, 5.5,
359 "The macOS app will be shared with you directly as a .dmg file. "
360 "Open the DMG and drag GoingsOn to your Applications folder."
361 )
362 pdf.ln(2)
363 pdf.set_font("Helvetica", "I", 9.5)
364 pdf.multi_cell(0, 5,
365 "Note: Since the app is not yet on the Mac App Store, macOS may show a security "
366 "warning on first launch. Right-click the app and choose \"Open\" to bypass this, "
367 "or go to System Settings > Privacy & Security and click \"Open Anyway\"."
368 )
369
370 pdf.ln(8)
371
372 # iOS TestFlight
373 pdf.set_font("Helvetica", "B", 13)
374 pdf.set_text_color(*TEXT)
375 pdf.cell(0, 8, "iOS (via TestFlight)", new_x="LMARGIN", new_y="NEXT")
376 pdf.ln(2)
377
378 pdf.set_font("Helvetica", "", 10.5)
379 pdf.set_text_color(*MUTED)
380 pdf.multi_cell(0, 5.5,
381 "GoingsOn for iOS is distributed through Apple's TestFlight, "
382 "which lets you install pre-release apps directly on your iPhone or iPad."
383 )
384 pdf.ln(4)
385
386 steps = [
387 ("1.", "Install TestFlight",
388 "Download \"TestFlight\" from the App Store if you don't have it already. "
389 "It's a free app by Apple."),
390 ("2.", "Open the invitation link",
391 "You'll receive a TestFlight invitation link (via email or message). "
392 "Tap the link on your iPhone or iPad."),
393 ("3.", "Accept the invitation",
394 "The link opens TestFlight. Tap \"Accept\" to join the GoingsOn beta, "
395 "then tap \"Install\" to download the app."),
396 ("4.", "Open GoingsOn",
397 "Once installed, GoingsOn appears on your home screen like any other app. "
398 "You can also launch it from inside TestFlight."),
399 ("5.", "Updates are automatic",
400 "When a new build is available, TestFlight will notify you. "
401 "Updates install with one tap -- no need to uninstall and reinstall."),
402 ]
403
404 for num, title, desc in steps:
405 pdf.set_font("Helvetica", "B", 11)
406 pdf.set_text_color(*TEXT)
407 pdf.cell(8, 6, num)
408 pdf.cell(0, 6, title, new_x="LMARGIN", new_y="NEXT")
409 pdf.set_x(pdf.l_margin + 8)
410 pdf.set_font("Helvetica", "", 9.5)
411 pdf.set_text_color(*MUTED)
412 pdf.multi_cell(pdf.epw - 8, 4.8, desc, new_x="LMARGIN", new_y="NEXT")
413 pdf.ln(3)
414
415 pdf.ln(4)
416
417 # Feedback
418 pdf.set_font("Helvetica", "B", 13)
419 pdf.set_text_color(*TEXT)
420 pdf.cell(0, 8, "Sending Feedback", new_x="LMARGIN", new_y="NEXT")
421 pdf.ln(2)
422
423 pdf.set_font("Helvetica", "", 10.5)
424 pdf.set_text_color(*MUTED)
425 pdf.multi_cell(0, 5.5,
426 "Your feedback is invaluable during this alpha period. You can:"
427 )
428 pdf.ln(2)
429
430 feedback = [
431 "Take a screenshot in the app and share it with any notes",
432 "Use TestFlight's built-in feedback tool (shake your iPhone or tap \"Send Beta Feedback\" in TestFlight)",
433 "Message me directly -- anything from bug reports to feature ideas",
434 ]
435 for item in feedback:
436 pdf.set_x(pdf.l_margin + 4)
437 pdf.set_font("Helvetica", "", 10)
438 pdf.multi_cell(pdf.epw - 4, 5, f"- {item}", new_x="LMARGIN", new_y="NEXT")
439
440 pdf.ln(10)
441
442 # Thank you
443 pdf.set_font("Helvetica", "B", 14)
444 pdf.set_text_color(*TEXT)
445 pdf.cell(0, 10, "Thank you for testing GoingsOn!", align="C", new_x="LMARGIN", new_y="NEXT")
446
447
448 # =========================================================
449 # OUTPUT
450 # =========================================================
451 output_path = "/Users/max/Git/active/goingson/GoingsOn-Pitch.pdf"
452 pdf.output(output_path)
453 print(f"PDF generated: {output_path}")
454
455
456 if __name__ == "__main__":
457 build_pdf()
458