Skip to content
AFM Studio
  • Pages

    AFM Studio
  • Work
  • Lab
  • About
  • CV
  • Contact
  • Projects

    AksaraA searchable library for Otorita IKN's knowledge, where the server checks every reader's access across three levels
  • Daily TaqwaTamper-resistant prayer attendance system for ~2,000 OIKN government employees at Masjid Negara IKN
  • Lantara v2A permit engine for Otorita IKN where administrators add new permit types from a screen, without a developer
  • eKiosk IKNTwo control rooms for Nusantara: kiosk content management and live bus tracking with arrival times that grade themselves
  • E-Monev IKNA monitoring platform for Nusantara's 23-year build, tracking every unit's quarterly progress against 24 national KPIs
  • JDIH OIKNOIKN's public legal document portal, with a controlled publishing workflow and AI summaries that never leave the server
  • Jejak PenanamanA check-in app for IKN tree-planting events that proves each participant was on site with a geofenced selfie
  • Dashboard SAKTIA nightly pipeline and dashboard that replaces OIKN's manual budget downloads for all six of its budget units
  • DoseRxA bedside dose calculator for 127 drugs that rounds each dose to tablets and syrups Indonesian pharmacies sell
  • CubiqA speedcubing trainer with competition-grade timing and a solver I wrote for each of the 8 major puzzles
  • ScimotionAn interactive science library of 86 articles, each explained next to animations the reader can control
  • WA Daily Scrum BotA WhatsApp bot that chases missing standup reports for a 14-person team and flags tasks stuck for days

↑ ↓ to move · Enter to open · Esc to close

Language Lineage

A branching map of how language families split over history, where every disputed classification shows both sides' evidence

Screenshots
language families covered, root to present
18
turning points, each citing a source
602
disputed classifications shown with evidence for both sides
77
historical figures indexed by turning point
67
On this page

The problem

Most readers meet a language family as a settled fact. West Germanic simply counts as separate from Proto-Germanic, and nobody explains why some scholars still reject "Altaic" as a family at all. That context exists in academic sources: Glottolog, sound-law catalogs, individual monographs. But it's written for other linguists, scattered across specialist literature no curious reader can just follow.

I built Language Lineage for people with no linguistics background: anyone curious about where their language's ancestors came from. Historical-linguistics classification doesn't resolve the way an open math problem might; more research won't settle whether "Altaic" is a family. So the site shows live disagreement, not a single tidied-up tree: who argues what, and how settled each claim is.

What I built

A branch can descend from more than one ancestor

Contact-driven change, such as creoles, sprachbunds and areal diffusion, can give a branch more than one ancestor. I modeled that as a DAG from the first commit: each branch stores a list of parent ids, and the layout algorithm was built from day one to handle more than one parent, not patched in later.

Every disputed classification carries both sides' evidence

Macro-family disputes such as Altaic, Nostratic and Austric are live scholarly disagreements, not solved-but-unpublished problems, so picking one side would misrepresent the field. Each disputed grouping is its own record: a status (widely accepted, minority position, largely rejected), the evidence for it, and the evidence against it, drawn on the tree with a dashed border and a ring-count glyph.

I hand-wrote the branch layout instead of reaching for a graph library

Branches can have more than one parent, so a plain linear timeline wasn't enough, but each family's tree stays small, up to a dozen branches, which didn't justify a general graph-layout dependency. The layout computes each branch's layer, then orders and refines it to keep the tree legible, drawn as the growth rings I wanted. I came back and adjusted it more than once as family trees grew wider than the original pilot.

Content lives as JSON files in the repo, with no database or backend

Every family and branch is one JSON file, so a correction is a single-file pull request. It's the same model I use across my other static reference sites, no admin panel, no login. The whole site builds to static HTML with no server to run and no accounts to secure.

The build itself catches data errors before they ship

With 165 hand-authored branch files, a bad id or a citation issue would otherwise only surface as a broken page after it deployed. The validator rejects an unknown parent, a cycle in the branch graph, and, added later, any turning point dated after every source cited for it. A broken commit can't reach production.

Result

Language Lineage is live. It maps 18 language families from proto-language to present, with 602 sourced turning points, 77 disputed classifications each showing both sides' evidence, and 67 historical figures indexed by the events they're tied to. I built it solo over five days in September 2026, writing every family's content myself, then wiring the world map and sound-law comparison views on top of the same data.

Under the hoodTechnical detail for engineers
  • Next.js 14, static export (next build → out/); no server runtime in production.
  • src/lib/treeLayout.ts is a manual layered DAG layout, drawn by src/components/LineageTree.tsx with D3 7.9 and a growth/reveal animation; it handles branches with more than one parent, and both chain and fan layouts (a flat fan when no well-supported intermediate subgroup exists, e.g. Austroasiatic, Pama-Nyungan).
  • Branch, TurningPoint (typed SPLIT / SOUND_LAW / CONTACT / WRITING_SYSTEM_ADOPTED / EXTINCTION / REVITALIZATION), ContestedClassification and Figure are defined in src/lib/types.ts; content is read at build time from JSON via src/lib/content.ts, with no database or runtime data layer.
  • Sound-law turning points carry extra comparison metadata (process, notation, sort_year) so /sound-laws can plot every sound law from all 18 families on one shared timeline and matrix.
  • src/lib/worldMap.ts builds a per-country choropleth from topojson-client and world-atlas, driven by per-branch speaker/country data; a UNESCO-style vitality indicator renders per language via src/components/Vitality.tsx.
  • IPA transcriptions and reconstructed forms render as real text in Gentium Plus, not images, so they stay selectable and searchable.
  • Sample phrases set their text direction automatically, so Arabic, Hebrew and Syriac samples render right to left; the heading font falls back to Source Serif 4 for extended-Latin diacritics it renders poorly.
  • scripts/validate-content.mjs runs before every build and fails it on an unknown parent, cross-branch id collisions, cycles, and any turning point dated later than every source cited for it.
  • Deploys via GitHub Actions on push to main: builds with the base path set by actions/configure-pages, then publishes via actions/upload-pages-artifact and actions/deploy-pages.
  • No database and no test framework anywhere in the project; tsc --noEmit is the only other automated check besides content validation.
  • 329 commits over 5 days, September 23 to 27, 2026. 3,941 lines of TypeScript and TSX across 6 route templates and 21 components, plus 18,441 lines of JSON content.

Screenshots

Beranda
Language Families
Language Family Tree
Language Family Detail
Language Family On The Map
Sound Laws

Hiring, or building something similar?

I'm open to full-time roles and select freelance work.