Personal page — hand-written HTML, CSS and vanilla Web Components, no build step. Served straight from this repository.
index.html work.html talks.html research.html resume.html life.html
styles.css design tokens + all page styles
script.js shared behaviour (carousels, share links, shortcuts)
resume.js PDF.js résumé viewer
components/site-nav.js <site-nav> sidebar navigation component
components/talk-map.js <talk-map> dotted world map of talk locations
Every page authors its nav as a plain list of links inside <site-nav>:
<site-nav brand="profradha.tech" tagline="honest pursuits">
<a href="/" data-icon="home">Home</a>
...
</site-nav>Those links work on their own with JavaScript disabled. When the component
upgrades, it renders them as a sticky rail on desktop (icon, label, and a
description that fades in on hover) and as a blurred full-screen sheet on
mobile, marking the current page from window.location. Adding or reordering a
page means editing that list — no CSS or JS changes.
data-icon picks a Lucide glyph inlined in the component (home, briefcase,
mic, flask, file, heart).
Plain black and white. Pure black surfaces, white ink, a neutral grey ramp in between, and no hue anywhere — hierarchy is carried by brightness and opacity alone. The structure is an homage to marko.tech: sticky sidebar, dashed hairlines, lowercase chrome. Type is Space Grotesk headings over DM Sans body copy, with JetBrains Mono kept for technical metadata (years, tags, eyebrows, captions).
Because nothing is colour-coded, hover and keyboard focus are signalled by a
white glow. Four --glow-* tokens drive it: --glow-sm for small chrome
(icon buttons, chips, thumbnails), --glow for cards and social buttons,
--glow-lg for the loudest targets (primary buttons, carousel arrows, video
play badges), and --glow-text as a text-shadow for links and nav rows,
where a box shadow would trace the wrong shape. Every interactive element gets
exactly one of them, on :hover and on :focus-visible, so keyboard users see
the same affordance as pointer users.
Headings, cards and photographs arrive out of the background as you scroll to
them — a short fade up and a 16px rise, which on a black page is literally a
fade up out of the dark. setupScrollReveal() in script.js stands an item
down by setting data-reveal-item on it and brings it in with .is-revealed;
both come back off once it has landed. Only items that start below the fold are
ever touched, so the first screenful is never animated, nothing is hidden under
prefers-reduced-motion, and with JavaScript off every page is plainly visible.
Which items take part is the selector list at the top of that function — the
grids, the carousels, the maps — never anything nested inside them.
All tokens live at the top of styles.css; nothing hard-codes a colour or a
font stack. Full attribution for the structural source and for every open
source dependency is in CREDITS.md.
python3 -m http.server 8000Then open http://localhost:8000/index.html. Internal links use extensionless
paths (/work), which the production host resolves; when previewing locally,
navigate to work.html directly.