A printed scientific treatise, rendered as a website. Numbered plates, figure captions, marginal annotations and a colophon, because that is the native publishing register of medical imaging.
Built with Astro 7, MDX, Tailwind 4 and TypeScript (strict). Zero client-side framework; every interactive piece is a small vanilla script.
npm install
npm run dev # http://localhost:4321
npm run verify # build + a11y audit + contrast check + typecheck| Route | What it is |
|---|---|
/ |
The scroll — eleven numbered chapters, condensed |
/works/[slug] |
One research plate, full chapter (4 entries) |
/elementa |
Index of the teaching corpus |
/elementa/[slug] |
One proposition |
/elementa/figures |
The reusable figure library, with copy-SVG |
/imagines |
Photographs, with captions and optional notes |
/papers |
Published papers and manuscripts, with links |
/tabulae |
The plate gallery — artwork, each with a note |
/tabulae/[slug] |
One plate, with its historical background |
/marginalia |
The commonplace book — reviews and notes |
/marginalia/[slug] |
One entry |
/vitae |
Printable CV (@media print styles included) |
/admin |
Content manager — write and publish from the browser |
Three of the four plates hold unpublished results. src/content.config.ts
carries a superRefine that fails the build if a plate marked
disclosure: embargoed also carries metrics.
This is deliberate. It puts the embargo in the data rather than in anyone's
memory. Do not soften it to a warning, and do not remove it. To confirm it still
works, add a metric to any embargoed plate and run npm run build — the build
must fail with Plate N is embargoed and must not carry metrics.
Everything you publish routinely is edited at /admin in the browser. No
files, no terminal.
Sveltia CMS commits MDX straight into this repository, so what you edit is what the site builds from — there is no separate database, and the whole history is in git. Saving triggers a Cloudflare Pages rebuild; the change is live in a minute or two.
What you can edit there:
| Section | What |
|---|---|
| Marginalia | Book reviews, model reviews, essays, notes — with image upload |
| Elementa | Propositions, including the Given dependency links |
| Plates | Research plates — add, edit and delete |
| Axioms | The five maxims and their marginal cites |
| Imagines | Photographs — upload, caption, optional note |
| Papers | Published papers and manuscripts — add one, paste the link |
| Education | Degrees, shown on the Vitae page and in the CV |
| Engineering projects | Shown in the Appendix and on the CV |
| Tabulae | Artwork — upload an image and write its history |
| Elementa books | The books propositions are filed under — add your own |
| Instrumentarium | The skill groups on the home page |
| Instrumenta propria | Architectures and losses written from scratch |
| Site text | The Prologue, the colophon, the frontispiece epigraph and role line |
Images have two destinations, on purpose:
- Pictures inside a review go to
public/uploads/and are referenced as/uploads/<name>. Framed like a plate by.prose img. - Artwork in the Tabulae collection goes to
src/assets/plates/with a path relative to the entry, so Astro optimises it into a responsive WebP set. That is what the collection-specificmedia_folderinconfig.ymlis for; do not "simplify" it to an absolute path or the optimisation stops.
The four research entries in works are Plates I–IV. The artwork is numbered
too, so calling both "plate" collided. The artwork is tabulae — the term
engraved atlases use — and lives at /tabulae.
Each entry carries a treatment: intaglio for line art, which prints onto
the page with a blend mode so the paper white becomes vellum, or photograph
for objects, which are toned instead. Choosing intaglio for a photograph
will render it nearly black.
Two numbers, doing different jobs. Plate order is the gallery sequence and sets the Tabula numeral. Position on the home page is where it falls on the front page: 1 sits beside the name, 2 next, and so on.
To put a plate between two others, use a half. 3.5 lands it straight after
3 and pushes everything below down. Typing a number another plate already
holds is not an error, but the plate already there stays in front — the half
is what actually inserts.
npm run tidy:plates renumbers everything back to 1, 2, 3… so the number you
type matches the position you see. Worth running after hiding a few plates,
which otherwise leaves holes: hide four and the plate showing fourth might
still be stored as 8.
Two options; the CMS offers both on its login screen.
- Access token — no infrastructure. In GitHub, create a fine-grained personal access token scoped to this repository only, with Contents: read and write. Paste it into Sign In Using Access Token. Stored in that browser only.
- Sign in with GitHub — a real button. Deploy the OAuth broker in
worker/, register a GitHub OAuth App, then uncommentbase_urlinpublic/admin/config.yml. Setup is in the deployment guide.
local_backend: true is also set, so running npm run dev and opening
/admin lets you edit the files on disk directly with no login at all.
Astro's glob loader fixes its file list when the dev server boots and never
rescans, so a newly created entry would otherwise stay invisible until a
restart. npm run dev therefore runs scripts/dev-auto.mjs, which watches
src/content/ and restarts the server only when the set of files changes.
| Action | What happens |
|---|---|
| Edit an existing entry | Hot-reloads. No restart — Astro already handles it. |
| Add a new entry | Restart, automatically. Visible in ~5s. |
| Delete an entry | Restart, automatically. Gone in ~2s. |
npm run dev:plain is the unwrapped astro dev if you ever want it.
In production none of this applies. The CMS commits to GitHub, Cloudflare
runs npm run build from a fresh checkout, and a cold build has no stale file
list. Verified by cloning the repo and building from scratch. If you ever
suspect a stale content cache, npx astro build --force clears it.
public/cv/Md-Asif-Uddin-CV.pdf is built by scripts/build-cv.mjs before
every astro build, reading the same content collections the site renders —
education, works, papers, instrumentarium, projects, and the CV summary under
Site text. Edit anything in /admin and the next deploy ships a matching CV.
It is gitignored, because it is an artefact rather than source.
Two deliberate choices in it:
- No phone number. Your CV has one; this file is served publicly, which is
a different exposure from a CV handed to a person. Add
PHONEtosrc/consts.tsand the masthead line in the script if you want it. - No results. Research entries carry the title and what the work does. Metrics live on the plate, where the caveats are next to them.
Three levels: Book → Chapter → Proposition, at
/elementa/<book>/<chapter>/<proposition>. A proposition names its chapter
and the book follows from that, so the two can never disagree about where it
lives. Both are typed references, so pointing at something that does not
exist fails the build rather than rendering a dead link.
Write in that order: book, then chapter, then propositions. A chapter with no propositions still renders — it shows its topic list, which is the plan for what will be written there.
given is also a reference, and the "used by" list on each proposition is
derived from it. Record a dependency once and both directions appear.
Add a figure. Figures are hand-written SVG diagrams, not uploads; a new
one needs a developer. The Figure dropdown lists what is registered in
src/lib/figures.ts. Artwork is different — that is Tabulae, and you can add
as much of it as you like.
That is the only one. Everything else — plates, tabulae, books, marginalia,
propositions, the home-page sections — can be added and deleted from /admin.
The Compendium on the home page has no collection of its own — it renders the Plates. Add or edit a plate and its card appears there, on the Vitae page and in the CV. The same holds for the Chronicle, which renders Education, and the Appendix, which renders Engineering projects.
Every section of the home page is CMS-backed. Nothing on it needs a code change any more.
Sveltia has no permission system. "Disabled by the administrator" is a
create: false flag in config.yml, not a role check. Whoever can sign in
has full write access to the repository, which is why the token should be
fine-grained and scoped to this repo alone.
The build refused it. That is the guard below doing its job, and Cloudflare keeps serving the last good version in the meantime — the site never breaks. Check the Pages build log for the reason.
All colour, type scale and rhythm live in src/styles/tokens.css. No hex
codes anywhere else in the codebase. Modes switch on data-theme="nocturne".
Three contrast corrections were required to clear the quality floor, and are
documented in the header of tokens.css: --fundus was darkened in Vellum, and
--brass-text / --oxblood-text exist as text-safe partners to --brass and
--oxblood (which stay verbatim for rules, borders and marks). Run
npm run contrast after touching any colour.
Everything that moves lives in src/styles/motion.css, driven by
src/scripts/motion.ts. Two rules matter:
- Every hiding rule is scoped to
html.js, a class set by the inline head script. With no JS the page renders fully visible instead of blank. - Above-the-fold content uses
data-intro, a pure-CSS animation. It must never wait on the IntersectionObserver — doing so held the hero atopacity: 0until the module booted and cost ~2.6s of Largest Contentful Paint.
Everything is inside @media (prefers-reduced-motion: no-preference).
Nine public-domain works (CC0) from the Art Institute of Chicago, in
src/assets/plates/, with attribution in src/lib/plates.json. Engravings are
printed onto the page with a blend mode (intaglio); photographed objects get a
duotone instead. Re-fetch with npm run plates.
| Command | Does |
|---|---|
npm run verify |
build → audit → contrast → typecheck |
npm run audit |
heading order, alt text, landmarks, self-hosting |
npm run contrast |
WCAG ratios for every token pair, both themes |
npm run check:cms |
Parses config.yml; checks folders and relations resolve |
npm run cv |
Regenerates the CV PDF on its own |
npm run plates |
re-download the artwork |
npm run og |
regenerate public/og.png |
Marked [SUPPLY] in the content files:
- The four paper abstracts, verbatim.
- Citations and paper URLs, once published.
- Repository URLs.
- Elementa propositions beyond the three Book I seeds.
All of the above are editable at /admin — none of it needs a code change.
The three marginalia entries are seeds written to demonstrate the format —
edit or replace them from /admin.
Set the real domain in src/consts.ts (SITE) before deploying; it drives
canonical URLs, the sitemap and Open Graph tags.