This website started with a name, a degree, and a blank page. The first commit landed on July 2, 2016. Its intended content was only “Samar Sajnani” and “Honours Bioinformatics.” The file had been exported through Cocoa HTML Writer, so the browser displayed the HTML as text instead of interpreting it. It was an imperfect first commit, but it established something that has stayed true ever since: this site would be a place where I could experiment in public.
The first portfolio
The corrected 2016 version was a traditional personal site. It used full-screen photographs as fixed backgrounds and divided the page into large sections for my résumé, projects, photography, dance, GitHub profile, and contact information. The layout was direct: HTML described the sections and one CSS file positioned six large panels over the images.
There was no framework, build step, or content system. Updating the portfolio meant changing the page by hand. That was enough while the site was mainly a digital introduction, but it did not yet feel personal in the way I wanted.
The turning point came in 2018, when I started a separate WebGL experiment inside the repository. The prototype used Three.js to create a perspective camera, 10,000 randomly positioned stars, ray-cast hover interactions, and two groups of five bright spheres. Their coordinates traced two opposing curves: the beginnings of the two S shapes for Samar Sajnani.
Hovering a small star scaled it up. It was a modest interaction, but it changed the design problem. The website no longer had to be a set of rectangles. The navigation itself could become a scene.
Turning the signature into a universe
In June 2019, the WebGL experiment became the main website. Each point in the two S-shaped constellations became a destination. Text geometry labelled the stars. Selecting one moved the camera into its local system, where planets orbited around the selected point.
That version introduced most of the visual language that still defines the site:
- Separate Three.js scenes for stars, signature lines, labels, orbit paths, planets, and background effects.
- Shader-driven stars and bloom post-processing to make the constellations glow without washing out every element.
- Raycasting to connect mouse movement and clicks to objects in three-dimensional space.
- Camera tweens and a warp-speed transition between the signature and an individual star system.
- Planet textures that turn projects, schools, research, and other content into objects rather than menu rows.
Keeping those concerns in separate scenes was useful. A destination label can fade without hiding its star. Orbit paths can appear only after the camera reaches a system. Bloom can be applied selectively. The renderer composes the layers, while each layer remains independently controllable.
The two signatures are therefore more than decoration. Their points are the information architecture. One S collects professional systems such as Projects, Education, Research, and Work. The other contains ProgRamen, Management, System Designs, Sajora, and this Blog.
The original data layer
The 2019 site tried to make the universe respond to work happening elsewhere. Repositories whose GitHub descriptions contained ((Project)) became project planets. Education and résumé information came from JSON files. Research papers came from a separate repository. Earlier versions also fetched YouTube, Twitter, Instagram, and Spotify data, with a small Heroku service handling APIs that needed authentication.
That approach made the portfolio feel alive, but it coupled the homepage to many unrelated services. A slow or retired API could prevent a system from appearing. Synchronous requests and a growing collection of callbacks also made the rendering lifecycle harder to reason about.
The current version keeps the useful part of that idea—content should update from its source—while reducing the number of moving parts.
The architecture today
The site now has three layers: a publishing layer, a content layer, and an experience layer.
The publishing layer is a small Node.js build in scripts/build.mjs. It reads Markdown front matter with gray-matter, renders the body with markdown-it, and writes a disposable _site directory. The output contains collection pages, article pages, a content index, the Three.js homepage, and the assets needed by GitHub Pages.
The content layer combines several kinds of sources:
- Markdown files hold articles, management writing, system designs, research notes, and curated project pages.
- Local JSON keeps résumé and education history reliable and available without a network request.
- The GitHub loader discovers personal projects and every public ProgRamen repository at runtime.
- A repository can carry a
portfolio.jsonmanifest beside its code to define its title, artwork, browser preview, and download. - A publish-time Spotify sync turns the public Sajora artist catalog into music planets without storing an API credential.
The experience layer remains the custom Three.js engine. It loads content-index.json alongside the live GitHub collections, creates the two signatures, and attaches zero to five planets to each destination. A planet keeps the information needed for its detail panel, preview, and final link. The newest three Blog entries become planets automatically.
The overall path looks like this:
Markdown + local JSON + GitHub manifests + Spotify catalog
↓
Node build
↓
static pages + content-index.json
↙ ↘
reading website Three.js universe
↘ ↙
GitHub Pages
Why keep a static site?
GitHub Pages is a good constraint for a personal site. The published result is a directory of files that can be cached, inspected, and served without maintaining an application server. Articles still have normal URLs and readable HTML. JavaScript adds the universe, live repositories, transitions, and previews, but it does not have to generate the writing itself.
The split also gives failures a boundary. If GitHub rate-limits a request, the reading pages, résumé, blog, and local collections continue to work. If Spotify cannot be reached during a publish, the last successful music catalog remains available. Dynamic sources improve the site without owning the whole page.
Why keep the original Three.js engine?
Rebuilding everything with a conventional portfolio template would be easier. It would also remove the part that makes this website mine.
The older code has real history in it: coordinates adjusted by eye, signature fonts, camera offsets, bloom values, orbit geometry, and years of fixes for different screen shapes. The current work treats that engine as a product with an identity. I can improve its loading, labels, mobile behavior, and data boundaries while preserving the signature, warp drive, planets, and sense of space.
That decision creates a useful architectural rule: content should not need to know how Three.js renders it. An article provides a title, summary, date, image, and URL. The build creates the page and index entry. The universe decides where its planet belongs. The same article remains usable in the Blog archive, through a direct link, or from inside the constellation.
The blog closes the loop
For years, the Blog star existed as an idea with no publishing system behind it. It now represents the three newest published articles. Writing a post means adding one Markdown file, setting draft: false, and running the build. Draft and future-dated work stays out of the output, and removed articles cannot linger because _site is recreated from scratch.
That makes this article a record and a test of the architecture it describes. It was written in the repository, compiled into a normal article page, added to the Blog archive, and turned into a planet in the same build.
The site began as a place to list what I had done. It has gradually become one of the things I am doing: a long-running experiment in graphics, interaction, system boundaries, and how a personal body of work can remain alive without becoming fragile.