Contour / Components
Components
The building blocks, rendered live from the production stylesheet. Every specimen is interactive — hover it, tab to it, and use the Dark / Light switches to re-theme a single specimen without leaving your reading theme.
Button#
Pill buttons in two emphases. Primary is the accent-filled action — one per view.
Ghost is the bordered companion for secondary paths. Both take an optional 24px
stroke icon and an optional data-magnetic attribute that lets the button lean toward
a fine pointer.
View markup
<a class="btn btn--primary" href="…" data-magnetic>
<span>Selected Work</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">…</svg>
</a>
<a class="btn btn--ghost" href="…" data-magnetic><span>See the work</span></a>States
| State | Trigger | Treatment |
|---|---|---|
| Default | — | Primary: --accent fill, --accent-contrast text. Ghost: hairline border, --text. |
| Hover | Pointer | Primary deepens to --accent-strong; ghost fills faintly. With data-magnetic, the button leans toward the cursor and settles back on leave. |
| Focus | Tab | Global 2px --accent outline, 3px offset. |
| Active | Press | Settles flat — no scale-down gimmick. |
✓ Do
- One primary per view; pair with ghosts.
- Keep labels to 1–3 words, sentence case.
- Use a real
<a>for navigation,<button>for actions.
✕ Don't
- Don't stack two primaries side by side.
- Don't put body-length sentences in a pill.
- Don't rely on the magnetic effect for meaning — it's decorative and absent on touch.
hover: hover + pointer: fine) and never moves the focus target.Tag#
Small hairline chips that annotate work with domains and disciplines. Tags are informational — they don't filter, so they're never interactive.
View markup
<span class="tag">Research</span>span clickable.Section head#
The numbered eyebrow-plus-title block that opens every major section. The number is a wayfinding device; the rule after the label stretches to fill.
01 Selected Work
Products shipped for Fortune 500s,
startups & everything between.
View markup
<div class="section-head">
<p class="section-head__label"><span class="section-head__num">01</span> Selected Work</p>
<h2 class="section-head__title">Products shipped for Fortune 500s…</h2>
</div>h2) in a correct outline; the specimen uses p to keep this page's own outline clean. Numbers use --accent-strong so they pass AA at label size.Stat#
A metric tile: kicker, display numeral, progress seam and caption. On the live site the numeral rolls in with a slot-machine scramble when scrolled into view (and re-rolls on hover); the bar fills once.
View markup
<div class="stat">
<span class="stat__kicker">Conversion</span>
<span class="stat__value"><span class="stat__num" data-odo="290">0</span>%</span>
<span class="stat__bar" aria-hidden="true"></span>
<span class="stat__label">Lift in Regal in-app ticket purchases</span>
</div>prefers-reduced-motion — reduced users get the final number set immediately. Keep captions self-sufficient: the numeral plus label must read as a sentence.Recommendation card#
A testimonial as a figure: long-form quote with a structured attribution — name, role, and the relationship + date that make the praise verifiable.
“You want Drew on your team. Simply put—Drew solves problems. And he doesn't just solve the ones clients articulate are their problems, but even the ones they don't know about.”
View markup
<figure class="rec-card">
<blockquote class="rec-card__quote">“…”</blockquote>
<figcaption class="rec-card__person">
<span class="rec-card__name">Logan Hartline</span>
<span class="rec-card__role">Sr. Director, UX Center at HCA Healthcare</span>
<span class="rec-card__meta">Skip-level design leader · March 2020</span>
</figcaption>
</figure>Work item#
The index-row link that lists case studies: ordinal, title + one-line summary, tags, and a corner arrow. The whole row is a single link — one tab stop per case study.
View markup
<li class="work-item" data-cursor="View">
<a class="work-item__link" href="work/league.html">
<span class="work-item__index">01</span>
<div class="work-item__body">
<h3 class="work-item__title">League</h3>
<p class="work-item__desc">Implemented League's healthcare platform…</p>
</div>
<div class="work-item__meta"><span class="tag">Research</span></div>
<span class="work-item__arrow" aria-hidden="true">…</span>
</a>
</li>data-cursor="View" labels the custom cursor for pointer users only — the link text carries the meaning.Timeline#
Career history as a rail of entries: period, role line, one-sentence note. Dates are uppercase labels; roles carry the display weight.
AUG 2024 — PRESENT
League — Principal Product Design Engineer
Rolled League's platform out to SCAN Health, Geisinger & EmblemHealth.
MAR 2023 — APR 2024
Bicycle Health — Principal UX Designer
Led UX design & research for the nation's #1 telehealth program for opioid use disorder.
View markup
<div class="ab-timeline">
<div class="ab-tl__item">
<p class="ab-tl__year">AUG 2024 — PRESENT</p>
<p class="ab-tl__role">League — Principal Product Design Engineer</p>
<p class="ab-tl__note">Rolled League's platform out to…</p>
</div>
</div>Capability pills#
Bordered pills for disciplines and mediums, and the dotted list variant for skill inventories. Like tags, they're informational text — not controls.
- Product Strategy
- Design Systems
- Interaction Design
- Usability & A11y
View markup
<span class="ab-cap">Product Design</span>
<ul class="about__capabilities">
<li>Product Strategy</li>
</ul>Theme toggle#
A true role="switch": aria-checked mirrors the light state, the accessible
name flips with it, and the choice persists to localStorage and boots before first paint.
The one in this page's top bar is live — the specimen below is presentational.
View markup
<button class="theme-toggle" id="themeToggle" type="button" role="switch"
aria-checked="false" aria-label="Switch to light mode">
<span class="theme-toggle__track">
<span class="theme-toggle__thumb">
<svg class="theme-toggle__icon theme-toggle__icon--sun" …></svg>
<svg class="theme-toggle__icon theme-toggle__icon--moon" …></svg>
</span>
</span>
</button>| Key | Action |
|---|---|
| Space / Enter | Toggle theme; aria-checked and label update |
Skip link#
First element in every page's tab order. Visually parked off-canvas until keyboard focus lands on it, then it drops in as an accent pill. Press Tab from this page's address bar to meet the real one.
View markup
<body>
<a class="skip-link" href="#top">Skip to content</a>
…Loader#
The intro veil: mark plus progress seam. It's aria-hidden, dismisses on load with a 3.5s failsafe so it can never strand a visitor, and skips its theatrics entirely under reduced motion.
Cursor#
A dot-and-ring cursor that trails the pointer and grows a label over annotated targets
(data-cursor="View"). Strictly progressive enhancement: it mounts only for
hover: hover + pointer: fine devices, is aria-hidden,
and never replaces visible affordances on the element itself.
View markup
<div class="cursor" id="cursor" aria-hidden="true">
<div class="cursor__dot"></div>
<div class="cursor__ring"></div>
<span class="cursor__label" id="cursorLabel"></span>
</div>
<!-- annotate any target -->
<a data-cursor="View" href="…">…</a>Marquee#
The client-logo ribbon. On the site it drifts continuously and speeds up with scroll velocity; the track is duplicated so the loop is seamless, and the whole strip is aria-hidden (the client list exists as real text elsewhere). Static here — the drift belongs to the homepage.
alt; the drift is GSAP-driven and suspended under reduced motion.