A digitized intellectual orbit — a private, botanically-illustrated vocabulary practice and highlights scrapbook, grown from your own Kindle reading.
node --version).npm i -g pnpm).vocab.db sqlite file.Everyone has their own intellectual orbit — the shape of thought inherited from family, schooling, the city of one's youth. To live is to widen that orbit. Reading is the humblest route: a book is a letter from a stranger who has lived what you have not. This garden is the digital form of the orbit — pressed, tended, revisited, and sometimes shared as a postcard.
There are three states a word can be in: seedling (never reviewed), in bloom (actively learning), pressed (21+ days of proven memory). The garden visualizes where each of your words is, and nudges you to return daily.
The home page — a cameo, a title, and a "Dearest Gentle Reader" letter in the spirit of the garden's aesthetic (edit the letter in src/pages/index.astro to your voice after cloning):
Daily practice (the words bed) — flip a botanical card to rate your memory; the word settles into its FSRS interval and eventually presses:
The garden — an isometric plot that grows over days, weeks, months, and years as you press words:
The scrapbook share flow — pick any highlight, turn it into a 1080×1350 social-ready PNG with your personal wax-seal stamp:
Kindle (usually at /Volumes/Kindle).system/ directory becomes visible.data/raw/:/Volumes/Kindle/system/vocabulary/vocab.db → required (powers the words bed + garden)/Volumes/Kindle/documents/My Clippings.txt → optional (powers the scrapbook tab)Nothing leaves your Mac. data/raw/ and public/covers/ are both gitignored. A pre-commit hook blocks any accidental git add -f on personal paths.
pnpm install # also creates src/site.config.ts (see below)
# copy your Kindle files per the walkthrough above
pnpm seed # process vocab.db + clippings
pnpm dev # Astro at http://localhost:4321
Edit src/site.config.ts with your name and title. pnpm install auto-copies this file from src/site.config.example.ts the first time, so you don't have to. Subsequent installs leave your edits alone. The file is gitignored so your values stay local — the template ships with only the example committed.
Optional personalization:
public/og-image.png (1200×630) for LinkedIn/Twitter/Facebook link previews.public/cameo.png with your 512×512 PNG, then run pnpm tsx scripts/gen-icons.ts to regenerate icon-192.png, icon-512.png, apple-touch-icon.png, and favicon.png in one shot.siteConfig.siteTitle at build time via src/pages/manifest.webmanifest.ts — no manual sync needed.When you share a passage from the Scrapbook tab, the generated card closes with a small wax-seal "stamp" in the bottom-right corner. This is the signature that says "from this reader's garden." The template ships with a generic seal — replace it with your own mark for a personal touch.
Out of the box: The template ships public/stamp-default.svg (a generic red-wax seal with a botanical flourish). If you don't customize anything, every share card you generate uses this seal. It works; it's just not yours.
To use your own:
public/, e.g. public/alice-stamp.png.src/site.config.ts → stampImage: "/alice-stamp.png".pnpm dev (or rebuild).That's it. The share modal probes the configured path at open time and uses it if present; missing files silently fall back to the default seal, so there's no broken-image failure mode.
Artwork ideas: a signet initial (hand-drawn), a personal sigil, a wax-seal generator output, a small illustrated icon that means something to you. Avoid anything rectangular — the card foot is styled for a roughly circular mark.
public/*-stamp.png is gitignored by default (see .gitignore) so your personal stamp doesn't accidentally upstream into a fork. If you want to commit a shared team stamp, use a different filename like public/brand-mark.png.
| Script | What it does |
|---|---|
pnpm seed |
Import vocab.db + clippings → data/processed/. Incremental. |
pnpm seed:refresh-covers |
Re-fetch book covers from Open Library even if cached. |
pnpm seed:refresh-definitions |
Re-fetch definitions (dictionaryapi.dev). |
pnpm seed:refresh |
Refresh both. |
pnpm seed:offline |
Skip all network; use only cached enrichments. |
pnpm reseed |
Seed + clear .astro cache (use after import changes). |
pnpm dev |
Astro dev server at http://localhost:4321. |
pnpm build |
Static build → dist/. |
pnpm preview |
Serve the built site locally. |
pnpm check |
Astro + Svelte type check. |
| Key | Action |
|---|---|
Space / Enter |
Flip the practice card |
1 |
Rate "didn't know" |
2 |
Rate "blurry, had to think" |
3 |
Rate "knew it instantly" |
Swipe ← / → (touch) |
Didn't know / knew it |
Full legend + more context lives on the /about page in the running app.
Rerun pnpm seed whenever you've added new highlights or vocabulary. The contract:
word_id, stored in your browser's localStorage, and never touched by the seed script.In short: your practice history lives in the browser; the seed script only rewrites data/processed/*.json and public/covers/. They don't talk to each other except at render time.
pnpm seed exits with "No vocab.db found" — copy it from /Volumes/Kindle/system/vocabulary/vocab.db per the walkthrough above.pnpm seed says "Seeded 0 words" — your Kindle's vocab.db is empty. Enable Vocabulary Builder on the device (Settings → Language & Dictionary → Vocabulary Builder → On), then look up a few words while reading. Then re-copy the db..astro/ (Astro's content cache) and restart dev: rm -rf .astro && pnpm dev. Or just pnpm reseed, which does both.pnpm install fails on better-sqlite3 — Node native module; check that your Node matches the project's (node --version should be ≥ 20) and that you're on Apple Silicon (arm64). If you switched Nodes, pnpm install --force rebuilds.Local-first by default. Nothing is uploaded, tracked, or synced. Definitions and covers are fetched once at seed time and bundled into the built site.
data/raw/, data/processed/, public/covers/, My Clippings.sdr/, and book_cover/ are all gitignored. The pre-commit hook (scripts/block-private.sh) blocks any accidental git add -f on those paths.
Do not deploy this to a public URL. Even just public/covers/ — which is your own cover cache — reveals your reading history via filenames. If you want always-on access from your phone, see the next section for the password-gated private-deploy path.
If you want to browse your garden from your phone, the simplest path is a private Vercel deploy:
pnpm add -D vercel
vercel login
vercel link # one-time: creates .vercel/, gitignored
Before the first deploy, in the Vercel dashboard → Project Settings → Deployment Protection, enable Vercel Authentication (free on Hobby). This gates your site behind Vercel's login — no public URL.
Also before the first deploy, make sure git config --local user.email matches an email verified on your GitHub account (see https://github.com/settings/emails). Vercel blocks deploys from commits whose author email isn't GitHub-verified — the default Mac-generated [email protected] trips this check. If you've already committed with the wrong email, rewrite history:
git config --local user.email "[email protected]"
git config --local user.name "Your Name"
git filter-branch -f --env-filter '
export GIT_AUTHOR_NAME="Your Name"
export GIT_AUTHOR_EMAIL="[email protected]"
export GIT_COMMITTER_NAME="Your Name"
export GIT_COMMITTER_EMAIL="[email protected]"
' -- --all
git push -f origin main
(Force-push is safe on a brand-new repo with no collaborators. Skip this step if your commits were already authored correctly.)
pnpm deploy:prod # seed → build → vercel build → vercel deploy --prebuilt
(NB: the script is deploy:prod, not deploy — the latter is a pnpm built-in that does something unrelated.)
Why deploys are CLI-only, not git-triggered. The repo ships with vercel.json setting git.deploymentEnabled.main: false. Reason: a remote git-triggered build would try to run pnpm seed, which needs data/raw/vocab.db (your personal Kindle file, gitignored). Seeding would fail on Vercel with no vocab.db. So we only deploy via the CLI's prebuilt path — seed + build locally, upload the static output. If you want git-triggered deploys (e.g. after migrating to a setup that doesn't need local Kindle data), delete the git.deploymentEnabled block from vercel.json.
Three warnings you must not skip:
literary-garden-ab12cd-...), NOT the short aliases it auto-creates (your-project.vercel.app, your-project-<scope>.vercel.app, your-project-git-main-<scope>.vercel.app). Those serve publicly. Find them with pnpm vercel alias ls | grep your-project and remove each with pnpm vercel alias rm <alias> --yes. Then verify with curl -I <alias> — you want a 404, not a 200.If you need shared-password access (a friend who doesn't have a Vercel account), switch to Cloudflare Pages + Access — their free tier allows magic-link email policies and up to 50 users. The pnpm build output is the same; only the deploy step changes.
scripts/gen-icons.ts for PWA icons)