export const metadata = { title: "Designing My Personal Site", publishedAt: "2025-12-10", summary: "The design decisions behind this site's visual system — what I changed, why, and how it works.", tags: ["Design", "UI/UX", "TailwindCSS", "Frontend"], image: "/images/design-system.png", }; 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. ## 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: ```css 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: ```tsx
``` 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: ```css @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: ```tsx className="bg-background/80 backdrop-blur-md border-border/50" ``` Cards: ```tsx 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 `` in the config: ```ts // tailwind.config.ts background: "hsl(var(--background) / )", foreground: "hsl(var(--foreground) / )", // 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](https://fonts.google.com/specimen/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. ```ts // src/lib/fonts.ts export const inter = Inter({ subsets: ["latin"], variable: "--font-sans" }); export const playfair = Playfair_Display({ subsets: ["latin"], variable: "--font-heading" }); ``` ```ts // 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 import { ColorPalette } from "~/components/ColorPalette"; 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.