My last post was all about the “how” – the tech stuff behind “The Odyssey of Gaurav,” from picking Astro to setting up all the important info for the site. This time? We’re jumping into what usually feels like the trickiest part of making any website: how it looks. This wasn’t a straight path; it was a journey with some fun discoveries, some annoying dead ends, and finally, a choice to just use the basic building blocks of the web.
Pico.css: A Quick, Cool Start (That Didn’t Last)
While I was busy building the site’s structure, I bumped into Pico.css. I honestly can’t remember where I found it – maybe a YouTube video, some docs, or a Reddit thread – but it seemed really promising. My first thought, a sigh of relief, was: “Finally, I can skip all the hard styling stuff!” I was so sure about it that as I built the site’s bones, I kept telling myself, “I’m definitely using this; it’ll save me from style headaches.” So, I decided to try it out.
Setting it up was ridiculously easy:
npm install @picocss/pico
Then, I just added this to my src/layouts/BaseLayout.astro file:
<link rel="stylesheet" href="css/pico.min.css">
<body>
<main class="container">
<h1>Hello world!</h1>
</main>
</body>
And just like that, magic happened. My page was centered, the fonts looked good, and a default blue theme popped up right away. For the first few hours, it felt like I was getting things done in minutes (or even seconds) instead of hours. It was exciting.
But then, that familiar feeling of being stuck started to creep in. As I kept working, I realized I was doing the same thing I did with the Congo theme when I tried Hugo. I was constantly changing Pico’s default styles to make it look how I wanted. “Wait,” I thought, “if I’m just going to change everything, then why am I even using Pico.css?” My brain felt like it was breaking. I decided to stop for the day. (Turns out, “stopping” for me often means I’m on my phone, searching like crazy for other options and soaking up info, haha!) The very next day, Pico.css was gone from the project.
Why I Skipped Tailwind (For Now)
I didn’t know much of styling frameworks. The other big one I knew was Tailwind CSS. But I’d never used it in any of my projects, so I wasn’t confident at all. While looking for Pico.css alternatives, I read an interesting blog post called “The Tailwind Misinformation Engine” on the cool Nue.js site. In a way, this post actually made me feel better about my own feelings against Tailwind. It wasn’t that I was totally “dumping” Tailwind, but this post, in its own way, really backed up my thoughts about not using it. It surprised me how much a blog post could make me feel that way and even help me decide things.
Even with my doubts, I did install Tailwind just to try it. But, well, I ended up uninstalling it pretty quickly. It felt like it added an extra layer of complexity over the basic tech, and I’m not really good with abstractions unless I know the basics of what’s underneath. I like to understand how things work first.
Back to Basics: Pure CSS and the Coolness of Preflight
So, I made up my mind, clearly: I would write pure CSS. Yeah, it might mean more code, but I knew I’d learn a lot more, and it works everywhere, which I liked. Plus, with Astro’s built-in way to keep styles separate (scoped styling), I didn’t have to worry too much about class names messing with each other.
But there was one thing I really loved about Tailwind: its CSS resets. They give you a super clean starting point – like a blank canvas with some hidden cool features, ready for you to design. I really didn’t want to add the whole Tailwind setup just for these resets.
It wasn’t as simple as just adding:
html, body {
box-sizing: border-box;
margin: 0;
padding: 0;
}
How did I know what else was needed? I asked Gemini what resets Tailwind actually uses, and then, like a detective, I went straight to the Tailwind Preflight documentation. I found out that Tailwind’s preflight.css file is built on something called modern-normalize. This project’s job is to make sure CSS looks the same across different browsers. I thought that was really cool! I love learning about stuff like this.
My fix was pretty simple: I went to the Tailwind source code, copied all the code from preflight.css, made my own preflight.css file in my project, and then just imported it into my global.css.
Did it work perfectly right away? Almost! I hit a small snag: I had to define a few CSS variables that Tailwind’s Preflight needed, mostly for fonts:
:root {
--default-font-family: "Lexend"; /* or any other font you want */
--default-font-feature-settings: "liga" 1, "clig" 1;
--default-font-variation-settings: "wght" 900; /* or your preferred weight */
--default-mono-font-family: "Victor Mono"; /* or your preferred monospace font */
--default-mono-font-feature-settings: "liga" 1, "clig" 1;
--default-mono-font-variation-settings: "wght" 700;
}
This led to creating another file, font.css. In it, I put these variables, along with my chosen fonts (regular and monospace), and then imported it. Boom! Just like that, I had all the benefits of a full CSS reset with hardly any extra work. My project structure felt super clean: font.css for fonts, preflight.css for resets, and global.css for all the overall site styles.
Finding the Right Font: A Mini-Odyssey
While setting up that font-feature-settings variable, I learned about clig and liga – cool typography tricks I didn’t know existed before building this. It’s little discoveries like these that make building stuff fun. For my site’s main fonts, I ended up with Roboto and my favorite monospace font, JetBrains Mono, both from Google Fonts. Picking the “perfect” font was surprisingly hard; I actually changed fonts 2-3 times while building the site, always asking Gemini for “cute,” “calm,” “techy” fonts, and tons of other ideas!
A quick thought about Google Fonts: I found it weird that they don’t let you download fonts in WOFF formats directly, even though they recommend using them. (This could be a whole separate blog post!) I tried to use variable fonts to make setup easier, but to change my TTF fonts to WOFF (which is better for web), I used online tools like transfonter.org and fontsquirrel.com. The fonts were finally set up, even though, like I said, I changed my mind a few times.
Colors and Themes: Getting Things Just Right
With fonts sorted, it was time for colors and themes. (I actually put in the theme toggle earlier because it helped me work better, but I’ll explain it here.) I made two more important CSS files: theme.css and config.css (this config.css became one of the most key CSS files for my whole site).
theme.css is where I kept all my main color variables, like --text-color, --bg-color, etc. Why more variables? For better organization, of course, but more importantly, they were totally necessary for adding light and dark mode.
Look how this setup lets me easily change colors:
:root {
/* Default (Light Theme) Variables */
--background-color: #eff1f5;
--background-color-secondary-a: #e6e9ef;
--background-color-secondary-b: #dce0e8;
}
[data-theme="dark"] {
/* Dark Theme Overrides */
--background-color: #1e1e2e;
--background-color-secondary-a: #181825;
--background-color-secondary-b: #11111b;
}
This neat trick let me easily change lots of colors – for card backgrounds, text, and more – just by switching a data-theme tag on the <html> element.
And config.css? Yes, this file became like the central control panel for my site’s CSS. It mostly held variables that were based on other variables, and some default styles I wanted for a consistent look everywhere.
:root {
--text-small: 0.75rem;
--subtext-color: var(--text-sub-head-b);
}
Seeing this minimalist approach come together made me really happy. It definitely took more work at the start, but the long-term benefits and the sheer control it gave me brought a smile to my face many times.
What about the colors themselves? No big story here. I really love the Catppuccin color palette, which I use on my “riced” desktop setup, so I decided to use those same colors for “The Odyssey of Gaurav.” I followed their style guide closely and then made small changes to fit what I liked. All the colors from the guide were set up as variables in theme.css, with a few changes in config.css. If you look closely, you’ll also see a subtle gradient on the page background, which makes the site feel nice and adds a sense of depth as you scroll. I used gradient generator from josh’s goodies providing my colors.
If you’re curious about other cool color palettes, Rose Pine is another well-documented option that I found. And if you like building your own theme from scratch, Huemint was a super useful site for creating nice gradients.
Building Page by Page: The Homepage Gets Its Look
At this point, my site’s pages were still just filled with placeholder text – lorem ipsum and basic headings like “This is my blogs page, this is my about.” Even though the content collections were set up and sample data from the Astro blog starter worked fine, the site’s look wasn’t there yet.
My plan was clear: finish one page at a time. This helped me stay focused and not get overwhelmed. I started with the homepage, and naturally, the first thing was the header. I wanted a clean, minimal design:
- Sticky to the top.
- With a cool glassmorphism background.
- And, as I promised, a theme toggle button.
The theme toggle itself uses standard methods. It smartly checks your system’s preferences and then saves your choice using local storage. If you’re using Astro’s view:transitions (which I played with a bit, so I know), you might see a quick FOUC - flash of unstyled content. But I learned that adding is:inline to your script can often help. I found this blog post by Jklakus really useful. Instead of one big script, I made two separate files: ThemeInitializer.astro to handle the first check, and ThemeToggle.astro for the button clicks. This separation also made it easier to add an animated toggle. While looking through Pico.css’s code, I saw their toggle came from toggles.dev – these are amazing resources and saved me a lot of time! I picked one of their toggles, added some custom CSS tweaks, and had a beautiful, working theme toggle ready.
Next, I put my branding in the header. This made me realize I needed a different font for my brand name, and Pacifico fit perfectly; it looks cool and adds a unique, personal touch.
The header felt almost done, just needing a hamburger menu for phones. “Just,” I thought. It turned out to be much harder than I expected! Since I wasn’t using a big JavaScript framework, I looked at different ways to do it – CSS-only, or JavaScript plus CSS. I went with a simple CSS + JS way (using a button with an onclick event), but getting the overflow to work right was a huge headache. I’m still learning how to handle that well!
You can skip to next section, it’s a small detail you might not even notice it.
I wanted to make every little part perfect. So, I added a smooth touch: when you open the hamburger menu and click a link, instead of just jumping to the new page, the menu first plays a nice closing animation, and then you go to the new page. It makes it feel really smooth, like an app.
Also, on phones, if the menu is open and you hit the back button, I wanted the menu to close instead of going back in your browser history. I hoped for a simple event like onbackpress, but there wasn’t one. After trying a few things, changing the browser’s history using pushState("") and popstate worked best. And, for desktop users, pressing the Escape key also closes the menu. Later, for SEO and accessibility, I also added the tabindex attribute to properly hide and show the menu. So, two animations are happening at once – one on the button and one on the menu panel. Phew! The header was finally, truly done. After that, I made a simple footer and later added icons to it.
The Hero Section: Watermelon, SVG Magic, and Welcome Animations
For the main part of the homepage (the hero section), I had an idea: my site needed a special “sprite,” something fun and unique. For some reason, a watermelon character kept popping into my head. It felt cute, calm, and full of personality – just what I wanted. To get some ideas, I asked ChatGPT to make some images for the sprite. The very first image it made just grabbed my heart! From there, I kept generating different poses of this cute watermelon. Since I like robotics and drones, I chose a version that subtly hinted at those things.
But now, the challenge: how to turn these PNG pictures into something I could use in my hero section? 3D options like Three.js or Spline looked cool. I knew about both but had never really used them. I followed a quick guide for Three.js and quickly realized it wasn’t right for this site’s goals. It would take way too much time, and I wanted something light. Spline felt similar – powerful, but a bit more paid and less control. So, I sadly dropped those ideas, though I definitely want to try Three.js more later. Big props to everyone making amazing stuff with Three.js!
I figured I wasn’t ready for that much 3D interactivity yet. So, 2D it was. I needed something light, that I could easily change on the fly – colors, animations, etc. What’s better than SVGs then? Oh man, I love SVGs!
The catch was, I only had PNGs. So, it meant some manual work, and I felt a bit overwhelmed, but I knew it would be worth it. I downloaded Inkscape, watched a few quick YouTube videos, and started carefully tracing the PNGs. The important part was to keep separate paths for each bit I wanted to control (like legs, feet, smile, shadow, etc.). Making these custom SVGs took a lot of effort, but I was building my own assets, and that made me happy. This was my watermelon.
I’m not sure if it was my mistake or how Inkscape works, but my exported SVGs often had generic path numbers instead of the names I gave them. The trick that saved me was opening the SVG in Chrome, inspecting it, hovering over the paths, and then changing the id names right in the SVG code.
With my custom SVGs finally ready, it was time to make them move. I made a special Astro component, pasted the SVG code right inside it, and then added alternate and infinite transitions, setting up the keyframes directly in CSS. And just like that, my watermelon sprite was wiggling its legs. Don’t disturb it, it’s doing something!
Just putting the watermelon there wasn’t enough, especially with how big the screen was. I decided to add some text for the watermelon to “rest” on, making it look like it was flying. After trying different words, “Hello!” looked really amazing. Even though it looked cool already, I also decided to add a stroke-dasharray transition. So, when the page loads, the text animates in, making it feel like a real greeting. Many tutorials said to draw a vector path for this, but I wasn’t great with the pen tool (my “Hello” didn’t look smooth when I tried). So, I looked for other ways.
GSAP? Looking at their website, I was almost convinced to add this library just for animating SVG paths. But then, I thought: is it really worth adding a whole library just for one small thing? Even though I always get excited to learn and try new things, it didn’t make much sense to write more JavaScript when simple, lightweight CSS could do the job (it was easier and took almost no effort). After more searching, I found a clever Figma hack that, with just 4-5 lines of CSS, animated the text (each letter by itself). It wasn’t exactly the letter-by-letter animation I wanted for the whole word, but I was happy with the trade-off for how simple it was, at least for now. Later I also added a flying drone with hovering animation.
Below the animated “Hello!”, I added a short welcome message and a greeting from me, followed by sections for my latest blogs, tags, and a feedback/connect form (there’s some interesting stuff in that we’ll talk about later!). With these parts in place, the hero section was complete.
Icons: The SvgIcon Component That Saved Me
I use icons all over the page, and I mostly get them from Tabler Icons through the Iconify repo. When you need to really customize SVGs, it’s fine to import them directly and make a custom component. But what about simple interface icons? I didn’t want to be stuck with one specific icon library. Astro’s way of using SVGs is to import each one and use it like a component. While that’s better than many ways, what if I have 5-6 icons on one page? That’s 5-6 imports!
To fix this, I used Astro’s powerful Content Collections and this guide. I made a custom <SvgIcon> component and set up a special folder where I would download all my custom icons. Now, instead of importing each individual SVG, I can just import my SvgIcon component and pass the icon name and any styles as props. This was a lifesaver, and I used it everywhere on the site.
---
interface Props {
name: string;
altText: string;
width?: number;
height?: number;
class?: string;
id?: string;
}
const { name, altText, width, height, class: className, id } = Astro.props;
const fullPath = `/src/assets/icons/${name}.svg`;
const iconImports = import.meta.glob<typeof import('*.svg')>('/src/assets/icons/*.svg', { eager: true });
if (!iconImports[fullPath]) throw new Error(`Icon "${name}.svg" does not exist in icons directory`);
const SvgIco = iconImports[fullPath].default;
---
<SvgIco
width={width}
height={height}
class={className}
aria-label={altText}
fill="currentColor"
id={id}
/>
Imports are now easy as:
---
import Icon from "@/components/SvgIcon.astro"; {/* Just import component once */}
---
<SvgIcon name="github" altText="GitHub" width={24} height={24} class="icon" />
<SvgIcon name="x" altText="x-icon" width={24} height={24} class="icon-red" />
Other Pages and What’s Coming Next
For the other pages and routes, their structure was more defined than /home and /about. So, I just needed to style them, and I tried to keep them as simple as possible. I did have a small challenge making the whole blog post preview cards clickable on the /blog page, especially when they already had links inside them. But there were many solutions online for this common problem; frameworks often handle this stuff automatically.
I saved the About section for last, finishing it after my other pages were ready. This part was mostly about making sure the site felt right.
We’ve talked a lot about how the site looks, but there’s still more to share! We haven’t discussed the confetti animation, the fun watermelon on the like button, how I’m tracking views and likes, or the details of the connect form. We’ll get into all those exciting things in the next blog post!