Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Figures for S4: How Does Encryption Protect Privacy? 5 Gaps in Your Notes."""
from atomic_viz import figure, banner, box, phone

GAPS = [("TRANSPORT", "HTTPS ends at the server"), ("KEYS", "The provider may hold them"), ("METADATA", "Dates and sizes stay visible"),
("ANALYTICS", "Tracking runs outside encryption"), ("RECOVERY", "A reset path is a key")]


def build():
out = {}

rows = "".join(f'<div style="display:grid;grid-template-columns:44px 1fr;gap:8px;padding:10px 0;border-top:1.5px solid #C6C6CB">'
f'<span style="font-family:Mono;font-weight:500;font-size:18px;color:#BA1A1A">{i + 1:02d}</span>'
f'<span><span style="font-family:Bebas;font-size:30px;line-height:1">{a}</span><br><span style="font-size:18px;color:#4A4D55">{b}</span></span></div>'
for i, (a, b) in enumerate(GAPS))
art = box(740, 90, 400, 470, f'<div class="lbl" style="margin-bottom:6px">ENCRYPTED, BUT...</div>{rows}', cls="sigshadow", style="padding:16px 22px")
out["01-banner"] = banner("SECURITY · PRIVACY", 'HOW DOES ENCRYPTION <span class="s">PROTECT PRIVACY?</span>',
"It locks content. Five gaps sit outside the lock.", art, title_size=86, text_width=640)

descs = ["The server unwraps your note on arrival.", "Server side keys mean the company can read.", "When, how much, how often: all readable.",
"SDKs report usage beside your notes.", "If they can reset you in, they can read in."]
body = ""
for i, ((t, _), d) in enumerate(zip(GAPS, descs)):
body += box(56 + i * 220, 220, 200, 330, f'<div class="num" style="font-size:52px;color:#BA1A1A">{i + 1:02d}</div><div class="ttl" style="font-size:34px">{t}</div><div class="txt muted" style="font-size:20px">{d}</div>',
cls="sigshadow" if i == 1 else "", style="padding:16px 16px")
out["02-five-gaps"] = figure("FIG 1 · THE GAPS", 'ENCRYPTED IS NOT <span class="s">THE SAME AS PRIVATE</span>', body,
"Each gap sits outside the cipher. A strong cipher doesn't close any of them.", h=650)

yes, no = '<span class="no">YES</span>', '<span class="yes">NO</span>'
data = [("ICLOUD NOTES", "Standard", "Apple", yes, "Apple can help"),
("ICLOUD NOTES", "With ADP", "Your devices", no, "Recovery key or contact"),
("GOOGLE KEEP", "Default", "Google", yes, "Google account"),
("ATOMIC NOTES", "T2T, default", "No note key", '<span class="part">IN TRANSIT</span>', "Google sign-in"),
("ATOMIC NOTES", "Vault on", "Your phone", no, "Six-word phrase only"),
("TYPICAL E2E APP", "Default", "Your devices", no, "Recovery key, if any")]
trs = "".join(f"<tr><th style=\"font-size:25px\">{a}</th><td>{b}</td><td>{c}</td><td>{d}</td><td>{e}</td></tr>" for a, b, c, d, e in data)
body = (f'<table class="d" style="left:56px;top:190px;width:1088px"><thead><tr><th>APP</th><th>SETUP</th><th>KEY HELD BY</th>'
f'<th>PROVIDER CAN READ</th><th>RECOVERY</th></tr></thead><tbody>{trs}</tbody></table>')
out["03-key-custody"] = figure("FIG 2 · KEY CUSTODY", 'SAME WORD, <span class="s">DIFFERENT KEY HOLDER</span>', body,
"From Apple, Google, and Atomic Notes documentation, October 9, 2026. Atomic T2T: your Drive holds readable files.", h=760)

items = ["When the note was created", "When it was last modified", "When it was last viewed", "Whether it's pinned",
"Whether it's marked as deleted", "Whether it has a drawing or handwriting", "A checksum of imported content"]
li = "".join(f'<div style="display:grid;grid-template-columns:46px 1fr;padding:9px 0;border-top:1.5px solid #C6C6CB">'
f'<span class="lbl" style="font-size:18px">{i + 1:02d}</span><span style="font-size:23px">{x}</span></div>' for i, x in enumerate(items))
body = box(56, 196, 640, 470, f'<div class="lbl">STAYS UNDER STANDARD PROTECTION</div><div class="ttl" style="font-size:40px;margin-bottom:8px">VISIBLE TO APPLE</div>{li}', cls="warn")
body += box(740, 196, 404, 470, '<div class="lbl">END-TO-END WITH ADP</div><div class="ttl" style="font-size:40px">HIDDEN</div>'
'<div class="txt" style="margin-top:14px">Note titles, text, and attachments.</div>'
'<div class="txt muted" style="margin-top:22px">Apple documents both lists. That openness is what every app should offer.</div>', cls="signal")
out["04-metadata-stays"] = figure("FIG 3 · METADATA", 'WHAT ADVANCED DATA PROTECTION <span class="s">STILL SHOWS</span>', body,
"Source: Apple's iCloud data security overview, checked October 9, 2026.", h=760)

qs = [("WHERE?", "In transit, at rest, or on your device?"), ("WHOSE KEY?", "Yours alone, or theirs too?"), ("WHAT'S VISIBLE?", "Is there a published metadata list?"),
("WHAT ELSE IS SENT?", "Analytics, crash reports, ads, AI?"), ("HOW DO I RECOVER?", "If they can reset you, they can read.")]
body = ""
for i, (q, d) in enumerate(qs):
x, yy = 56 + (i % 3) * 370, 196 + (i // 3) * 230
body += box(x, yy, 340, 200, f'<div class="num" style="font-size:44px">{i + 1:02d}</div><div class="ttl" style="font-size:36px">{q}</div><div class="txt muted" style="font-size:21px">{d}</div>',
cls="sigshadow" if i == 1 else "", style="padding:14px 20px")
out["05-five-questions"] = figure("FIG 4 · THE CHECK", 'FIVE QUESTIONS <span class="s">FOR ANY APP</span>', body,
"Vague answers usually mean the gap is real.", h=700)

body = phone(12, 64, 190, 260, crop=470, shadow="ink")
facts = [("WHERE", "HTTPS always. On the phone too, with the vault"), ("WHOSE KEY", "Vault: yours, from six words"),
("VISIBLE", "IDs, type, flags, times, rough size"), ("ALSO SENT", "Nothing. No analytics or trackers"),
("RECOVERY", "Your phrase only. No reset path"), ("HONEST LIMITS", "Vault off by default. No audit yet")]
rws = "".join(f'<div style="display:grid;grid-template-columns:210px 1fr;gap:14px;padding:11px 0;border-top:1.5px solid #C6C6CB">'
f'<span class="lbl" style="font-size:18px">{a}</span><span style="font-size:22px">{b}</span></div>' for a, b in facts)
body += box(400, 190, 744, 500, f'<div class="ttl big" style="margin-top:0">ATOMIC NOTES</div><div style="margin-top:10px">{rws}</div>', cls="sigshadow")
out["06-atomic-answers"] = figure("FIG 5 · OUR ANSWERS", 'ATOMIC NOTES, <span class="s">SAME FIVE QUESTIONS</span>', body,
"It's my app, so the bar is higher. Every answer is checkable in its public code.", h=760)
return out
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Figures for S3: Storing Passwords in Notes App, 5 Real Dangers."""
from atomic_viz import figure, banner, box, phone, arrow


def build():
out = {}

note = ("<div class=\"lbl\" style=\"margin-bottom:10px\">NOTE · LOGINS</div>"
+ "".join(f'<div style="font-family:Mono;font-size:19px;padding:7px 0;border-top:1.5px solid #C6C6CB">{a}</div>'
for a in ["email: ••••••••••", "bank: ••••••••", "wifi: ••••••••••", "phone carrier: •••••", "mother's maiden name: ••••", "netflix: same as bank"]))
art = box(740, 110, 400, 400, note, cls="warn", style="padding:18px 22px")
out["01-banner"] = banner("SECURITY · PASSWORDS", 'STORING PASSWORDS <span class="s">IN A NOTES APP</span>',
"Five real dangers of a note called logins, and how to move out in twenty minutes.", art, title_size=88, text_width=640)

# Blast radius.
body = box(56, 360, 240, 130, '<div class="lbl">ONE NOTE</div><div class="ttl" style="font-size:40px">"LOGINS"</div>', cls="warn")
body += box(410, 360, 240, 130, '<div class="lbl">THE MASTER KEY</div><div class="ttl" style="font-size:40px">EMAIL</div>', cls="ink")
targets = [("BANK", 200), ("SOCIAL", 330), ("CLOUD STORAGE", 460), ("SHOPPING", 590)]
for t, y in targets:
body += box(800, y - 10, 340, 96, f'<div class="lbl" style="font-size:16px">RESET BY EMAIL</div><div class="ttl" style="font-size:34px">{t}</div>', style="padding:12px 18px")
lines = arrow(300, 425, 402, 425, color="err", width=4.5)
for _, y in targets:
lines += arrow(654, 425, 792, y + 38, color="err")
out["02-blast-radius"] = figure("FIG 2 · THE BLAST RADIUS", 'ONE NOTE, <span class="s">EVERY ACCOUNT</span>', body,
"Most accounts reset through your inbox. Whoever reads the email password can reach the rest.", lines=lines, h=740)

y, n, p = '<span class="yes">YES</span>', '<span class="no">NO</span>', '<span class="part">SOMETIMES</span>'
rows = [("Encrypts each stored secret", p, y), ("Fills only on the matching site", n, y), ("Warns about breached passwords", n, y),
("Generates strong passwords", n, y), ("Flags reused passwords", n, y), ("Separate master password", p, y),
("Hidden from normal search", n, y)]
trs = "".join(f"<tr><td style=\"font-size:22px\">{a}</td><td>{b}</td><td class=\"hl\">{c}</td></tr>" for a, b, c in rows)
body = (f'<table class="d" style="left:56px;top:190px;width:1088px"><thead><tr><th>FEATURE</th><th style="width:260px">NOTES APP</th>'
f'<th class="hl" style="width:300px">PASSWORD MANAGER</th></tr></thead><tbody>{trs}</tbody></table>')
out["03-notes-vs-manager"] = figure("FIG 1 · TWO DIFFERENT TOOLS", 'A NOTE IS NOT <span class="s">A PASSWORD MANAGER</span>', body,
"Sometimes: a few notes apps can lock or encrypt a note, but none adds the other features.", h=740)

rows = [("GOOGLE KEEP", n, n, n, n), ("SAMSUNG NOTES", '<span class="yes">NOTE LOCK</span>', n, n, n),
("APPLE NOTES", '<span class="yes">NOTE LOCK</span>', '<span class="part">WITH ADP</span>', n, n),
("ATOMIC NOTES", '<span class="yes">APP LOCK</span>', '<span class="part">OPTIONAL VAULT</span>', n, n),
("PASSWORD MANAGER", y, y, y, y)]
trs = "".join(f"<tr><th style=\"font-size:26px\">{a}</th><td>{b}</td><td>{c}</td><td>{d}</td><td>{e}</td></tr>" for a, b, c, d, e in rows)
body = (f'<table class="d" style="left:56px;top:190px;width:1088px"><thead><tr><th>APP</th><th>LOCK</th><th>E2E SYNC</th>'
f'<th>BREACH ALERTS</th><th>SAFE AUTOFILL</th></tr></thead><tbody>{trs}</tbody></table>')
out["04-apps-compared"] = figure("FIG 3 · POPULAR APPS", 'IS YOUR NOTES APP <span class="s">SAFE FOR PASSWORDS?</span>', body,
"Checked October 9, 2026. ADP is Apple's Advanced Data Protection, off by default.", h=640)

steps = [("01", "PICK A MANAGER", "Protect it with a long passphrase used nowhere else."),
("02", "EMAIL FIRST", "Move it and change it. Email resets everything."),
("03", "MONEY AND PHONE", "Bank, card, and phone carrier logins next."),
("04", "TWO-STEP ON", "Authenticator app or passkey on those accounts."),
("05", "DELETE THE NOTE", "Then empty the notes app's trash.")]
body = ""
for i, (num, t, d) in enumerate(steps):
body += box(56 + i * 220, 220, 200, 320, f'<div class="num" style="font-size:52px">{num}</div><div class="ttl" style="font-size:32px">{t}</div><div class="txt muted" style="font-size:20px">{d}</div>',
cls="sigshadow" if i == 1 else "", style="padding:16px 16px")
out["05-move-out"] = figure("FIG 4 · MOVING OUT", 'TWENTY MINUTES, <span class="s">IN THIS ORDER</span>', body,
"Highest blast radius first. A deleted note often stays in the trash for 30 days.", h=620)

body = phone(12, 64, 190, 260, crop=470, shadow="ink")
facts = [("GOOD FOR", "Journals, health notes, plans, drafts"), ("PROTECTION", "App lock and an optional end-to-end vault"),
("NO AUTOFILL", "Can't stop you pasting into a fake site"), ("NO BREACH ALERTS", "Won't tell you a password leaked"),
("NO GENERATOR", "Can't create unique passwords"), ("VERDICT", "Not a password manager. Use one.")]
rws = "".join(f'<div style="display:grid;grid-template-columns:230px 1fr;gap:14px;padding:11px 0;border-top:1.5px solid #C6C6CB">'
f'<span class="lbl" style="font-size:18px">{a}</span><span style="font-size:22px">{b}</span></div>' for a, b in facts)
body += box(400, 190, 744, 500, f'<div class="ttl big" style="margin-top:0">ATOMIC NOTES, HONESTLY</div><div style="margin-top:10px">{rws}</div>', cls="sigshadow")
out["06-atomic-notes-card"] = figure("FIG 5 · RIGHT TOOL, RIGHT JOB", 'MY APP IS <span class="s">NOT A PASSWORD MANAGER</span>', body,
"I build Atomic Notes. I'd rather you used it for notes and a password manager for passwords.", h=760)
return out
73 changes: 73 additions & 0 deletions Atomic-Blog-Generation-Pipeline/visuals/posts/switch_notes_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Figures for S5: Switch Notes App Safely, 7 Checks Before You Move."""
from atomic_viz import figure, banner, box, phone, arrow

CHECKS = [("EXPORT FORMAT", "Markdown, HTML, or JSON travel well"), ("ATTACHMENTS", "Count them before you move"),
("ENCRYPTION", "Unlock on your own device to export"), ("RECOVERY", "Know the new app's way back in"),
("OFFLINE", "Open the new app in airplane mode"), ("STORAGE", "Who holds the synced copy?"),
("SHUTDOWN RISK", "How would you leave the new app?")]


def build():
out = {}

rows = "".join(f'<div style="display:grid;grid-template-columns:40px 1fr;gap:8px;padding:8px 0;border-top:1.5px solid #C6C6CB">'
f'<span style="font-family:Mono;font-weight:500;font-size:18px;color:#3A2FF0">{i + 1:02d}</span>'
f'<span style="font-family:Bebas;font-size:29px;line-height:1">{a}</span></div>' for i, (a, _) in enumerate(CHECKS))
art = box(740, 92, 400, 460, f'<div class="lbl" style="margin-bottom:6px">BEFORE YOU MOVE</div>{rows}', cls="sigshadow", style="padding:16px 22px")
out["01-banner"] = banner("MIGRATION · 2026", 'SWITCH NOTES APP <span class="s">SAFELY</span>',
"Seven checks, a rollback copy, and a two-week overlap. Nothing left behind.", art, title_size=96, text_width=640)

body = ""
for i, (t, d) in enumerate(CHECKS):
x, yy = 56 + (i % 4) * 275, 196 + (i // 4) * 230
body += box(x, yy, 255, 205, f'<div class="num" style="font-size:44px">{i + 1:02d}</div><div class="ttl" style="font-size:31px">{t}</div><div class="txt muted" style="font-size:19px">{d}</div>',
cls="sigshadow" if i == 0 else "", style="padding:14px 18px")
body += box(881, 426, 255, 205, '<div class="lbl">THEN</div><div class="ttl" style="font-size:31px">FOLLOW THE PLAN</div><div class="txt" style="font-size:19px">Export, freeze a copy, import, count, wait.</div>', cls="signal", style="padding:14px 18px")
out["02-seven-checks"] = figure("FIG 1 · THE CHECKS", 'SEVEN CHECKS <span class="s">BEFORE YOU MOVE</span>', body,
"Each one has cost someone their notes. Most take a minute.", h=700)

data = [("GOOGLE KEEP", "Google Takeout", "HTML and JSON", "Each note as two files"),
("APPLE NOTES", "Export as Markdown", "Markdown + images", "iOS 26 or macOS Tahoe. No folders"),
("EVERNOTE", "Desktop app only", "ENEX or HTML", "100 notes at a time, or notebooks"),
("NOTION", "Settings or page menu", "Markdown + CSV, HTML, PDF", "Databases arrive as CSV"),
("OBSIDIAN", "Nothing to do", "Markdown files", "Plugins' data may not travel"),
("ATOMIC NOTES", "No button yet", "JSON files in your Drive", "Vault notes stay encrypted")]
trs = "".join(f"<tr><th style=\"font-size:25px\">{a}</th><td>{b}</td><td>{c}</td><td>{d}</td></tr>" for a, b, c, d in data)
body = (f'<table class="d" style="left:56px;top:190px;width:1088px"><thead><tr><th>APP</th><th>HOW</th><th>YOU GET</th>'
f'<th>WATCH FOR</th></tr></thead><tbody>{trs}</tbody></table>')
out["03-export-formats"] = figure("FIG 2 · EXPORTS", 'HOW POPULAR APPS <span class="s">LET YOU LEAVE</span>', body,
"Checked against each app's help pages on October 9, 2026.", h=680)

steps = [("01", "EXPORT", "Everything, with attachments."), ("02", "FREEZE A COPY", "Zip it. Put it somewhere else. Don't touch it."),
("03", "IMPORT", "From a second copy, never the frozen one."), ("04", "COUNT", "Notes, checklists, attachments."),
("05", "OVERLAP", "Use both apps for two weeks."), ("06", "RETIRE", "Close the old account last.")]
body = ""
for i, (num, t, d) in enumerate(steps):
body += box(56 + i * 183, 230, 165, 300, f'<div class="num" style="font-size:46px">{num}</div><div class="ttl" style="font-size:30px">{t}</div><div class="txt muted" style="font-size:19px">{d}</div>',
cls="sigshadow" if i == 1 else "", style="padding:14px 14px")
out["04-migration-plan"] = figure("FIG 3 · THE PLAN", 'A MOVE WITH <span class="s">A WAY BACK</span>', body,
"Step 2 is the rollback copy. It exists so you never have to use it.", h=620)

marks = [(0, "DAY 0", "Export and freeze the rollback copy"), (1, "DAY 1", "Import, then count"),
(2, "DAYS 2 TO 14", "Use both apps. Search for old notes"), (3, "DAY 15", "Close the old account"),
(4, "ONE YEAR", "Keep the rollback copy until then")]
body = '<div style="position:absolute;left:156px;width:888px;top:250px;height:6px;background:#15171B"></div>'
for i, lbl, d in marks:
x = 56 + i * 222
cx = x + 100
body += f'<div style="position:absolute;left:{cx - 13}px;top:240px;width:26px;height:26px;border-radius:50%;background:{"#3A2FF0" if i in (0, 4) else "#15171B"};border:4px solid #F4F5F1"></div>'
body += box(x, 300, 200, 170, f'<div class="lbl">{lbl}</div><div class="txt" style="font-size:21px">{d}</div>',
cls="sigshadow" if i in (0, 4) else "", style="padding:14px 16px")
out["05-timeline"] = figure("FIG 4 · TIMING", 'TWO WEEKS OF OVERLAP, <span class="s">ONE YEAR OF BACKUP</span>', body,
"Yearly notes, like tax lists, won't show up in two weeks. The rollback copy covers them.", h=560)

body = phone(3, 64, 190, 260, crop=470, shadow="ink")
facts = [("WHERE NOTES LIVE", "One JSON file per note in your own Drive"), ("GETTING OUT", "Download the folder, convert with a script"),
("VAULT NOTES", "Stay encrypted. Only the app opens them"), ("GETTING IN", "No import tool yet. Paste by hand"),
("EXPORT BUTTON", "Not built yet. It's planned"), ("LOCK-IN", "None by format. The files are yours")]
rws = "".join(f'<div style="display:grid;grid-template-columns:230px 1fr;gap:14px;padding:11px 0;border-top:1.5px solid #C6C6CB">'
f'<span class="lbl" style="font-size:18px">{a}</span><span style="font-size:22px">{b}</span></div>' for a, b in facts)
body += box(400, 190, 744, 500, f'<div class="ttl big" style="margin-top:0">MOVING WITH ATOMIC NOTES</div><div style="margin-top:10px">{rws}</div>', cls="sigshadow")
out["06-atomic-notes-card"] = figure("FIG 5 · OUR APP, HONESTLY", 'IN AND OUT OF <span class="s">ATOMIC NOTES</span>', body,
"I build it. The way out works today with a script. A one-tap export is still to come.", h=760)
return out
Loading