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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ Here's how Atomic Notes splits those jobs:
2. **A sync server coordinates, and keeps metadata only.** It tracks note IDs, timestamps, flags, and versions so your devices agree. It never stores your note titles or text.
3. **Your own Google Drive holds the cloud copy.** Every note lands in a My-Atomic-Notes folder as its own `.atomic` file. The app uses Google's narrow `drive.file` scope, so it can only see files it created.

![A phone showing the Atomic Notes storage report, beside three cards: your phone holds the real copy, the sync server keeps metadata only, and your Google Drive holds one file per note. An optional vault keeps cloud copies as ciphertext.](05-atomic-notes-layers.png "Fig 4. Atomic Notes: phone first, Google Drive second, a metadata-only server in between.")
![A phone showing the Atomic Notes Cloud Notes screen, beside three cards: your phone holds the real copy, the sync server keeps metadata only, and your Google Drive holds one file per note. An optional vault keeps cloud copies as ciphertext.](05-atomic-notes-layers.png "Fig 4. Atomic Notes: phone first, Google Drive second, a metadata-only server in between.")

One honest detail. Unless you turn on the optional vault, note text is plain JSON in your Drive and passes through the sync server on its way there, without being stored. Turn the vault on and your phone encrypts every note with AES-256-GCM before upload, so the server and Google only ever hold ciphertext.

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ To find the best offline notes app Android owners will actually keep, every entr
- **Latest:** 2.03.5, September 28, 2026
- **Weak spot:** the first sign-in needs internet, the free plan holds 30 notes, and there's no export button yet

![The Atomic Notes notes database screen on a phone showing 8 notes on device and 8 in cloud, beside a fact card about offline saving, sync later, conflict copies, and honest limits.](06-atomic-notes-card.png "Fig 3. Atomic Notes, disclosed: it's my app, and the offline behavior is in its public code.")
![The Atomic Notes Cloud Notes screen on a phone showing 23 notes on the device and 23 in the cloud, beside a fact card about offline saving, sync later, conflict copies, and honest limits.](06-atomic-notes-card.png "Fig 3. Atomic Notes, disclosed: it's my app, and the offline behavior is in its public code.")

When the network comes back, Atomic Notes retries a failed sync after 5, 15, and 45 seconds, then waits for the next trigger instead of draining the battery. If an offline edit collides with one from another device, the app keeps both and names one "(conflict copy)". That's why it's my pick for the best offline notes app Android users can run on two phones.

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion Atomic-Blog-Generation-Pipeline/visuals/atomic_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ def file_url(path):


def mockup(n):
return file_url(os.path.join(PUBLIC, "mockups", f"atomic-notes-mockup-image-{n:02d}.png"))
"""An app screen from public/mockups: a name like "encryption" (release 2.03.5) or a legacy number."""
name = f"app-{n}.png" if isinstance(n, str) else f"atomic-notes-mockup-image-{n:02d}.png"
return file_url(os.path.join(PUBLIC, "mockups", name))


ATOM = file_url(os.path.join(PUBLIC, "icons", "atom.svg"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def group(x, lbl, title, apps, note, cls):
out["05-backup-plan"] = figure("FIG 4 · BACKUPS", 'THE PHONE MAY HOLD <span class="s">THE ONLY COPY</span>', body,
"Ten minutes a month. Apps with sync cover a lost phone, not a deleted note.", lines=lines, h=680)

body = phone(7, 64, 190, 260, crop=470, shadow="ink")
body = phone("cloud-notes", 64, 190, 260, crop=470, shadow="ink")
facts = [("OFFLINE", "Write, edit, search, delete. No connection needed"), ("SAVES", "To the phone as you type"),
("SYNC LATER", "To your own Google Drive, on its own"), ("RETRIES", "After 5, 15, and 45 seconds, then waits"),
("CONFLICTS", "Both versions kept, one marked conflict copy"), ("HONEST LIMITS", "Sign-in needs internet once. 30 free notes")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def build():
out["05-permissions"] = figure("FIG 4 · PERMISSIONS", 'WHO CAN EVEN <span class="s">REACH THE INTERNET?</span>', body,
"From each app's AndroidManifest.xml or F-Droid listing. No internet permission means notes can't leave by the app itself.", h=790)

body = phone(12, 64, 190, 260, crop=470, shadow="ink")
body = phone("encryption", 64, 190, 260, crop=470, shadow="ink")
facts = [("WHERE NOTES LIVE", "Your phone first, then your own Google Drive"), ("ENCRYPTION", "Optional end-to-end vault, off by default"),
("SERVER", "Metadata only, never note titles or text"), ("NO", "Ads, trackers, or AI"),
("PRICE", "Free, 30 notes, daily sync energy"), ("HONEST LIMITS", "Google sign-in required. No export button yet")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def build():
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")
body = phone("encryption", 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")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def build():
routine = [("LOCKS", "Screen lock, app lock, and no-screenshots still on"), ("SHARES", "Revoke links you no longer need"),
("EXPORTS", "Delete or encrypt stray copies"), ("ACCOUNT", "Unique password and two-step verification"),
("UPDATES", "Read release notes for new sharing or AI features")]
body = phone(9, 64, 190, 260, crop=470, shadow="ink")
body = phone("security", 64, 190, 260, crop=470, shadow="ink")
rws = "".join(f'<div style="display:grid;grid-template-columns:44px 170px 1fr;gap:10px;padding:12px 0;border-top:1.5px solid #C6C6CB">'
f'<span class="lbl" style="font-size:18px">{i + 1:02d}</span><span class="lbl" style="font-size:18px;color:#15171B">{a}</span><span style="font-size:22px">{b}</span></div>'
for i, (a, b) in enumerate(routine))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def build():
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")
body = phone("encryption", 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.")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def lane(y, label, steps, note, sig):
"Source: \"Local-first software\", Ink & Switch, 2019.", h=740)

# How Atomic Notes applies it.
body = phone(7, 64, 196, 250, crop=470, shadow="ink")
body = phone("cloud-notes", 64, 196, 250, crop=470, shadow="ink")
body += box(390, 190, 360, 196,
'<div class="lbl">1 · YOUR PHONE</div><div class="ttl">THE REAL COPY</div><div class="txt" style="font-size:21px">Hive storage on the device. Every note saves as you type, online or not.</div>',
cls="sigshadow")
Expand Down
2 changes: 1 addition & 1 deletion content/blog/best-offline-notes-apps-android.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ To find the best offline notes app Android owners will actually keep, every entr
- **Latest:** 2.03.5, September 28, 2026
- **Weak spot:** the first sign-in needs internet, the free plan holds 30 notes, and there's no export button yet

![The Atomic Notes notes database screen on a phone showing 8 notes on device and 8 in cloud, beside a fact card about offline saving, sync later, conflict copies, and honest limits.](/blog/best-offline-notes-apps-android/06-atomic-notes-card.png "Fig 3. Atomic Notes, disclosed: it's my app, and the offline behavior is in its public code.")
![The Atomic Notes Cloud Notes screen on a phone showing 23 notes on the device and 23 in the cloud, beside a fact card about offline saving, sync later, conflict copies, and honest limits.](/blog/best-offline-notes-apps-android/06-atomic-notes-card.png "Fig 3. Atomic Notes, disclosed: it's my app, and the offline behavior is in its public code.")

When the network comes back, Atomic Notes retries a failed sync after 5, 15, and 45 seconds, then waits for the next trigger instead of draining the battery. If an offline edit collides with one from another device, the app keeps both and names one "(conflict copy)". That's why it's my pick for the best offline notes app Android users can run on two phones.

Expand Down
2 changes: 1 addition & 1 deletion content/blog/what-is-a-local-first-notes-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ Here's how Atomic Notes splits those jobs:
2. **A sync server coordinates, and keeps metadata only.** It tracks note IDs, timestamps, flags, and versions so your devices agree. It never stores your note titles or text.
3. **Your own Google Drive holds the cloud copy.** Every note lands in a My-Atomic-Notes folder as its own `.atomic` file. The app uses Google's narrow `drive.file` scope, so it can only see files it created.

![A phone showing the Atomic Notes storage report, beside three cards: your phone holds the real copy, the sync server keeps metadata only, and your Google Drive holds one file per note. An optional vault keeps cloud copies as ciphertext.](/blog/what-is-a-local-first-notes-app/05-atomic-notes-layers.png "Fig 4. Atomic Notes: phone first, Google Drive second, a metadata-only server in between.")
![A phone showing the Atomic Notes Cloud Notes screen, beside three cards: your phone holds the real copy, the sync server keeps metadata only, and your Google Drive holds one file per note. An optional vault keeps cloud copies as ciphertext.](/blog/what-is-a-local-first-notes-app/05-atomic-notes-layers.png "Fig 4. Atomic Notes: phone first, Google Drive second, a metadata-only server in between.")

One honest detail. Unless you turn on the optional vault, note text is plain JSON in your Drive and passes through the sync server on its way there, without being stored. Turn the vault on and your phone encrypts every note with AES-256-GCM before upload, so the server and Google only ever hold ciphertext.

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/blog/encrypted-notes-privacy/06-atomic-answers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/blog/notes-app-privacy-mistakes/06-routine.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mockups/app-atomic-energy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mockups/app-avatar-picker.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mockups/app-cloud-notes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mockups/app-cloud-sync.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mockups/app-danger-zone.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mockups/app-encryption.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mockups/app-energy-activity.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mockups/app-profile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mockups/app-security.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mockups/app-settings-about.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mockups/app-settings-energy-popup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/mockups/app-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/mockups/atomic-notes-mockup-image-01.png
Binary file not shown.
Binary file removed public/mockups/atomic-notes-mockup-image-02.png
Diff not rendered.
Binary file removed public/mockups/atomic-notes-mockup-image-07.png
Diff not rendered.
Binary file removed public/mockups/atomic-notes-mockup-image-08.png
Diff not rendered.
Binary file removed public/mockups/atomic-notes-mockup-image-09.png
Diff not rendered.
Binary file removed public/mockups/atomic-notes-mockup-image-10.png
Diff not rendered.
Binary file removed public/mockups/atomic-notes-mockup-image-11.png
Diff not rendered.
Binary file removed public/mockups/atomic-notes-mockup-image-12.png
Diff not rendered.
Binary file removed public/mockups/atomic-notes-mockup-image-13.png
Diff not rendered.
Binary file removed public/mockups/atomic-notes-mockup-image-14.png
Diff not rendered.
Binary file removed public/mockups/atomic-notes-mockup-image-16.png
Diff not rendered.
Binary file removed public/mockups/atomic-notes-mockup-image-19.png
Diff not rendered.
Binary file added public/video/atomic-notes-demo-1280x720.mp4
Binary file not shown.
Binary file added public/video/atomic-notes-demo-720x1280.mp4
Binary file not shown.
31 changes: 31 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,10 @@ section h2 {
.shot img {
width: 100%;
display: block;
/* Screens come from two builds with different heights. Show every card at the 2.03.5 shape, top-aligned. */
aspect-ratio: 1080 / 2199;
object-fit: cover;
object-position: top;
}
.shot .cap {
display: flex;
Expand Down Expand Up @@ -3685,3 +3689,30 @@ section h2 {
position: static;
}
}

/* ---------- demo video ---------- */
.lp-demo {
margin: 28px 0 34px;
}
.lp-demo video {
display: block;
width: 100%;
height: auto;
border: 2px solid var(--ink);
border-radius: 8px;
box-shadow: 8px 8px 0 var(--signal);
background: var(--ink);
}
.lp-demo-tall {
display: none !important;
}
@media (max-width: 640px) {
.lp-demo-wide {
display: none !important;
}
.lp-demo-tall {
display: block !important;
max-width: 360px;
margin: 0 auto;
}
}
74 changes: 58 additions & 16 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,32 @@ import { APK_URL, DEVELOPER_URL, SITE_URL } from "@/lib/site";

export const dynamic = "force-dynamic";

// Screens from earlier builds (notes, editors, coins, notifications) that have no 2.03.5 mockup yet.
const M = (n: string) => `/mockups/atomic-notes-mockup-image-${n}.png`;
// Release 2.03.5 screens, cut from the iPhone 18 Pro mockups without the device frame (the app is Android-only).
const A = (name: string) => `/mockups/app-${name}.png`;
const A_H = 2199;

// The screen tour. Image 17 (a recovery phrase) is left out on purpose.
// The screen tour. The recovery phrase screen is left out on purpose.
const SHOTS: Shot[] = [
{ src: M("03"), label: "Notes" },
{ src: M("05"), label: "Editor · Note" },
{ src: M("04"), label: "Editor · Checklist" },
{ src: M("06"), label: "Select & Delete" },
{ src: M("12"), label: "Encryption vault" },
{ src: M("14"), label: "Atomic Energy" },
{ src: A("settings"), label: "Settings" },
{ src: A("profile"), label: "Profile" },
{ src: A("avatar-picker"), label: "Choose an avatar" },
{ src: A("encryption"), label: "Encryption vault" },
{ src: A("security"), label: "Lock and no screenshots" },
{ src: A("atomic-energy"), label: "Atomic Energy" },
{ src: A("energy-activity"), label: "Energy activity" },
{ src: A("settings-energy-popup"), label: "Energy popup" },
{ src: M("15"), label: "Convert Coins" },
{ src: M("13"), label: "Energy popup" },
{ src: A("cloud-sync"), label: "Cloud Sync" },
{ src: A("cloud-notes"), label: "Device and cloud" },
{ src: M("18"), label: "Notifications" },
{ src: M("09"), label: "Biometric lock" },
{ src: M("08"), label: "Cloud Sync" },
{ src: M("07"), label: "Device and cloud" },
{ src: M("02"), label: "Settings" },
{ src: M("01"), label: "Profile" },
{ src: M("10"), label: "Danger Zone" },
{ src: A("danger-zone"), label: "Danger Zone" },
{ src: A("settings-about"), label: "About" },
];

async function latestUpdates(): Promise<NotificationRow[]> {
Expand Down Expand Up @@ -74,7 +81,7 @@ function jsonLd() {
isAccessibleForFree: true,
offers: { "@type": "Offer", price: "0", priceCurrency: "USD" },
image: `${SITE_URL}/og-banner.png`,
screenshot: [`${SITE_URL}${M("03")}`, `${SITE_URL}${M("12")}`, `${SITE_URL}${M("14")}`],
screenshot: [`${SITE_URL}${M("03")}`, `${SITE_URL}${A("encryption")}`, `${SITE_URL}${A("atomic-energy")}`],
featureList: [
"Local-first notes and checklists that work offline",
"Sync to a folder in your own Google Drive",
Expand Down Expand Up @@ -268,9 +275,9 @@ export default async function Home() {
</ul>
</div>
<div className="lp-stack" aria-hidden="true">
<Phone src={M("12")} alt="" shadow="ink" className="lp-p1" eager />
<Phone src={A("encryption")} alt="" shadow="ink" className="lp-p1" height={A_H} eager />
<Phone src={M("03")} alt="" className="lp-p2" eager />
<Phone src={M("14")} alt="" shadow="ink" className="lp-p3" eager />
<Phone src={A("atomic-energy")} alt="" shadow="ink" className="lp-p3" height={A_H} eager />
</div>
</div>
</section>
Expand Down Expand Up @@ -449,8 +456,13 @@ export default async function Home() {
</p>
</div>
<div className="lp-showcase-phones">
<Phone src={M("12")} alt="Atomic Notes encryption screen showing the vault is on" />
<Phone src={M("09")} alt="Atomic Notes security screen with the biometric lock switch" shadow="ink" />
<Phone src={A("encryption")} alt="Atomic Notes encryption screen showing the vault is on" height={A_H} />
<Phone
src={A("security")}
alt="Atomic Notes security screen with biometric unlock and no-screenshots mode both on"
shadow="ink"
height={A_H}
/>
</div>
</Reveal>

Expand Down Expand Up @@ -618,7 +630,37 @@ export default async function Home() {
</h2>
<p className="lp-lead">
The Technical Editorial design system: ink #15171B on paper #F4F5F1 with one accent, Signal #3A2FF0.
Tap any screen to zoom. Some screens are from the August 2026 build.
Tap any screen to zoom. Settings, vault, energy, sync, and Danger Zone screens are from release 2.03.5.
The notes, editor, coin, and notification screens are from the August 2026 build.
</p>
</Reveal>
<Reveal className="lp-demo">
<video
className="lp-demo-wide"
controls
playsInline
preload="none"
poster="/video/atomic-notes-demo-poster-1280x720.jpg"
width={1280}
height={720}
>
<source src="/video/atomic-notes-demo-1280x720.mp4" type="video/mp4" />
</video>
<video
className="lp-demo-tall"
controls
playsInline
preload="none"
poster="/video/atomic-notes-demo-poster-720x1280.jpg"
width={720}
height={1280}
>
<source src="/video/atomic-notes-demo-720x1280.mp4" type="video/mp4" />
</video>
<p className="lp-small">
A 30-second tour in a phone mockup. The home and editor screens in it preview the redesign in progress,
with search, tags, and text formatting that aren&apos;t in release 2.03.5 yet. Everything else matches
the current app.
</p>
</Reveal>
<Gallery shots={SHOTS} />
Expand Down
5 changes: 4 additions & 1 deletion src/components/Phone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ export function Phone({
shadow = "signal",
className = "",
eager = false,
height = 2460,
}: {
src: string;
alt: string;
shadow?: "signal" | "ink";
className?: string;
eager?: boolean;
/** Pixel height of the screenshot at 1080 px wide. Release 2.03.5 screens are 2199. */
height?: number;
}) {
return (
<div className={`phone phone-${shadow} ${className}`}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={src} alt={alt} width={1080} height={2460} loading={eager ? "eager" : "lazy"} />
<img src={src} alt={alt} width={1080} height={height} loading={eager ? "eager" : "lazy"} />
</div>
);
}