A portfolio you fly through. Built with Astro + TypeScript — all content is statically rendered HTML (SEO-friendly, works without JS), and the flight experience is a small vanilla-TS runtime with zero framework JavaScript.
| Command | Action |
|---|---|
pnpm install |
Install dependencies |
pnpm dev |
Dev server at localhost:4321 |
pnpm build |
Production build to ./dist/ |
pnpm preview |
Preview the production build locally |
pnpm check |
Typecheck .astro + .ts files |
src/
data/portfolio.ts ← EDIT ME: all content (timeline, eras, notes, links, SEO text)
lib/blog.ts ← build-time RSS fetch from blogs.ijlalahmad.dev
styles/global.css ← all styling
layouts/Base.astro ← <head>: meta tags, Open Graph, JSON-LD, fonts
pages/index.astro ← the single page
components/
World.astro ← the 3D flight path, server-rendered (incl. card satellites,
blog postcards, Mission Control shell)
Overlays.astro ← HUD, rail, dock, rocket co-pilot, robot, train, satellite
FlatMode.astro ← the "boring mode" plain version
scripts/
main.ts ← camera + magnetic snap, input, animation loop
era.ts ← era color blending
sky.ts ← canvas sky + particles
setpieces.ts ← drawing canvas, terminal, castle, ?-block
github.ts ← live GitHub API → orbital repo system
src/data/*.json is the only data layer. Workers write it, the app only
renders it; astro build needs no network and the browser calls no APIs.
| file | source | written by |
|---|---|---|
blog.json |
Hashnode RSS | pnpm sync:data / daily Action |
github.json |
GitHub API (story+repos) | pnpm sync:data / daily Action |
leetcode.json |
LeetCode GraphQL | pnpm sync:data / daily Action |
movies.json |
Notion database | pnpm sync:notion / daily Action |
sketches.json |
content/ originals |
pnpm sketches (manual, on new art) |
games.json, anime.json |
hand/agent-edited JSON | you (or an AI agent) |
portfolio.ts |
site copy & timeline | code — edited like code |
Every worker is stale-safe: a failed or empty source keeps the committed JSON, so the site always builds from the last good snapshot.
- Journey Log — a transit-map scene with three lines: GitHub milestones
(account created, first repo, orgs, today's totals — fetched at build
time), blog milestones (from RSS), and LinkedIn milestones (hand-edited in
portfolio.ts, the API is closed). - Mission Control — repos are fetched client-side from the GitHub API (unauthenticated, cached in sessionStorage for 30 min) and rendered as an orbital system: planet size = stars, color = language. Fails soft to a plain GitHub link if the API is unreachable.
- Field Reports — the latest three posts are fetched from the blog's RSS feed at build time and server-rendered as airmail postcards (plus listed in flat mode). A dead feed never breaks the build.
Notion is the editing console; the site never calls it at runtime. The worker
scripts/sync-notion.mjs (locally: pnpm sync:notion, in CI: the daily
refresh data action) pulls the Notion databases into src/data/*.json —
the committed JSON stays authoritative if Notion is unreachable or empty.
Set NOTION_TOKEN + NOTION_MOVIES_DB in .env (see .env.example), and
as repository secrets once the repo is on GitHub (plus GH_STORY_TOKEN for
the journey's org data — Actions reserves the name GITHUB_TOKEN).
Org memberships are private to the public API. Copy .env.example to .env
and set GH_PAT to a personal access token with the read:org scope — the
Journey Log's GitHub line will then include your org stations. In CI the same
value is the GH_STORY_TOKEN secret, passed to the worker as GH_PAT
(Actions reserves the name GITHUB_TOKEN for its own auto-injected token).
The token is read only by scripts/sync-data.mjs; it never reaches
astro build or the client bundle. Re-run pnpm sync:data after publishing
posts or joining orgs to refresh the committed JSON.
- Domain: set
siteinastro.config.mjsand the Sitemap URL inpublic/robots.txtto your real domain (powers canonical/OG/sitemap URLs). - Email: replace
hello@example.cominsrc/data/portfolio.ts(SITE.email). - OG image: add a 1200×630
public/og.pngand uncomment theog:imagetag insrc/layouts/Base.astrofor rich link previews.
- Campus photo: "Centenary Gate, Jamia Millia Islamia" by Muntaqibah, Wikimedia Commons, CC BY-SA 4.0 (resized).
- S&P Global logo: Wikimedia Commons, public-domain text logo; GUS logo from globaluniversitysystems.com — both shown to identify former/current employers (nominative use).
- Fonts are self-hosted variable fonts (
@fontsource-variable/*) — no render-blocking Google Fonts round trips. - CSS is inlined into the single HTML file (
inlineStylesheets: 'always'). - The timeline, skills and bio are real HTML at build time — crawlable, and
shown via
<noscript>/reduced-motion fallbacks as the plain version. - Sitemap (
@astrojs/sitemap), canonical URL, Open Graph, Twitter card and JSON-LDPersonschema are generated insrc/layouts/Base.astro.