Navya Sharma // personal site
← projects

Web · 2026

navyasharma.org — a personal site and a workshop of tools

This site: a zero-dependency PHP framework written by hand, hosting both the pages you are reading and seven private and public tools I use daily.

PHP 8.1MariaDBPDOvanilla JSCSS custom properties

This website. It runs on a PHP framework I wrote from nothing — no Composer packages, no npm, no build step — because I wanted to know exactly what every part of it does, and because it has to run on plain shared hosting.

That is a self-imposed constraint rather than a claim that frameworks are bad. Writing the router, the PDO wrapper, the autoloader, the session auth and the templating layer by hand is the fastest way I know to stop treating any of them as magic. A framework you have written is a framework you can debug at three in the morning.

RuntimePHP 8.1, MariaDB, plain shared hosting
DependenciesNone. No Composer, no npm, no bundler
Build stepNone — the files on disk are the files that run
Front endHand-written CSS and JavaScript, four themes
Also hostsSeven tools, public and private

Two things from one codebase

The site is what you are reading: projects, writing, poetry, research. None of it is hard-coded in a template — every word comes out of the database and is edited from an admin panel.

The tools host at /tools is a drop-in module system, and it is the part I actually use every day. Each tool is a self-contained folder with a manifest; the framework discovers it, decides whether it is public or private, and gives it a page. Some run entirely in the browser; some have their own database tables and server-side state.

Seven live here now: a calendar and tasks workspace, a personal finance ledger that tracks accounts, investments, debts and money loaned out, a physical-inventory tracker for finding which box a thing is in, a planner that drafts each day's schedule for me to approve, a multi-user driver for running DFT calculations across a lab of unreliable machines, an encrypted vault for files and passwords, and a word counter. Most are private.

They exist because I wanted them, which is the only justification a personal tool needs — but the arrangement has a second benefit I did not plan for. A tool is a real, demanding tenant. Every awkward edge in the framework underneath shows up within a week of building one on top of it, which is a much better test than any amount of admiring the architecture.

The CRUD engine

Adding a whole new type of content — this page's Experience section, for instance — takes three small steps and no new controller: a table, a model that names it, and an entry in a configuration file describing the fields. The admin panel reads that description and generates the list, the form, the uploads, the auto-slugging and the delete confirmation.

The configuration is genuinely a description rather than a script. A field says what it is — text, markdown, date, a select with these options, an image, a PDF — and the engine decides everything downstream from that: how it renders in the form, how it is validated, how it is stored, where an upload lands.

It is a small idea and it has paid for itself repeatedly. The generic path is the one that gets exercised constantly, so it is the one that is actually correct. A hand-written admin screen for one content type is a screen that is only tested when someone happens to use that content type; here, every content type shares a code path that runs whenever any of them is edited.

A renderer sized to the writing

The Markdown renderer is deliberately small — headings, emphasis, links, lists, code, block quotes, images, and tables. It is not CommonMark, and it is not trying to be. It supports exactly the things I write with, and grows only when a piece of writing actually needs something it does not have.

Tables arrived that way. The pages on the books here are full of small comparisons that want to be tables — a manuscript spelling beside the one that was set, three numbering sequences beside the ink each is printed in — and prose was the wrong shape for all of them. So the renderer learned pipe tables, with alignment, and with a header row it will drop entirely when the cells are empty, because half of these are two-column fact lists that want no header at all.

The same is true of the type. The site sets in Spectral and Fraunces, neither of which has a single Devanagari glyph, so a page of chaupais would have fallen through to whatever the reader's operating system happened to offer. The stack now names the booklet's own face as the fallback, which means the verse on this site is set in the face it was printed in.

Neither of those is a large change. Both are the renderer and the type following the writing, which is the right direction for that relationship to run.

Themes

The whole front end is driven by CSS custom properties, so a single attribute on the root element repaints everything. Four ship — Ink, Bone, Blueprint and Phosphor — and the choice is applied before first paint, so there is no flash of the wrong theme on reload. The admin panel and the tool chrome follow the same saved setting.

There is no per-theme stylesheet and no duplicated rules. Every colour in the site is a variable, which means adding a fifth theme is a block of six declarations rather than a project — and, more usefully, it means no component can hard-code a colour without immediately looking wrong in three of the four themes.

Written for machines too

The sitemap, the RSS feed and llms.txt are generated from the database rather than kept as static files, so publishing something puts it in all three with no second step to forget. This matters more than it sounds: every static equivalent of these files is a promise to update it by hand later, and that promise is broken within about two months on every site I have ever seen.

Every page carries a schema.org graph with exactly one definition of the person it belongs to, which each page then references by id rather than restating. One definition, referenced everywhere, is the same instinct as the theme variables — say a thing once, point at it from everywhere else, and it cannot drift.

The decisions behind it

  • Code is disposable; the database is the site. Every word of content lives in one database and nothing is baked into a template, so the code can be re-uploaded from anywhere at any time without touching a single published page. The split is deliberate, and it makes backing up the database the one operational habit that actually matters.
  • The private tools are built for one user on purpose. They assume that user is me, and that assumption is what lets them stay small and direct instead of carrying an account system nobody needs. A tool that ever needed a second user would get a proper multi-tenant model rather than an apology.
  • Nothing is added until the writing asks for it. The renderer, the themes and the tool host have all grown a piece at a time, in response to something I was actually trying to publish or use. That is the whole design philosophy of the site, and it is why there is no dependency list to maintain.