My last post was all about how “The Odyssey of Gaurav” came to life visually – the styling choices, why I stuck with pure CSS, and those tiny design details. With most of the site’s look sorted (though, let’s be honest, there’s always something to tweak!), it was time for the next big step: adding data, interactivity, and those delightful little touches that make a website truly come alive.
This chapter covers everything from custom forms and analytics to playful animations and the final polish that turned my static site into a dynamic digital home.
Feedback & Newsletter: Past to the Rescue
After getting the initial styling done, I went back to the homepage to tackle the feedback form. I explored a bunch of options, but most either had trial limits, required paid plans, or meant embedding a generic Google Form, which would totally destroy my custom styling. At this point, I wasn’t running a full-blown database, knowing my audience would start small. Plus, I had that “subscribe to the newsletter” option which also needed a one-way data capture.
Then it hit me: I’d previously worked on an IoT project where I logged data from an ESP32 to Google Sheets. This use case was no different! If I could log feedback and newsletter sign-ups directly into a spreadsheet, I could easily manage everything and even automate emails later.
Enter the hero: Google App Script (GAS). With some docs and a little help from AI, I quickly wrote a GAS script to get user responses in Google Sheets. The best part? I had full control over the UI. Unlike a simple HTML form submit that would cause a redirection to display a response, I could use JavaScript to do cool things like show proper success messages, loading animations, error states, and even a little extra surprise…
What’s the surprise? Well, you’ll have to give me feedback to find out! (Just kidding… mostly.)
I love learning and genuinely value feedback, appreciation, or suggestions. So, I wanted to greet users with something special for their effort to contact me. Inspired by Josh W. Comeau’s site, I added a confetti animation to the submit button. Every time you send feedback, if the request is successful, you’ll see a burst of confetti!
Luckily, this wasn’t too much work, thanks to the awesome canvas-confetti project. Seriously, check out their GitHub page for tons of cool demos!
This made the form exactly how I wanted it – a lot of fun to build.
You might want to consider adding basic protection like IP checking or honeypots to avoid spam requests, though I didn’t go deep into that for this initial setup, but I have some deployed.
With the form complete, my homepage felt truly finished: sorted tags, top posts, and a way for people to connect. Confetti everywhere!
Tracking Engagement: Views, Likes and Analytics
Next, I moved to the blog post pages. I realized adding a view counter would be super useful. Why? Well, many popular blogs have them – it helps readers gauge popular content, and gives me quick stats (though as a developer, I have other ways to verify traffic, of course! for simplicity, I also use Vercel’s integrations like Speed Insights and Analytics.)
Similarly, I knew I had to add a like button at the end of each blog post. I wanted to know: did you just read it, or did you like it? Who wouldn’t want to get as many hearts as possible?
This seemed easy at first, but it brought me back to the database problem. Unlike a form, which is a one-way log, here I needed to fetch data (the current count) and update it. You definitely don’t want outdated counts!
I knew some providers offer generous free tiers for hobbyists and integrate well with hosting platforms like Vercel (which I’m using). So, the process felt smooth. I chose Supabase. There’s a Vercel integration, so it was just a matter of installing the necessary packages. I created a simple table in the database to store like_count and view_count, using the blog post’s unique route as the key. Astro’s docs also have good guide for Supabase integration, which made setup quick. Configuring environment variables felt a bit tricky at first, but the docs always help!
This process required creating two simple APIs: one for likes and one for views – nothing too fancy, just basic requests and responses.
-
Views: I managed views simply by session storage. One view per session. I didn’t want to track repeated tab refreshes, but if someone genuinely revisited the site later, I’d count that. This method isn’t foolproof (closing a tab and reopening might increase the count), but I wasn’t keen on tracking IPs or other personal data, and I also wanted to keep things simple; we’re here for learning and fun!
-
Likes: Likes, however, couldn’t be managed by session. You wouldn’t want your like to disappear if you close the tab and reopen it! Also, I wanted them to be authentic. So, the like button uses local storage. Once you like a post, it’s saved locally, preventing you from liking it again (though technically you could manipulate local storage, why would you?).
You can click the button below to see the animation. This won’t actually add a like to my database, it’s just for demonstration! (To try again just refresh the page.)
Again, the magic of AI, SVG, animation, and canvas-confetti came into play here, similar to the homepage watermelon. The original like button (which you’ll find at the bottom of blog posts) currently doesn’t have an “unlike” option. I deliberately kept it this way to save on database requests and prevent users from repeatedly clicking to scrutinize the visuals. To compensate for this decision, I added a small animation that plays when you again click the like button.
With the like and view components ready, I decided against putting view counts on blog overview cards (too many requests on one page). Instead, views are displayed only on the actual blog post page. The like button, naturally, sits with other Calls to Action (CTAs) like the feedback link, author name, publication date, and social share options.
After this, I did some house-cleaning and fixed visual glitches, along with functional tweaks like ensuring proper prerenderer settings for server functions (likes/views).
The “About Me” Page: Simplicity Wins
The “About Me” page was always saved for last. I initially had grand plans: using Matter.js for physics-based elements, or interactive tiles for hobbies and tech skills. But as I started playing with it, it felt a little glitchy and the user experience wasn’t clear. What content would go on those tiles? I decided to drop those complex ideas completely. Since i created that pyramid (combined two-three demos from the docs) you can checkout below.
This is not fully tested. But yes you can definitely play with the elements. I had a lot of fun with it, but it didn’t fit the current site vibe and met testing time.
So, I simply framed a paragraph from my heart, added some basic personal lines, and a contact form. That’s it. Simple, personal, and to the point.
Final Polish: Sounds, Transitions & Markdown Reborn
With the main sections complete, I felt ambitious and even tried adding sounds to the site. It was a lot of fun to learn about! But paired with animations, it quickly became overwhelming – my mom even said it felt like “too much.” Plus, adding sound properly would require a global toggle (Good practice), selecting copyright-free audio, and dealing with browser limitations (you can’t play sound on hover until a user’s first interaction, like a click). While cool to experiment with, it wasn’t right for the current version of the site.
I also focused on performance. By default, Astro handles a lot of optimizations, but I used Lighthouse reports to make further improvements, especially with images (proper formats, sizes).
While auditing, I realized images are very messy to handle and sometimes tricky too. My site has functionality to display images in various places (like within blog posts or for postcard images), but sometimes I opted not to add them. For instance, when it came time to add an image to a blog post or an Open Graph (OG) image for sharing, I felt blank. What image to add? Where to get it from? What about relevance, ownership, etc.? The obvious choice became AI-generated images, but that didn’t feel authentic, and I felt it might dilute the blog’s overall taste. So, I decided to mostly opt out of AI-generated images. I realized the OG image I created for the /blog route could suffice for most shared links. Since the functionality is there, I can always add a specific image if I find a perfect fit, but it’s a visual preference that doesn’t sacrifice core functionality. Don’t be surprised if you see some images in some blogs, as the functionality is there if I find a perfect fit.
For page transitions, I initially used JavaScript for an artificial delay to create smooth navigations. This caused inconsistencies, likely due to heavy browser caching. I looked into libraries like swup.js, astro-transitions API but sometimes, the simplest solution is best. I ended up with a pure CSS fix that works perfectly:
/* Main content fade-in animation */
main {
opacity: 0;
animation: fadeIn 0.6s ease-in-out 0.3s forwards;
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
The Markdown “Plot Twist”
Then came the moment to write my first blog post (the one explaining why I started). This is where I hit another snag: everything was flat, like Markdown wasn’t working. Why? All those meticulous Preflight CSS resets I’d implemented had wiped out all default Markdown styling!
Initially, I thought my choice was a huge mistake. But solving it gave me even more control. I simply created a new file, markdown.css, in my styles folder, and imported it into the blog post layout component. I then defined specific font sizes, spacing, and colors for all Markdown elements, especially impressing myself with the customizations for pre and code tags.
I also created custom components, like Container wrappers, to align assets (icons, images) within Markdown. And for code highlighting, as suggested by Astro docs, I used Shiki. My only hiccup was realizing I hadn’t added Shiki’s variables to the data-theme updates in theme.css. Once fixed, everything worked wonderfully!
I love this component-based approach. Every time I need something new, I can create a component and just use it – like the Aside component I introduced in the previous blog post. It reinforces the idea of not over-editing existing components, ensuring consistency.
The Odyssey Concludes (for now)
At this point, I’m genuinely happy with this site. There are always improvements one can make (like reduced-motion toggles), but I realized I had to stop somewhere, or it would never be truly “complete.”
This has been the entire Odyssey behind building gauravb.me – or at least, most of it! I might have forgotten a few things, but I’d love to answer any questions you have. The blog size for this site breakdown grew larger than I anticipated, but it was incredibly peaceful writing it, and I hope it added some value for you. As of now, the site doesn’t have a comment section (though I’ve researched it, I have mixed feelings and kept it aside for now).
This blog post, like its predecessors, took a good chunk of time to prepare, as another “Odyssey” has already begun, and content for that will be added to the site soon. The effort is ongoing; I’m building it while writing blogs. My focus now is on writing and content, which is one of the best adventures. Happy learning!