Playground

Design System

Design System

Everything I built to make this site. Colors, type, and components — shown as-is, no abstractions.

Color Palette

The full palette — semantic tokens for UI, and the brand ramps they're built from. All values are CSS custom properties in global.css using HSL.

Theme colors

Each card shows a background color with its paired foreground color as text — so you can immediately judge contrast. These are the tokens Basis UI components read directly.

Aa

Background

--background --foreground
Aa

Card

--card --card-foreground
Aa

Popover

--popover --popover-foreground
Aa

Primary

--primary --primary-foreground
Aa

Secondary

--secondary --secondary-foreground
Aa

Accent

--accent --accent-foreground
Aa

Muted

--muted --muted-foreground
Aa

Destructive

--destructive --destructive-foreground

UI colors

Single-use tokens for structural UI elements — not surfaces or text, but borders, inputs, and focus rings.

Border

--border

Card edges, dividers

Input

--input

Form field borders

Ring

--ring

Focus ring — Navy 700

Brand ramp — Navy

900 #081F5D
800 #0A2872
700 #1040A8
500 #2060D0
300 #6090E0
100 #C0D4F4

Brand ramp — Lemon

900 #8A8A0A
700 #BFBF12
500 #F1F11C
300 #F6F670
100 #FAFABC

Neutral scale

950 #1A1A1A
800 #4D4D4D
500 #808080
200 #D9D9D9
100 #F2F2F2
0 #FFFFFF

Component aliases

The --color-* tokens are backward-compatible aliases used by the handbuilt components (Button, Nav, Footer, Carousel, SectionLabel). They sit on top of the semantic tokens above. Fixed aliases always resolve to the same value regardless of theme. Theme-aware aliases flip automatically — toggle dark mode above to see them change.

Fixed — same in light and dark

Navy

--color-navy

Footer bg, code blocks, button hover
#081F5D → Navy 900

Royal Blue

--color-royal-blue

Links, nav hover, hero title
#1040A8 → Navy 700

Lemon

--color-lemon

SectionLabel bg, Footer name
#F1F11C → Lemon 500

Citrine (unused)

--color-citrine

Defined but not used in any component
#BFBF12 → Lemon 700

Slate

--color-slate

Borders, placeholders, Footer links
#808080 → Neutral 500

Theme-aware — flips with dark mode

Heading

--color-heading

h1–h6, nav name, carousel title
--primary

Charcoal

--color-charcoal

Body text, nav links, button text
--foreground

Surface

--color-surface

Section backgrounds, preview areas
--muted

White

--color-white

Card backgrounds, slide backgrounds
--background

Intentionally excluded

Shadcn/ui defines additional token groups that this site does not use. They are absent by design — not an oversight.

Chart 1–5

--chart-1…5

Data visualization palette. Not needed — no charts in this portfolio.

Sidebar tokens

--sidebar-*

Sidebar navigation system. Not needed — this site uses a simple top nav.

Typography

The type scale used across the site. Fraunces for display headings, Geist for everything else. Sizes are defined as CSS variables in global.css.

--text-6xl · 80px · Bold

Display

--text-5xl · 56px · Bold · h1

Page Title

--text-4xl · 40px · Bold · h2

Section Heading

--text-3xl · 32px · Bold · h3

Subsection

--text-2xl · 24px · Medium

Hero title / Now statement

--text-lg · 18px · Regular · Body lead

I'm a product designer who works at the intersection of emerging technology and human experience.

--text-base · 16px · Regular · Body

Default body text. Comfortable for long-form reading. Max-width is capped at 65ch to maintain an ideal line length.

--text-sm · 14px · Regular · UI text

Navigation links, footer links, secondary labels.

--text-xs · 12px · Bold · Label

Basis UI Components

Astro components from Basis UI, customized to match this palette. Each one is a copy-paste component — source lives in src/components/ui/. Toggle the preview to see dark mode.

The token pipeline

Every color decision starts in Figma as a variable and ends up in a live component through a four-layer chain. A brand update touches one value and cascades everywhere.

Step 1 — Figma variable (designer's source of truth)
         color/navy = #081F5D

Step 2 — Brand ramp token in global.css
         --navy-900: 223.8 84.2% 19.8%;  /* stored as HSL, no hsl() wrapper */

Step 3 — Semantic mapping in global.css (the bridge layer)
         --primary: 223.8 84.2% 19.8%;   /* "primary" maps to Deep Navy */
         --ring:    221.1 82.6% 36.1%;   /* focus rings use Navy 700 */

Step 4 — Basis UI component in Button.astro
         bg-primary   → resolves --primary → #081F5D in light / #C0D4F4 in dark
         hover:bg-ring → shifts to Navy 700 on hover / focus

Component gallery

Variant → token reference

Each variant name maps to a specific semantic token pair. Changing the token in global.css updates every component that uses that variant instantly.

Button / Badge variants → token used
─────────────────────────────────────────────
default      → bg-primary       text-primary-foreground
secondary    → bg-secondary     text-secondary-foreground
destructive  → bg-destructive   text-destructive-foreground
outline      → border-border    bg-background    hover:bg-accent
ghost        → bg-transparent   text-foreground  hover:bg-accent
link         → text-primary     (underline on hover)

Card variants → token used
─────────────────────────────────────────────
default      → bg-card          border-border
muted        → bg-muted         border-border
outline      → bg-transparent   border-2 border-border

Astro Components

Hand-built components specific to this site. These predate the Basis UI migration and may be refactored over time. What you see here is the real component — no separate demo version.

Button

Two variants: primary (navy fill) and secondary (surface + outline). Both use a pill shape and share the same hover behavior — the background shifts and the arrow icon rotates from ↗ (rest) to → (hover) over a smooth 0.2s ease transition. When an href prop is passed, the component renders as an <a> tag (correct for navigation). Without href, it renders as a <button> (correct for actions). The download prop triggers a file save instead of navigating. The optional icon prop accepts an SVG string — when provided, the icon renders on the left of the label and the right-side arrow is hidden.

import Button from '../components/Button.astro';

<!-- Primary (default) -->
<Button label="LinkedIn" href="https://linkedin.com/in/..." variant="primary" />

<!-- Secondary -->
<Button label="Download Resume" href="/resume.pdf" variant="secondary" download={true} />

<!-- Disabled -->
<Button label="Coming Soon" href="#" variant="primary" disabled={true} />

<!-- Icon left (no href = renders as <button>, not <a>) -->
<Button label="Reset" variant="secondary" icon='<svg ...>...</svg>' />

Section Label

A small lemon pill used to introduce each major section. Built with display: inline-block and background-color: var(--color-lemon). Lemon is used nowhere else on the page at full saturation — this constraint keeps it feeling like a signal, not decoration.

import SectionLabel from '../components/SectionLabel.astro';

<SectionLabel label="Selected Work" />

Navigation

Top bar with name on the left and anchor links on the right. On screens 768px and smaller, the links collapse into a hamburger menu that toggles open and closed. Links use /#section anchors so they work correctly from any page, not just the homepage. aria-current="page" is applied automatically to the active link.

import Nav from '../components/Nav.astro';

<Nav />

Project Card

Used in the Work section to display a project. Image placeholder uses aspect-ratio: 4 / 3 to stay proportional at any width. Cards sit in a flex row with flex: 1 so they share space equally regardless of content length.

[ image ]

Project title goes here

import ProjectCard from '../components/ProjectCard.astro';

<ProjectCard title="Project title goes here" />

Theme Toggle

Sun/moon icon button that lives in the navigation. Clicking it switches between light and dark mode and saves the choice to localStorage so it persists on future visits. On first load, the site reads the user's OS preference (prefers-color-scheme) as the default — the toggle lets them override it. The moon icon is shown in light mode (click to go dark), the sun icon in dark mode (click to go light).

import ThemeToggle from '../components/ThemeToggle.astro';

<ThemeToggle />