Designing My Personal Site
Most developer portfolios look the same: flat white, rigid grid, card borders at full opacity. That's fine — it's readable and it works — but I wanted this site to feel a bit more considered. Here's what I actually changed and why.
The starting point was Apple's liquid glass — the frosted, layered surfaces they've been pushing across visionOS and iOS 26. I liked the idea but not the constraints: it's a heavy effect, tied to specific rendering hardware, and not something you can cleanly port to a web project without it feeling like a theme rather than a system. I wanted something simpler, familiar enough that it doesn't distract, and performant enough that I could reuse it across multiple projects without thinking twice. The result is less "liquid glass" and more "glass-adjacent" — translucent surfaces, soft blur, a moving background that gives everything something to sit on top of.
Background
The background is a fixed layer sitting behind everything at z-index: -10. It has two parts.
First, a 24px line grid using a CSS background gradient:
background-image: linear-gradient(to right, #80808010 1px, transparent 1px),
linear-gradient(to bottom, #80808010 1px, transparent 1px);
background-size: 24px 24px;
Second, two large blurred circles that slowly drift on a 9-second loop:
<div className="animate-blob absolute left-[45%] top-[30%] h-[700px] w-[700px] rounded-full bg-primary/5 blur-3xl" />
The circles use primary/5 — 5% opacity of the site's dark navy — so they're barely perceptible in isolation. The effect is more visible through the glass surfaces (nav, sidebar, cards) because of the backdrop-blur applied there.
The keyframe:
@keyframes blob {
0%, 100% { transform: scale(1) translate(0px, 0px); }
25% { transform: scale(1.08) translate(40px, -30px); }
50% { transform: scale(0.94) translate(-25px, 50px); }
75% { transform: scale(1.04) translate(35px, 30px); }
}
Just shifts and scales slightly over time so the page doesn't feel completely static.
Glass surfaces
The nav, sidebar, and cards all use the same basic pattern: partially transparent background with a backdrop blur.
Nav and sidebar:
className="bg-background/80 backdrop-blur-md border-border/50"
Cards:
className="bg-card/80 backdrop-blur-sm"
The blur is stronger on the nav and sidebar (md vs sm) because they sit in front of everything and need cleaner visual separation from the content layer behind them.
For this to work, the color variables need to carry an alpha channel. In Tailwind v3, that means using <alpha-value> in the config:
// tailwind.config.ts
background: "hsl(var(--background) / <alpha-value>)",
foreground: "hsl(var(--foreground) / <alpha-value>)",
// and so on for every color
Without this, bg-background/80 silently renders at full opacity — the modifier does nothing. Took longer to track down than I'd like to admit.
Typography
Two fonts: Playfair Display for headings and card titles, Inter for everything else. Both loaded via next/font.
The reason isn't purely aesthetic — it's contrast. Inter at body size is readable and neutral, which is what you want for dense text. Playfair at heading sizes adds visual weight without needing to push font sizes up. It also reads differently from the surrounding text, which makes the hierarchy more obvious at a glance.
// src/lib/fonts.ts
export const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });
export const playfair = Playfair_Display({ subsets: ["latin"], variable: "--font-heading" });
// tailwind.config.ts
fontFamily: {
sans: ["var(--font-sans)", ...fontFamily.sans],
heading: ["var(--font-heading)", ...fontFamily.serif],
}
Shape
Cards use rounded-2xl (16px) instead of the shadcn default rounded-lg (8px). The sharper corners looked out of place next to the blurred background — softer corners make the floating-card feel more coherent.
What didn't work out of the box
The shadcn component suite I updated to targets dense dashboards. The defaults are too compact for a reading-oriented site:
| Component | Default | Updated |
|-------------|----------------|----------------|
| Card text | text-xs/relaxed (12px) | text-sm (14px) |
| Card title | text-sm font-medium (14px) | text-base font-semibold (16px) |
| Badge | 10px text, 20px height | 12px text, 24px height |
| Button | 12px text, 28px height | 14px text, 36px height |
The other issue was keyframes. Several accordion and slide animations were defined inside an @theme inline { } block — a Tailwind v4 directive. In a v3 project, the browser treats @theme as an unknown at-rule and skips its contents entirely, so the animations were silently broken. Moved them to top-level CSS and they worked immediately.
Color palette
Scales
The system uses semantic color scales that adapt to the current theme. Hover to see hex codes.
light Mode
dark Mode
The palette is a standard HSL monochrome system that adapts to the user's system preference. Nothing exotic.
That's the system. Most of it is just being deliberate about which defaults to override and why.
