Develop · Build
How it came together.
- 01
Three traditions, one synthesis layer
Each tradition has its own ontology - houses, palaces, pillars - and flattening them into one synthesis on top would lose what makes each useful. The synthesis layer holds all three in parallel and lets the reader weave between them in prose, without collapsing them into a single chart.
Product Strategy - 02
Daily transits as a separate cron
Transit forecasts run against the user's natal chart every twenty-four hours using
Backend Engineeringastronomy-engine, not at request time, because the calculation is expensive and the result is identical for every viewer of that user's day. Caching by user-day collapses the per-request cost to a database lookup and lets the forecast feed surface instantly when the user opens the app. - 03
Falsifiable predictions, logged
Predictions in the reading get tagged with a time window and an outcome category. When the window passes, the user marks the prediction hit, missed, or partial. The tracker holds the prediction reader accountable to their own forecasts, which is both the trust mechanic and the dataset that improves the next reading.
Product Strategy - 04
Per-user auth + Turso libSQL
Per-user auth wasn't optional here. Built on Next.js with jose-signed JWTs against Turso libSQL via Prisma - the database edge-replicates so transit calculations near the user stay fast. Migrations run through a custom script because the Prisma CLI doesn't speak Turso.
Backend Engineering
Develop · Forks
Decisions on the record.
The few calls worth defending. Each one is a fork; the other branch would have been a different project.
Decision · 01
Reading content straight from the Obsidian vault
Reading markdown lives in an Obsidian vault; the Obsidian app is the only editor, the web app only reads. A CMS would mean a second editor surface and a writeback path that risks overwriting the practitioner's notes - plain files avoid both.
Decision · 02
Why per-user access control from day one
Natal readings are intimate, so per-user access is a precondition, not a later feature. Each
natal_userreads only their own reading viagetAccessibleSlugs(user), built in from the first commit rather than retrofitted after a leak.Decision · 03
Why AI drafts, the practitioner ships
Every reading is AI-drafted from a curated corpus and human-reviewed before it reaches the user; the DB schema enforces the queue - questions queue, the practitioner answers, the user sees the answer. A pure-AI pipeline would scale faster, but it wouldn't be a practice.
Develop · System
The design system.
Tokens and treatments as they exist in the repo - values verbatim, nothing aspirational.
Gold on midnight, run as a real token system rather than a palette: a full opacity ramp off one gold value, an editorial serif for reading and a system sans for chrome, and the whole thing reachable through user-controlled accessibility overrides instead of a single fixed look.
Colour
--gold#C8A96E- Brand anchor - headings, active states, the orbital glyph, focus rings.
--midnight#07060D- Base background across the natal shell and marketing pages.
--cream#F3ECDD- Primary reading text on midnight; autofill fill colour.
--gold-faint / -glass / -border / -medium / -strong / -boldrgba(200,169,110, 0.06 -> 0.45)- A six-step opacity ramp off the one gold value, replacing ~94 hardcoded rgba(200,169,110,x) literals that had drifted across admin/settings/forms.
--rose-soft / --lilac-soft#E6B7A3 / #C5B6E6- Prediction/tarot/Ask verdict pastels for "false" and "mixed/uncertain", pulled out as tokens so PredictionsList, TarotForm, and AskForm can't drift apart.
--accent#3B82F6- Public marketing/auth shell only (login, signup, privacy) - the natal reading itself never uses blue.
Type
- Reading prose
AaBb Handgloves 0123
"Iowan Old Style", "Palatino Linotype", "Book Antiqua", Palatino, serifEditorial serif for the actual reading content inside .natal-shell; drop-caps the first paragraph after H1 unless it opens on a quote/strong/em, where the drop-cap would compete with the callout.
- UI chrome
AaBb Handgloves 0123
system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serifEverything outside the reading itself: nav, forms, buttons, admin.
Implementation
Plain CSS custom properties in app/globals.css (marketing/auth shell tokens) mirrored into app/natal/natal.css's .natal-shell (the reading surface's own token set, kept in sync by convention, not a build step). No Tailwind: the repo shipped unbuilt @tailwind at-rules for months with no tailwind.config or postcss plugin to expand them, so the utility classes were dead code. Removed 2026-07-25 along with the tailwindcss/clsx/tailwind-merge dependencies once that was caught.
Elements
- Drop-cap on the reading's opening paragraph
The first paragraph after an H1 gets a large serif drop-cap (::first-letter), sized down responsively at narrow viewports.
Suppressed when the paragraph opens on a quote, <strong>, or <em> - those already read as a callout, and a drop-cap on top of one competes for the same visual emphasis instead of adding to it.
- Radius scale
Four fixed steps - 4px small controls, 8px cards/panels/buttons, 12px large surfaces, a pill for chips/badges - one shared source instead of per-component values.
- Gold-on-midnight scrollbar
A gold gradient thumb (cream at the top fading to bronze) with a soft glow that intensifies on hover/active, covered on both Firefox (scrollbar-color) and WebKit (::-webkit-scrollbar-*). The reading view's thumb runs 12px instead of 10px so it reads as part of the editorial chrome, not browser furniture.
- "Continue with Google" button
Restyled to the gold-on-midnight register (pill shape, gold border/text) while leaving Google's official 4-colour G mark untouched.
Google's brand guidelines require the mark itself to stay unmodified even when everything around it is reskinned.
- Notification pill/chip (admin header)
Gold-tinted uppercase pill for the idle/enable state, a quieter outlined chip once granted, muted text for denied/unsupported - three states mapped to three visual weights instead of one button that just changes its label.
Process · 01
Killing Tailwind instead of finally configuring it
The fix wasn't adding the missing tailwind.config - the repo had already been style-complete for months on plain CSS custom properties, Tailwind or not. Configuring it now would mean re-deriving styles that already exist as tokens into utilities. Deleting the dead dependency was less work, and one less build step.
Process · 02
An opacity scale instead of ad hoc rgba() literals
The gold accent needed a dozen-odd translucency levels (hover glows, borders, chip fills) and they'd been hand-typed as rgba(200,169,110, <n>) at each call site, ~94 times over, with no guarantee two "the same" usages actually matched. Naming six fixed steps (faint/glass/border/medium/strong/bold) off the one gold value turns every one of those into a lookup, and turns a future rebrand into a one-line change.
Process · 03
Accessibility as data attributes, not a second theme
Text size, motion, and contrast preferences are read from the signed-in user's saved settings and applied as data-text-size / data-motion / data-contrast attributes on <html>, driving CSS custom-property overrides: --text-scale, a brightened --text-dim, a thickened --border. Scaling one root variable propagates through every rem/em measurement in the site. The alternative, a parallel high-contrast stylesheet, would drift from the primary one within a release or two.
