Playground

Design System

The tokens, primitives, and components that make this site, plus the component library it draws from.

Color Palette

The full color system. Semantic tokens for new components, brand ramps for visual identity, and --color-* aliases for the handbuilt components.

Theme colors

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

Use for borders, inputs, and focus rings. Not for surfaces or text.

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

Use for the handbuilt Astro components. Fixed aliases stay the same in both themes. Theme-aware aliases flip with the theme. Toggle the theme to see the 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

The token pipeline

A color decision starts in Figma and reaches a live component through three layers. A brand change touches one value and cascades everywhere.

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

Step 2 — Brand ramp in global.css
         --navy-900: 223.8 84.2% 19.8%;   /* 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 */
         --color-navy:  hsl(var(--navy-900));       /* legacy alias for handbuilt components */

Step 4 — Component use
         Custom Astro:   color: var(--color-navy);  /* Footer, WorkCard, Button */
         Basis UI:       bg-primary                 /* Tailwind utility reading --primary */

Intentionally excluded

These token groups don't exist in this system. Omitted by choice, not 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

Fraunces for display headings. Geist for everything else.

--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

Inline link style for body copy. Apply the .text-link class to any <a> tag.

The thread across my work is keeping people in their flow. The best interfaces never pull you out of what you're doing; they blend into the task itself.

Spacing

Use for padding, margins, and gaps. A 9-step scale from 4px to 96px.

Token rem px Visual
--space-2xs0.25rem4px
--space-xs 0.5rem 8px
--space-sm 0.75rem12px
--space-md 1rem 16px
--space-lg 1.5rem 24px
--space-xl 2rem 32px
--space-2xl3rem 48px
--space-3xl4.5rem 72px
--space-4xl6rem 96px

Primitives

The smallest reusable building blocks: standalone actions and inline labels.

Button Stable

Triggers an action or navigates to a destination.

Anatomy

Variants

Primary Secondary

States

Properties

NameTypeDefaultNotes
labelstringrequiredVisible button text.
hrefstringWhen set, renders as <a>; otherwise renders as <button>.
variant"primary" | "secondary""primary"Visual style.
disabledbooleanfalseGreys out and prevents interaction.
iconstringRaw SVG string rendered on the left; replaces the right arrow.
externalbooleanfalseAdds target="_blank" and safe rel attributes for off-site links.
downloadbooleanfalseTriggers a file save instead of navigation.

Tokens used

  • --color-navy
  • --color-lemon
  • --color-surface
  • --color-charcoal

Usage notes

  • Use primary for the main action on a page. One per section.
  • Use secondary for supporting actions. Multiple per section is fine.
  • Pass href when the button navigates somewhere; omit it when the button triggers an action in place.
  • Pass external when the link leaves the site.
View code
<Button label="Primary" href="/work" />
<Button label="Secondary" href="/about" variant="secondary" />
<Button label="External" href="https://example.com" external />
<Button label="Resume" href="/resume.pdf" download />

Section Label Stable

Introduces a major section with a small lemon pill.

Anatomy

Properties

NameTypeDefaultNotes
labelstringrequiredPill text. Keep it short (1 to 3 words).

Tokens used

  • --color-lemon
  • --color-navy

Usage notes

  • Use as the introduction to a major section, above the section heading.
  • Lemon is reserved for this purpose at full saturation. It feels like a signal because it appears nowhere else.
View code
<SectionLabel label="Selected Work" />

Cards

Content-bearing surfaces. Use for grids of work, writing, or field notes.

Project Card Stable

Displays a project as an image with a title beneath.

Anatomy

Project title

Project title

Variants

With image

With image

[ image ]

No image (placeholder)

Properties

NameTypeDefaultNotes
titlestringrequiredCard heading shown below the image.
imagestringImage src. When omitted, a placeholder appears.

Tokens used

  • --color-surface
  • --color-charcoal
  • --color-heading

Usage notes

  • Use in a flex row to display multiple projects side by side.
  • Omit the image only during development or when the project doesn't have a cover yet.
View code
<ProjectCard title="Project title" image="/images/work/project.png" />

Field Note Card Stable

Two-column card showing a field note body alongside an artifact preview.

Anatomy

Properties

NameTypeDefaultNotes
titlestringrequiredCard heading.
bodystringrequiredLong copy. Paragraphs separated by blank lines.
toolsTool[]requiredList of tool badges. Empty array hides the row.
toolsLabelstring"Tools:"Override the tools-row label.
exploreHrefstringWhen omitted, the explore link is hidden.
exploreLabelstring"Explore experiment"Override the explore link label.
artifactArtifactrequiredRight-column artifact preview.

Tokens used

  • --color-surface
  • --color-charcoal
  • --color-royal-blue

Usage notes

  • Use on the Field Notes archive page (/field-notes) for entries that have a paired experiment.
  • Stack vertically with a comfortable gap. Body reads left, artifact reads right on desktop; collapses to one column on mobile.
View code
<FieldNoteCard
  title="Card title"
  body="Paragraph one.\n\nParagraph two."
  tools={[{ label: "Figma" }]}
  exploreHref="/writing/some-slug"
  artifact={{ image: "/images/x.png", alt: "", caption: "Caption" }}
/>

Writing Card Stable

Link card to a writing entry, with an optional 'New' badge.

Anatomy

Variants

Properties

NameTypeDefaultNotes
hrefstringrequiredDestination URL.
imagestringCover image src. When omitted, a placeholder appears.
altstringAlt text for the image.
titlestringrequiredCard heading.
descstringrequiredShort description shown under the title.
isNewbooleanfalseAdds a lemon "New" badge in the top-right of the image.

Tokens used

  • --color-surface
  • --color-charcoal
  • --color-lemon
  • --color-navy

Usage notes

  • Use on the home page Field Notes section and the /field-notes archive.
  • Pass isNew for entries within the recency window so visitors can see what just shipped.
View code
<WritingCard
  href="/writing/some-slug"
  image="/images/writing/cover.png"
  alt="Cover"
  title="Article title"
  desc="One-line description."
  isNew={true}
/>

Work Card Stable

Flip card showing a project thumbnail on the front and case details on the back.

Anatomy

Channels:
voice
screen
Case Detail

Back face heading

First paragraph on the back face.

Second paragraph.

Outcome
Stat
description
Tap to return

Properties

NameTypeDefaultNotes
idstringrequiredUnique id used for the flip interaction and lightbox target.
patternstringrequiredSmall label above the title (e.g. "Learnable Efficiency").
titlestringrequiredProject title shown on the front face.
backHeadingstringrequiredHeading shown on the back face after flipping.
backParagraphsstring[]requiredBody paragraphs on the back face.
statsStat[]requiredHighlight stats on the back face.
imagestringrequiredFront-face thumbnail.
imageAltstring""Image alt text. Empty marks it decorative.
channelsChannel[]requiredChannel dots (voice, screen, camera, gesture, gaze).
lightboxDescstringrequiredDescription shown when the lightbox opens.
eagerbooleanfalseHints the image loader to fetch eagerly for above-the-fold cards.

Tokens used

  • --color-navy
  • --color-surface
  • --color-royal-blue
  • --color-citrine

Usage notes

  • Use in the Selected Work section on the home page.
  • Click the card to flip; click the expand button to open the lightbox.
  • Set eager on the first one or two cards that appear above the fold to speed up first paint.
View code
<WorkCard
  id="some-id"
  pattern="Pattern label"
  title="Project title"
  backHeading="Heading on the back"
  backParagraphs={["First paragraph", "Second paragraph"]}
  stats={[{ value: "First", desc: "of its kind" }]}
  image="/images/work/thumb.png"
  channels={[{ type: "voice", label: "voice" }]}
  lightboxDesc="Description when expanded."
/>

Other Components

Theme Toggle Stable

Sun/moon button that switches between light and dark mode.

Anatomy

Properties

NameTypeDefaultNotes
No props. The button reads data-theme on <html> and flips it on click.

Tokens used

  • --color-charcoal
  • --color-heading

Usage notes

  • Place inside the site nav. The button in this preview is visual only; the live one in the top nav is what actually changes the theme (only the first .theme-toggle on the page is wired by the inline script).
  • On first load, the site reads prefers-color-scheme as the default. The toggle lets the visitor override it; the choice is saved to localStorage.
  • If the visitor's chosen theme matches their OS preference, the override is cleared so future OS changes take effect naturally.
View code
<ThemeToggle />

Site Patterns

Recurring layouts that appear on every page.

Navigation Stable

Top bar with the site name and anchor links to the home page sections.

Anatomy

Properties

NameTypeDefaultNotes
No props. The link list is hardcoded inside Nav.astro.

Tokens used

  • --color-navy
  • --color-heading
  • --color-surface

Usage notes

  • Used on every page via BaseLayout.
  • Links use /#section anchors so they work from any page, not just the home page.
  • Below 768px the links collapse into a hamburger menu.
View code
<Nav />

Basis UI Components

Components from Basis UI. Status dots below show what's in use.

Component gallery