Skip to main content

max / makenotwork

18.5 KB · 687 lines History Blame Raw
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Balanced Breakfast — Pitch</title>
7 <style>
8 @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=Lato:wght@300;400;700&family=Young+Serif&display=swap');
9
10 :root {
11 --bg: #faf8f5;
12 --bg-warm: #f5f0e8;
13 --bg-deep: #ebe4d8;
14 --text: #3d3225;
15 --text-secondary: #6b5d4d;
16 --text-muted: #9a8b78;
17 --accent: #c94b4b;
18 --yolk: #e8a841;
19 --yolk-light: #f4c56d;
20 --success: #6b9b5a;
21 --border: #e0d6c8;
22 }
23
24 * { margin: 0; padding: 0; box-sizing: border-box; }
25
26 @page {
27 size: letter;
28 margin: 0;
29 }
30
31 body {
32 font-family: 'Lato', sans-serif;
33 color: var(--text);
34 background: var(--bg);
35 line-height: 1.6;
36 -webkit-print-color-adjust: exact;
37 print-color-adjust: exact;
38 }
39
40 .page {
41 width: 8.5in;
42 min-height: 11in;
43 margin: 0 auto;
44 padding: 0.7in 0.8in;
45 background: var(--bg);
46 page-break-after: always;
47 position: relative;
48 }
49
50 .page:last-child { page-break-after: auto; }
51
52 /* ── Hero ── */
53
54 .hero {
55 text-align: center;
56 padding: 0.4in 0 0.35in;
57 border-bottom: 2px solid var(--border);
58 margin-bottom: 0.35in;
59 }
60
61 .hero h1 {
62 font-family: 'Young Serif', serif;
63 font-size: 42px;
64 font-weight: 400;
65 color: var(--text);
66 margin-bottom: 6px;
67 letter-spacing: -0.5px;
68 }
69
70 .hero h1 .dot {
71 color: var(--yolk);
72 }
73
74 .hero .tagline {
75 font-family: 'IBM Plex Mono', monospace;
76 font-size: 14px;
77 color: var(--text-secondary);
78 letter-spacing: 0.3px;
79 }
80
81 .hero .sub {
82 font-size: 13px;
83 color: var(--text-muted);
84 margin-top: 10px;
85 }
86
87 /* ── Section headers ── */
88
89 h2 {
90 font-family: 'IBM Plex Mono', monospace;
91 font-size: 15px;
92 font-weight: 600;
93 text-transform: uppercase;
94 letter-spacing: 1.5px;
95 color: var(--accent);
96 margin-bottom: 14px;
97 padding-bottom: 6px;
98 border-bottom: 1px solid var(--border);
99 }
100
101 h3 {
102 font-family: 'IBM Plex Mono', monospace;
103 font-size: 13px;
104 font-weight: 600;
105 color: var(--text);
106 margin-bottom: 6px;
107 margin-top: 16px;
108 }
109
110 h3:first-child { margin-top: 0; }
111
112 /* ── Intro block ── */
113
114 .intro {
115 font-size: 15px;
116 line-height: 1.7;
117 margin-bottom: 0.3in;
118 color: var(--text);
119 }
120
121 .intro strong { color: var(--text); }
122
123 /* ── Feature grid ── */
124
125 .features {
126 display: grid;
127 grid-template-columns: 1fr 1fr;
128 gap: 18px;
129 margin-bottom: 0.3in;
130 }
131
132 .feature-card {
133 background: var(--bg-warm);
134 border: 1px solid var(--border);
135 border-radius: 6px;
136 padding: 14px 16px;
137 }
138
139 .feature-card h3 {
140 margin: 0 0 6px 0;
141 font-size: 12.5px;
142 color: var(--accent);
143 }
144
145 .feature-card ul {
146 list-style: none;
147 padding: 0;
148 }
149
150 .feature-card li {
151 font-size: 12px;
152 line-height: 1.5;
153 color: var(--text-secondary);
154 padding: 2px 0;
155 padding-left: 14px;
156 position: relative;
157 }
158
159 .feature-card li::before {
160 content: '';
161 position: absolute;
162 left: 0;
163 top: 8px;
164 width: 5px;
165 height: 5px;
166 background: var(--yolk);
167 border-radius: 50%;
168 }
169
170 /* ── Full-width feature section ── */
171
172 .feature-section {
173 margin-bottom: 0.25in;
174 }
175
176 .feature-section ul {
177 list-style: none;
178 padding: 0;
179 columns: 2;
180 column-gap: 24px;
181 }
182
183 .feature-section li {
184 font-size: 12.5px;
185 line-height: 1.5;
186 color: var(--text-secondary);
187 padding: 2.5px 0 2.5px 14px;
188 position: relative;
189 break-inside: avoid;
190 }
191
192 .feature-section li::before {
193 content: '';
194 position: absolute;
195 left: 0;
196 top: 9px;
197 width: 5px;
198 height: 5px;
199 background: var(--yolk);
200 border-radius: 50%;
201 }
202
203 /* ── Highlight box ── */
204
205 .highlight-box {
206 background: var(--bg-warm);
207 border-left: 3px solid var(--yolk);
208 padding: 14px 18px;
209 margin-bottom: 0.25in;
210 border-radius: 0 6px 6px 0;
211 }
212
213 .highlight-box p {
214 font-size: 13px;
215 line-height: 1.6;
216 color: var(--text-secondary);
217 }
218
219 .highlight-box strong {
220 color: var(--text);
221 }
222
223 /* ── Comparison table ── */
224
225 .comparison {
226 width: 100%;
227 border-collapse: collapse;
228 margin-bottom: 0.25in;
229 font-size: 12px;
230 }
231
232 .comparison th {
233 font-family: 'IBM Plex Mono', monospace;
234 font-size: 11px;
235 font-weight: 600;
236 text-align: left;
237 padding: 8px 10px;
238 background: var(--bg-deep);
239 color: var(--text);
240 border-bottom: 1px solid var(--border);
241 }
242
243 .comparison td {
244 padding: 6px 10px;
245 border-bottom: 1px solid var(--border);
246 color: var(--text-secondary);
247 vertical-align: top;
248 }
249
250 .comparison .check {
251 color: var(--success);
252 font-weight: 700;
253 font-size: 14px;
254 }
255
256 .comparison .dash {
257 color: var(--text-muted);
258 }
259
260 .comparison tr:last-child td { border-bottom: none; }
261
262 /* ── Stats row ── */
263
264 .stats {
265 display: grid;
266 grid-template-columns: repeat(4, 1fr);
267 gap: 12px;
268 margin-bottom: 0.3in;
269 }
270
271 .stat {
272 text-align: center;
273 background: var(--bg-warm);
274 border: 1px solid var(--border);
275 border-radius: 6px;
276 padding: 12px 8px;
277 }
278
279 .stat .num {
280 font-family: 'Young Serif', serif;
281 font-size: 28px;
282 color: var(--accent);
283 display: block;
284 line-height: 1;
285 margin-bottom: 4px;
286 }
287
288 .stat .label {
289 font-family: 'IBM Plex Mono', monospace;
290 font-size: 10px;
291 color: var(--text-muted);
292 text-transform: uppercase;
293 letter-spacing: 0.5px;
294 }
295
296 /* ── Plugin showcase ── */
297
298 .plugins {
299 display: grid;
300 grid-template-columns: repeat(4, 1fr);
301 gap: 12px;
302 margin-bottom: 0.25in;
303 }
304
305 .plugin {
306 background: var(--bg-warm);
307 border: 1px solid var(--border);
308 border-radius: 6px;
309 padding: 12px 14px;
310 text-align: center;
311 }
312
313 .plugin .icon {
314 font-size: 24px;
315 display: block;
316 margin-bottom: 6px;
317 }
318
319 .plugin .name {
320 font-family: 'IBM Plex Mono', monospace;
321 font-size: 12px;
322 font-weight: 600;
323 color: var(--text);
324 display: block;
325 margin-bottom: 3px;
326 }
327
328 .plugin .desc {
329 font-size: 11px;
330 color: var(--text-muted);
331 line-height: 1.4;
332 }
333
334 /* ── Keyboard shortcuts ── */
335
336 .shortcuts {
337 display: grid;
338 grid-template-columns: repeat(3, 1fr);
339 gap: 6px 16px;
340 margin-bottom: 0.2in;
341 }
342
343 .shortcut {
344 display: flex;
345 align-items: center;
346 gap: 8px;
347 font-size: 12px;
348 color: var(--text-secondary);
349 padding: 2px 0;
350 }
351
352 .shortcut kbd {
353 font-family: 'IBM Plex Mono', monospace;
354 font-size: 11px;
355 background: var(--bg-deep);
356 border: 1px solid var(--border);
357 border-radius: 3px;
358 padding: 1px 6px;
359 color: var(--text);
360 min-width: 24px;
361 text-align: center;
362 }
363
364 /* ── Footer ── */
365
366 .footer {
367 position: absolute;
368 bottom: 0.5in;
369 left: 0.8in;
370 right: 0.8in;
371 display: flex;
372 justify-content: space-between;
373 align-items: center;
374 padding-top: 12px;
375 border-top: 1px solid var(--border);
376 }
377
378 .footer .left {
379 font-family: 'IBM Plex Mono', monospace;
380 font-size: 10px;
381 color: var(--text-muted);
382 }
383
384 .footer .right {
385 font-family: 'IBM Plex Mono', monospace;
386 font-size: 10px;
387 color: var(--text-muted);
388 }
389
390 /* ── Inline footer (non-absolute, for page 2+) ── */
391 .footer-inline {
392 display: flex;
393 justify-content: space-between;
394 align-items: center;
395 padding-top: 12px;
396 border-top: 1px solid var(--border);
397 margin-top: auto;
398 }
399
400 .footer-inline .left,
401 .footer-inline .right {
402 font-family: 'IBM Plex Mono', monospace;
403 font-size: 10px;
404 color: var(--text-muted);
405 }
406
407 /* ── Utilities ── */
408
409 .mb-sm { margin-bottom: 10px; }
410 .mb-md { margin-bottom: 18px; }
411 .text-small { font-size: 12px; color: var(--text-secondary); }
412 .text-muted { color: var(--text-muted); }
413
414 @media print {
415 body { background: white; }
416 .page { box-shadow: none; }
417 }
418
419 @media screen {
420 body { background: #ddd; padding: 20px 0; }
421 .page { box-shadow: 0 2px 20px rgba(0,0,0,0.15); margin-bottom: 20px; }
422 }
423 </style>
424 </head>
425 <body>
426
427 <!-- ════════════════════════════════════════════════════════════════════════ -->
428 <!-- PAGE 1 -->
429 <!-- ════════════════════════════════════════════════════════════════════════ -->
430 <div class="page">
431
432 <div class="hero">
433 <h1>Balanced Breakfast<span class="dot">.</span></h1>
434 <div class="tagline">When it comes to media and news, it's good to be a picky eater.</div>
435 <div class="sub">A local-first, extensible feed reader for people who read with intention.</div>
436 </div>
437
438 <div class="intro">
439 Balanced Breakfast is a native desktop feed reader that puts you in control.
440 Subscribe to RSS, Hacker News, arXiv, and any source you can script.
441 Everything stays on your machine. No account required, no telemetry, no cloud dependency.
442 Built with Rust and Tauri for speed and a small footprint.
443 </div>
444
445 <div class="stats">
446 <div class="stat">
447 <span class="num">4</span>
448 <span class="label">Built-in sources</span>
449 </div>
450 <div class="stat">
451 <span class="num">&infin;</span>
452 <span class="label">Custom via plugins</span>
453 </div>
454 <div class="stat">
455 <span class="num">9</span>
456 <span class="label">Bundled themes</span>
457 </div>
458 <div class="stat">
459 <span class="num">0</span>
460 <span class="label">Telemetry &amp; tracking</span>
461 </div>
462 </div>
463
464 <h2>What You Get</h2>
465
466 <div class="features">
467 <div class="feature-card">
468 <h3>Multi-Source Timeline</h3>
469 <ul>
470 <li>RSS, Atom, and JSON Feed (1.0/1.1)</li>
471 <li>Hacker News: Top, New, Best, Ask, Show, Jobs</li>
472 <li>arXiv papers by category (cs.AI, cs.LG, etc.)</li>
473 <li>Reader view with article extraction</li>
474 <li>Everything in one unified feed</li>
475 </ul>
476 </div>
477 <div class="feature-card">
478 <h3>Rhai Plugin System</h3>
479 <ul>
480 <li>Write custom source adapters in Rhai script</li>
481 <li>Dynamic config forms (text, URL, secret, select)</li>
482 <li>Pagination, search, auth capabilities</li>
483 <li>Plugin secrets encrypted at rest (AES-256-GCM)</li>
484 <li>No recompilation needed to add sources</li>
485 </ul>
486 </div>
487 <div class="feature-card">
488 <h3>Reading & Organization</h3>
489 <ul>
490 <li>Three-panel layout: sources, items, detail</li>
491 <li>Mark read/unread, star/favorite items</li>
492 <li>Feed tags and per-source filtering</li>
493 <li>Full-text search across all content (FTS5)</li>
494 <li>Sort by date, score, unread-first, starred-first</li>
495 </ul>
496 </div>
497 <div class="feature-card">
498 <h3>Data Ownership</h3>
499 <ul>
500 <li>All data in local SQLite &mdash; no cloud required</li>
501 <li>OPML import and export</li>
502 <li>Optional E2E encrypted cloud sync (SyncKit)</li>
503 <li>URL tracker stripping (utm_*, fbclid, gclid)</li>
504 <li>Content-addressable storage (no duplicates)</li>
505 </ul>
506 </div>
507 </div>
508
509 <h2>Keyboard-Driven</h2>
510
511 <div class="shortcuts">
512 <div class="shortcut"><kbd>j</kbd><kbd>k</kbd> Navigate items</div>
513 <div class="shortcut"><kbd>s</kbd> Star / unstar</div>
514 <div class="shortcut"><kbd>r</kbd> Toggle read</div>
515 <div class="shortcut"><kbd>/</kbd> Search</div>
516 <div class="shortcut"><kbd>o</kbd> Open in browser</div>
517 <div class="shortcut"><kbd>Esc</kbd> Close panel</div>
518 <div class="shortcut"><kbd>&#8984;R</kbd> Refresh all</div>
519 <div class="shortcut"><kbd>&#8984;N</kbd> Add new feed</div>
520 <div class="shortcut"><kbd>&#8984;I</kbd> Import OPML</div>
521 </div>
522
523 <div class="footer">
524 <div class="left">Balanced Breakfast &mdash; v0.3.1</div>
525 <div class="right">makenot.work</div>
526 </div>
527
528 </div>
529
530 <!-- ════════════════════════════════════════════════════════════════════════ -->
531 <!-- PAGE 2 -->
532 <!-- ════════════════════════════════════════════════════════════════════════ -->
533 <div class="page" style="display: flex; flex-direction: column;">
534
535 <h2>Built-In Plugins</h2>
536
537 <div class="plugins">
538 <div class="plugin">
539 <span class="icon">&#x1F4E1;</span>
540 <span class="name">RSS / Atom</span>
541 <div class="desc">Subscribe to any feed URL. Multi-URL support, tag extraction, JSON Feed compatible.</div>
542 </div>
543 <div class="plugin">
544 <span class="icon">&#x1F4AC;</span>
545 <span class="name">Hacker News</span>
546 <div class="desc">Browse Top, New, Best, Ask HN, Show HN, and Jobs. Score metadata and pagination.</div>
547 </div>
548 <div class="plugin">
549 <span class="icon">&#x1F4C4;</span>
550 <span class="name">arXiv</span>
551 <div class="desc">Track papers by category. cs.AI, cs.LG, cs.CL, cs.CV, stat.ML, and more.</div>
552 </div>
553 <div class="plugin">
554 <span class="icon">&#x1F4D6;</span>
555 <span class="name">Reader View</span>
556 <div class="desc">Extract clean article content from any web page. Readability-style rendering.</div>
557 </div>
558 </div>
559
560 <div class="highlight-box">
561 <p><strong>Write your own.</strong> Plugins are Rhai scripts &mdash; a lightweight, sandboxed scripting language. Define a fetch function, a config schema, and you have a new source. Host functions for HTTP requests, XML/JSON parsing, and text processing are built in. A full authoring guide ships with the app.</p>
562 </div>
563
564 <h2>Query Feeds</h2>
565
566 <div class="highlight-box mb-md">
567 <p>Build saved filters with multiple conditions. Match on title, author, body, source, tag, or status. Supports <strong>contains</strong>, <strong>equals</strong>, and <strong>regex</strong> operators. Live match counts show how many items hit each query. Think of them as virtual feeds that pull from everything you subscribe to.</p>
568 </div>
569
570 <h2>Under the Hood</h2>
571
572 <div class="feature-section">
573 <ul>
574 <li>Auto-fetch every 15 minutes (configurable per plugin)</li>
575 <li>Circuit breaker: auto-disables feeds after 10 consecutive failures</li>
576 <li>Feed health monitoring with failure counts and last-error display</li>
577 <li>Stale item cleanup (read, non-starred items older than 30 days)</li>
578 <li>Duplicate detection on OPML import</li>
579 <li>HTML sanitization on all rendered content</li>
580 <li>9 bundled TOML themes + user custom themes + high-contrast mode</li>
581 <li>Paginated item loading (50 at a time, load-more)</li>
582 <li>Toast notifications for fetch errors and status updates</li>
583 </ul>
584 </div>
585
586 <h2>Cloud Sync (Optional)</h2>
587
588 <div class="highlight-box mb-md">
589 <p>Sync feed configs, read/star state, and preferences across devices via <strong>MNW SyncKit</strong>. End-to-end encrypted &mdash; the server never sees your data. Feed content re-fetches from source on each device, so only your state crosses the wire. <strong>No account required to use the app</strong> &mdash; sync is entirely opt-in.</p>
590 </div>
591
592 <h2>How It Compares</h2>
593
594 <table class="comparison">
595 <tr>
596 <th style="width: 28%">Feature</th>
597 <th style="width: 18%">BB</th>
598 <th style="width: 18%">Feedly / Inoreader</th>
599 <th style="width: 18%">NetNewsWire</th>
600 <th style="width: 18%">Miniflux</th>
601 </tr>
602 <tr>
603 <td>Custom source plugins</td>
604 <td><span class="check">Yes</span></td>
605 <td><span class="dash">&mdash;</span></td>
606 <td><span class="dash">&mdash;</span></td>
607 <td><span class="dash">&mdash;</span></td>
608 </tr>
609 <tr>
610 <td>HN + arXiv built-in</td>
611 <td><span class="check">Yes</span></td>
612 <td><span class="dash">&mdash;</span></td>
613 <td><span class="dash">&mdash;</span></td>
614 <td><span class="dash">&mdash;</span></td>
615 </tr>
616 <tr>
617 <td>Native desktop</td>
618 <td><span class="check">Yes</span></td>
619 <td><span class="dash">&mdash;</span></td>
620 <td><span class="check">Yes</span></td>
621 <td><span class="dash">&mdash;</span></td>
622 </tr>
623 <tr>
624 <td>Cross-platform</td>
625 <td><span class="check">Yes</span></td>
626 <td><span class="check">Yes</span></td>
627 <td>Apple only</td>
628 <td><span class="check">Yes</span></td>
629 </tr>
630 <tr>
631 <td>No account required</td>
632 <td><span class="check">Yes</span></td>
633 <td><span class="dash">&mdash;</span></td>
634 <td><span class="check">Yes</span></td>
635 <td><span class="dash">&mdash;</span></td>
636 </tr>
637 <tr>
638 <td>Free, no limits</td>
639 <td><span class="check">Yes</span></td>
640 <td>Freemium</td>
641 <td><span class="check">Yes</span></td>
642 <td>Self-host</td>
643 </tr>
644 <tr>
645 <td>Zero telemetry</td>
646 <td><span class="check">Yes</span></td>
647 <td><span class="dash">&mdash;</span></td>
648 <td><span class="check">Yes</span></td>
649 <td><span class="check">Yes</span></td>
650 </tr>
651 <tr>
652 <td>Full-text search</td>
653 <td><span class="check">Yes</span></td>
654 <td>Paid tier</td>
655 <td><span class="check">Yes</span></td>
656 <td><span class="check">Yes</span></td>
657 </tr>
658 <tr>
659 <td>Saved query feeds</td>
660 <td><span class="check">Yes</span></td>
661 <td>Paid tier</td>
662 <td><span class="dash">&mdash;</span></td>
663 <td><span class="check">Yes</span></td>
664 </tr>
665 <tr>
666 <td>Mobile app</td>
667 <td><span class="dash">&mdash;</span></td>
668 <td><span class="check">Yes</span></td>
669 <td><span class="check">Yes</span></td>
670 <td>Web only</td>
671 </tr>
672 </table>
673
674 <div class="highlight-box">
675 <p><strong>Free during alpha.</strong> Balanced Breakfast is a native desktop app. macOS ready today, Windows and Linux via Tauri. Source-available under PolyForm Noncommercial 1.0.0.</p>
676 </div>
677
678 <div class="footer-inline" style="margin-top: 16px;">
679 <div class="left">Balanced Breakfast &mdash; v0.3.1</div>
680 <div class="right">makenot.work</div>
681 </div>
682
683 </div>
684
685 </body>
686 </html>
687