Design System

Living reference of all design tokens powering dustinrea.com. Each section shows tokens in both light and dark themes, stacked vertically.

Colors

10 semantic color tokens defined in global.css. Light and dark values shown for each token. The palette pairs a deep navy primary with a mustard-gold accent for selective emphasis (CTAs, highlights, hover states).

Light

Background --color-bg #FFFFFF
Background Alt --color-bg-alt #F6F3F2
Surface --color-surface #F2EEED
Text --color-text #171717
Text Muted --color-text-muted #555555
Primary (Navy) --color-primary #1F2547
Primary Hover --color-primary-hover #2E3559
Accent (Mustard Gold) --color-accent #C8961E
Accent Hover --color-accent-hover #A87C16
Border --color-border #E0DDDC

Dark

Background --color-bg #171717
Background Alt --color-bg-alt #1E1E1E
Surface --color-surface #252525
Text --color-text #F6F3F2
Text Muted --color-text-muted #A0A0A0
Primary (Navy) --color-primary #9CA0C4
Primary Hover --color-primary-hover #B0B4D4
Accent (Mustard Gold) --color-accent #E8B73D
Accent Hover --color-accent-hover #F0C457
Border --color-border #4A4A4A

Typography

Type Scale

Fluid type scale using clamp(), scaling smoothly between mobile and desktop viewports.

Light

Aa
--text-xs clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem)
Aa
--text-sm clamp(0.875rem, 0.8rem + 0.35vw, 1rem)
Aa
--text-base clamp(1rem, 0.9rem + 0.5vw, 1.125rem)
Aa
--text-lg clamp(1.125rem, 1rem + 0.6vw, 1.25rem)
Aa
--text-xl clamp(1.25rem, 1.1rem + 0.75vw, 1.5rem)
Aa
--text-2xl clamp(1.5rem, 1.2rem + 1.5vw, 2rem)
Aa
--text-3xl clamp(1.875rem, 1.5rem + 1.875vw, 2.5rem)
Aa
--text-4xl clamp(2.25rem, 1.75rem + 2.5vw, 3.5rem)

Dark

Aa
--text-xs clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem)
Aa
--text-sm clamp(0.875rem, 0.8rem + 0.35vw, 1rem)
Aa
--text-base clamp(1rem, 0.9rem + 0.5vw, 1.125rem)
Aa
--text-lg clamp(1.125rem, 1rem + 0.6vw, 1.25rem)
Aa
--text-xl clamp(1.25rem, 1.1rem + 0.75vw, 1.5rem)
Aa
--text-2xl clamp(1.5rem, 1.2rem + 1.5vw, 2rem)
Aa
--text-3xl clamp(1.875rem, 1.5rem + 1.875vw, 2.5rem)
Aa
--text-4xl clamp(2.25rem, 1.75rem + 2.5vw, 3.5rem)

Font Stacks

Sans (Primary) --font-sans

The quick brown fox jumps over the lazy dog.

'Gordita', system-ui, -apple-system, sans-serif
Mono --font-mono

The quick brown fox jumps over the lazy dog.

ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace

Font Weights

Gordita Regular 400
Gordita Medium 500
Gordita Bold 700

Tracking (Letter Spacing)

4 letter-spacing tokens for controlling text density. Tight values compress display text; wide values open up uppercase labels.

Light

Section Heading
--tracking-tight -0.015em: Tight Display/hero text
Section Heading
--tracking-snug -0.01em: Snug Section headings (h1, h2)
Section Heading
--tracking-normal 0: Normal Body text
DESIGN TOKENS
--tracking-wide 0.05em: Wide Uppercase labels

Dark

Section Heading
--tracking-tight -0.015em: Tight Display/hero text
Section Heading
--tracking-snug -0.01em: Snug Section headings (h1, h2)
Section Heading
--tracking-normal 0: Normal Body text
DESIGN TOKENS
--tracking-wide 0.05em: Wide Uppercase labels

Spacing

8 spacing tokens from global.css. Visual bars show relative size with pixel equivalents.

Light

--space-xs 0.25rem (4px)
--space-sm 0.5rem (8px)
--space-md 1rem (16px)
--space-lg 1.5rem (24px)
--space-xl 2rem (32px)
--space-2xl 3rem (48px)
--space-3xl 4rem (64px)
--space-4xl 6rem (96px)

Dark

--space-xs 0.25rem (4px)
--space-sm 0.5rem (8px)
--space-md 1rem (16px)
--space-lg 1.5rem (24px)
--space-xl 2rem (32px)
--space-2xl 3rem (48px)
--space-3xl 4rem (64px)
--space-4xl 6rem (96px)

Borders & Radii

Border-radius tokens from global.css. Each box demonstrates the radius applied to a 1px border. Token names map to CSS custom properties.

Light

--radius-sm 6px Small
--radius-md 8px Medium
--radius-lg 12px Large
Direct value 50% Circle (special)
Direct value 999px Pill (special)

Dark

--radius-sm 6px Small
--radius-md 8px Medium
--radius-lg 12px Large
Direct value 50% Circle (special)
Direct value 999px Pill (special)

Shadows

6 shadow patterns used for elevation, focus, and interactive feedback. Each card demonstrates the shadow at rest.

Light

Subtle Used for cards, elevated surfaces at rest. 0 1px 2px rgba(0,0,0,0.08), 0 2px 8px rgba(0,0,0,0.06)
Hover / Elevated Multi-layer hover shadow for interactive cards. 0 2px 4px rgba(0,0,0,0.1), 0 4px 16px rgba(0,0,0,0.08)
Soft Lift Comparison blocks and light hover feedback. 0 2px 4px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.06)
Dropdown Nav dropdowns, popovers. 0 4px 6px -1px rgba(0,0,0,0.08), 0 2px 4px -2px rgba(0,0,0,0.05)
Sheet (top edge) Cookie banner and bottom sheets. 0 -4px 12px rgba(0,0,0,0.08)
Focus ring Focus indicator for form inputs. 0 0 0 3px rgba(43,46,74,0.15)

Dark

Subtle Used for cards, elevated surfaces at rest. 0 1px 2px rgba(0,0,0,0.08), 0 2px 8px rgba(0,0,0,0.06)
Hover / Elevated Multi-layer hover shadow for interactive cards. 0 2px 4px rgba(0,0,0,0.1), 0 4px 16px rgba(0,0,0,0.08)
Soft Lift Comparison blocks and light hover feedback. 0 2px 4px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.06)
Dropdown Nav dropdowns, popovers. 0 4px 6px -1px rgba(0,0,0,0.08), 0 2px 4px -2px rgba(0,0,0,0.05)
Sheet (top edge) Cookie banner and bottom sheets. 0 -4px 12px rgba(0,0,0,0.08)
Focus ring Focus indicator for form inputs. 0 0 0 3px rgba(43,46,74,0.15)

Icon System

Two-tier system built on Phosphor Icons. Chrome uses the Regular weight at 20–24px for nav, FAQ chevrons, buttons, and form fields. Story uses the Duotone weight at 32–48px for cards making a category claim — service ladder, founder profiles, trigger events, "what changes". Decorative by default (aria-hidden="true"); icons that carry meaning on their own get an aria-label.

Chrome (Regular)

magnifying-glass Search / inspect
caret-down Dropdown chevron
list Nav drawer
x Close / dismiss

Story (Duotone)

Service ladder

stethoscope Clarity Audit
compass Founder's Coaching
rocket-launch Ship Sprint
wrench Maintenance
target PMF Accelerator
chart-line-up Scale Accelerator

Founder-profile self-qualifier

hourglass-medium Offshore-agency burnout
wall Vibe-coding wall
bug Bug-hell trajectory
trophy Post-jackpot founder

Trigger events

arrows-clockwise Re-explaining loop
magnifying-glass-minus Subtle bugs / no real-user testing
warning-octagon Lack of confidence in releases
calendar-x Releases falling behind
user-minus Client lost or complaining

"What changes when your partner has done this before"

calendar-blank Your week opens up
arrow-right The product moves
chats-circle Conversations are about the business

Spline-matching tier (optional)

For the two or three hero-card positions where spline scenes already live, swap Phosphor Duotone for a 3dicons.co glyph. Cap site-wide at five surfaces. More than that and the page turns into a parade of 3D objects.

Breakpoints

3 responsive breakpoints used in @media queries. All layouts use min-width (mobile-first).

Name Value Usage
sm 640px Small tablets, landscape phones. Single-column to two-column shifts.
md 768px Tablets. Dual-column layouts, theme grid side-by-side, font-stack grid.
lg 1024px Desktops. Sidebar TOC becomes sticky, nav drawer → inline links.
sm: 640px
md: 768px
lg: 1024px

Motion

Duration tokens and motion patterns used for transitions and animations.

Duration Tokens

3 duration tokens from global.css. Hover each demo box to see the transition speed in action.

Light

Hover me
--duration-fast 0.15s: Fast Micro-interactions: focus rings, hover feedback
Hover me
--duration-normal 0.2s: Normal Standard hover/click transitions
Hover me
--duration-slow 0.3s: Slow Theme changes, layout shifts, drawer slides

Dark

Hover me
--duration-fast 0.15s: Fast Micro-interactions: focus rings, hover feedback
Hover me
--duration-normal 0.2s: Normal Standard hover/click transitions
Hover me
--duration-slow 0.3s: Slow Theme changes, layout shifts, drawer slides

Motion Patterns

5 motion patterns used for transitions and animations. Hover or click each demo to see the curve in action.

Theme Transition --transition-theme background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease Applied globally for smooth light ↔ dark mode switches.
A
A
Hover Ease 0.15s ease Quick interactive feedback on buttons, links, and TOC items.
Hover me
Nav Drawer Slide 0.3s cubic-bezier(0.22, 1, 0.36, 1) Elastic slide-in for the mobile navigation drawer.
Cookie Banner Rise 0.3s cubic-bezier(0.22, 1, 0.36, 1) Bottom sheet rise with matched opacity fade.
ThemeToggle Icon 0.3s cubic-bezier(0.2, 0, 0, 1) Icon morph curve for sun ↔ moon toggle.

Components

All site components rendered with props tables and dual-theme previews. Components marked Static replica cannot be rendered live due to singleton constraints (transition:persist, canvas, or cookie-gated scripts).

Button Variants

Three canonical button styles: .btn-primary (solid background, prominent CTA), .btn-secondary (text-only with underline on hover), and .cta-link (inline arrow link). All use --color-primary / --color-primary-hover and share min-height 48px for touch targets.

Defined in: global.css (available site-wide)

Usage: CTAs on all pages. .btn-primary has box-shadow lift on hover, .btn-secondary shows border-bottom on hover, and .cta-link shows border-bottom on hover with arrow suffix. Styles defined globally in global.css.

Theme Toggle

Animated sun/moon icon toggle for light/dark mode with cross-fade transition.

Props: None

Light

Dark

Usage: Rendered inside Nav. Persists choice to localStorage. View-transition compatible via astro:page-load listener. The is:inline script binds a click handler to [data-theme-toggle].

Nav

Site-wide navigation with responsive hamburger/drawer, theme-aware logo, and dropdown menu.

NameTypeDefaultDescription
No props. Active-link highlighting is updated client-side via setupNav() on each astro:page-load event, reading window.location.pathname to set .active class and aria-current="page"

Light

Static replica
Book a Call

Dark

Static replica
Book a Call
Usage: Rendered in PageLayout with transition:persist="nav". Active-page highlighting is updated client-side on each navigation via updateActiveLinks() in the astro:page-load handler — the server-rendered .active class and aria-current are stale after the first page since the nav persists across view transitions. The dropdown trigger uses the same .nav-link styles as regular links (apart from its chevron caret). Cannot be rendered live here because transition:persist and data-nav selectors would conflict with the real Nav.

Nav Dropdown

Keyboard-accessible dropdown menu used for the "Resources" nav item. Supports Enter/Space to toggle, ArrowDown/ArrowUp to navigate items, and Escape to close.

Props: None (embedded in Nav component)

Light

Static replica
Resources
Resources
Better Every Cycle

Dark

Static replica
Resources
Resources
Better Every Cycle
Usage: Part of the desktop Nav. Trigger uses the same .nav-link styles as regular links (font-size, weight, color) plus a chevron caret. Uses aria-expanded and aria-haspopup. Menu items use role="menuitem" with roving tabindex. Active state (.active class + aria-current="page") updated client-side on navigation. Closed on click-outside, Escape, Tab, or view transition navigation.

Dot Field

Generative canvas dot field for the homepage hero with ambient dots, cursor interaction on desktop, and drift on mobile.

Props: None

Light

Static replica

Dark

Static replica
Usage: Homepage hero only. 3–4 dot size classes with organic spacing. Desktop: cursor parallax. Mobile: ambient drift. Respects prefers-reduced-motion (static single draw). Theme-adaptive via MutationObserver on data-theme. Canvas-based, so a second instance cannot be rendered without selector conflicts.

Calendly Embed

Inline Calendly scheduling widget with view-transition support and script dedup.

NameTypeDefaultDescription
url string 'https://calendly.com/dustinrea/strategy-call' Calendly scheduling URL to embed

Light

Static replica

Loading scheduling widget…

Book directly on Calendly →

Dark

Static replica

Loading scheduling widget…

Book directly on Calendly →

Usage: Placed on scheduling pages. Loads Calendly's widget.js from CDN with dedup checks. Uses Calendly.initInlineWidget() (not data-url auto-load) for view-transition compatibility. Container has id="calendly" for CTA scroll targets. Cannot be rendered live here because it would load external scripts and create a real scheduling widget.

Text Input

Standard form text input with focus ring, placeholder styling, and error state. Used across all form components.

ClassPurpose
.form-inputBase input styles — border, padding, min-height 44px, focus ring
.input-errorRed border for validation errors

Light

Name is required.

Dark

Name is required.
Usage: All forms (Contact, Discovery, Feedback). Base class .form-input provides consistent styling. Focus ring uses box-shadow: 0 0 0 3px with theme-adaptive opacity. Error state via .input-error turns border red. Min-height 44px for WCAG touch target compliance.

Select Dropdown

Native <select> with custom chevron icon, appearance: none styling, and theme-aware SVG arrow. Used for engagement type, team size, timeline, and budget fields.

ClassPurpose
.form-input.form-selectCombines base input styles with custom select appearance

Light

Dark

Usage: Discovery and Feedback forms. Uses appearance: none with a background SVG chevron that swaps between light/dark variants via [data-theme]. Padding-right reserves space for the custom arrow. Options list uses native browser rendering.

Rating Pills

1–5 radio button group styled as clickable pill buttons. Uses visually-hidden <input type="radio"> with styled <span> labels.

Props: None (embedded in Feedback Form)

Light

Rating

Dark

Rating
Usage: Feedback form. Radio inputs are visually hidden with clip: rect(0,0,0,0). Pill labels (.rating-pill-label) use border-radius: 999px for pill shape. Checked state fills with --color-primary. Focus-visible shows a 2px outline. Supports keyboard navigation via native radio group behavior.

Card

Canonical surface block used across the site to group related content. Base style: background-color: var(--color-surface), border: 1px solid var(--color-border), border-radius: var(--radius-lg), padding: var(--space-xl). Adapts automatically to dark mode via token references.

Variants in use: .change-card, .profile-card, .portfolio-item, .faq-item, .testimonial, .objection-card, .case-item. All share the surface + border + radius foundation; they differ in padding, internal layout, and accent treatments (e.g. .testimonial uses a 4px left border in --color-primary).

Light

Card title

A short body line that demonstrates the muted text token on top of the surface background.

Dark

Card title

A short body line that demonstrates the muted text token on top of the surface background.

Usage: Wrap a heading + short body in an <article> with the relevant variant class. For interactive cards add a hover lift (transform: translateY(-2px)) and the Soft Lift shadow token. For placeholder/empty states use border-style: dashed and opacity: 0.7 (see .portfolio-item--placeholder on the home page).

Comparison Table

Three-column comparison table for the /vs/* pages. First column labels the dimension; remaining columns hold the contrasted positions. The "this practice" column is emphasised via color: var(--color-text) and font-weight: 500 against the muted competitor column.

Defined in: page-scoped styles in src/pages/vs/offshore-agency.astro and src/pages/vs/freelancers.astro. Wrapped in .compare-table-wrapper for horizontal scroll on narrow viewports.

Light

Dimension Alternative This practice
Who you talk to Account manager plus rotating juniors The same person on every call
Communication Slack and Jira translation overhead Specs in plain language before code

Dark

Dimension Alternative This practice
Who you talk to Account manager plus rotating juniors The same person on every call
Communication Slack and Jira translation overhead Specs in plain language before code
Usage: Render a <table class="compare-table"> with <thead> + <tbody>. First body cell of each row uses <th scope="row"> for the dimension label. The right-most <td> auto-styles as the emphasised winner column via tbody td:last-child. Wrap in <div class="compare-table-wrapper"> for horizontal overflow scroll on narrow viewports.

Patterns

Reusable layout and interaction patterns used across the site. Each pattern shows the canonical structure with live or code examples in both themes.

Page Layout

Every page follows BaseLayout → PageLayout → page content. PageLayout adds Nav, Footer, CookieConsent, and ClientRouter (view transitions). Content is structured with semantic <section> blocks inside <main id="main-content">.

Light

Nav
Section 1
Section 2
Section 3

Dark

Nav
Section 1
Section 2
Section 3
Usage: Every page. PageLayout wraps BaseLayout and provides Nav, Footer, CookieConsent, and ClientRouter for view transitions.

Section Pattern

Content sections alternate between default and --color-bg-alt backgrounds. Each section uses section-inner (max-width 64rem) or section-inner--narrow (48rem, centered) containers, with aria-labelledby linking to the section heading.

Light

Section (default bg)

Content inside .section-inner

Section (alt bg)

Alternating --color-bg-alt

Dark

Section (default bg)

Content inside .section-inner

Section (alt bg)

Alternating --color-bg-alt

Usage: All content pages. Alternating backgrounds create visual rhythm between sections.

Form Pattern

All forms share: honeypot spam protection (name="_gotcha", hidden via CSS), client-side validation, a server error banner (initially hidden), a success state transition, and fetch-based submission. The form replaces itself with a success message on completion.

Interactive demo — fill out and submit to see the success state, or toggle "Trigger Errors" to see validation and server error states. No data is sent anywhere.

Light

Dark

Usage: Contact, Discovery, and Feedback forms. All three share identical error handling, honeypot protection, and success-state architecture. Toggle "Trigger Errors" to simulate a server error after validation passes.