Building Nritya Samarpanam - A Bharatanatyam Arangetram Website
Some projects are just work. And then there are the ones that mean something.
This past year my wife Anitha has been preparing for her Arangetram - the formal graduation recital in Bharatanatyam, a classical Indian dance form. Months of early mornings, late evenings, and a kind of dedication I have a deep respect for. When it came time to tell the world about the event, I wanted to do it right. Not just a PDF flyer or a WhatsApp message - a proper website, built with care.
The result is nrityasamarpanam.art - a site for my wife Anitha and her fellow dancer Sreyoshi, who are performing together on April 18, 2026 at the India Community Center in Macedon, NY.
Here is how I built it.
Starting From Scratch
The site is intentionally a static site - no frameworks, no build pipelines, no JavaScript bloat. Just HTML, CSS, and vanilla JS. For something like this, simplicity is the right call. It needs to load fast on every device, work offline after first load, and be easy to update without a deployment headache.
The design language is dark and elegant - deep near-black backgrounds, warm gold accents, and Cormorant Garamond for the display type. Classical dance deserves a classical aesthetic.
The Pages
The site has seven pages:
- Home - hero slideshow, countdown timer, meet-the-dancers cards, background music
- Anitha - biography, portrait, personal photo gallery
- Sreyoshi - biography, portrait, personal photo gallery with 20 photos
- Gallery - full combined gallery with filter tabs (All, Anitha, Sreyoshi, Both)
- Our Guru - profile of Guru Parvatha Chidambaram and Bharata School of Dance
- Event & RSVP - venue details, schedule, what to expect, RSVP link
- Guest Book - visitors can leave congratulations messages
Things I’m Proud Of
The Gallery
The gallery was one of the more interesting engineering problems. Photos are filtered by dancer using data attributes, and the “All Photos” tab shuffles the order randomly on every page load. Switch to “Anitha” or “Sreyoshi” and you get them in their natural numbered order.
The trickier part was making the lightbox (GLightbox) respect the active filter. By default, clicking a photo and hitting the arrow navigated through all photos regardless of which tab was active. The fix was to destroy and reinitialize the lightbox instance after each filter change, using a selector scoped to the active data-dancer attribute. Small detail. Big difference in experience.
The Guest Book
The Guest Book lets visitors leave congratulatory messages. Since it’s a public form, I built in two layers of protection against abuse:
- Honeypot field - a hidden input that real users never see but bots tend to fill in. Any submission with that field populated is silently discarded.
- Manual moderation - submissions go to Firebase Firestore without an
approvedfield. Only entries explicitly approved via the Firebase console (by me) are shown to the public. Visitors are told upfront that messages are reviewed before appearing.
It’s simple, effective, and requires no third-party moderation service.
Background Music
The home page plays a Carnatic music clip that loops between the 2 second and 55 second mark - the sweetest part of the piece. Getting this to work reliably across browsers, especially Safari on iOS, took more iterations than I expected.
The core issue: iOS Safari won’t allow audio.currentTime to be set until after play() resolves, not before. Setting currentTime = 2 synchronously before calling play() is silently ignored. The fix was to seek inside the .then() callback after the play promise resolves.
For the loop boundary, I use both a timeupdate listener (fires every ~250ms) and an ended fallback in case the boundary is missed.
The Photographer Credit
All photos were taken by Vikram Chandran (@revealtrumpet_media). I wanted to give him proper credit without it being disruptive - a subtle footer line on every page plus a more prominent placement above the gallery grid. Tasteful, present, and clickable.
The Stack
- HTML/CSS/JS - no framework
- Fonts - Cormorant Garamond (display), Cinzel (accents), Raleway (body)
- Animations - AOS (Animate On Scroll)
- Lightbox - GLightbox
- Guest Book backend - Firebase Firestore
- Analytics - Google Analytics (gtag)
- Hosting - GitHub Pages on a custom domain
What I Learned
A few things that surprised me along the way:
macOS is case-insensitive. Linux is not. A photo was named sreyoshi-home.JPG in git but referenced as .jpg in HTML. Works perfectly on macOS. 404 on GitHub Pages (Linux). The fix was to create an entirely new file with an unambiguous name rather than try to rename in git - which would have required force-pushing to fix the index.
CSS columns flows top-to-bottom, not left-to-right. The gallery originally used columns: 3 which gives a masonry layout but flows items down each column. For sequentially numbered photos, that meant photo 1, 4, 7 across the top row. Switching to CSS Grid (grid-template-columns: repeat(3, 1fr)) fixed the reading order to left-to-right.
pagehide fires after async callbacks complete, not before. When I tried to persist the music player state across page navigations using sessionStorage, the state was saving as false even when music was playing. The culprit: isPlaying was set in a promise .then() callback that hadn’t resolved yet when navigation happened. Switching to !audio.paused - which is set synchronously when play() is called - fixed it.
Signing This One
This site is credited as An AsynKrunner Creation - that’s me. It’s the first site I’ve built and shipped under that name, and it happens to be for something that matters a lot to my family. A good one to start with.
If you’re in the Upstate NY area on April 18, 2026, come watch. It will be beautiful.
Comments