AboutBlogArticlesProjectsPublicationsExperienceTravelCV

Designing My Personal Site

DesignUI/UXTailwindCSSFrontend

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

Backgrounds
#FFFFFF
#FFFFFF
#FFFFFF
#F4F4F5
#E4E4E7
Foregrounds
#09090B
#09090B
#09090B
#71717A
#18181B
Primary
#18181B
#FAFAFA
Destructive
#EF4444
#FAFAFA
UI Elements
#E4E4E7
#E4E4E7
#09090B

dark Mode

Backgrounds
#09090B
#09090B
#09090B
#27272A
#313135
Foregrounds
#FAFAFA
#FAFAFA
#FAFAFA
#A1A1AA
#FAFAFA
Primary
#FAFAFA
#18181B
Destructive
#7F1D1D
#FAFAFA
UI Elements
#27272A
#27272A
#D4D4D8

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.