Hello there!

I'm Charles, a senior frontend engineer at Michigan Creative in Ann Arbor. I build websites and web apps for University of Michigan units, and I care most about accessibility, speed, and giving the people who own the content a site they can run themselves.

principles-of-pain-science/
src/
studio/              Sanity v3: editor-authored curriculum
migrations/          Drizzle ORM → Cloudflare D1
e2e/                 Playwright, scripted auth reset
docs/
  architecture.md    chapter gating · quiz progress
Currently building: LMS platform for the Chronic Pain & Fatigue Research Center, University of Michigan

Selected work

Four recent projects. The rest are in the archive.

in development
Astro 6
Cloudflare Workers
D1 · Drizzle
Better Auth
Sanity v3
Diagram of chapter gating. A participant watches the video and takes the quiz through Astro running on Cloudflare Workers, which serves static pages and server routes for the dashboard, gating checks, progress endpoints, and auth checks. Sanity v3 holds the chapters, quizzes, and media, edited by research staff. The Worker checks a per-email allowlist through Better Auth and KV, and reads and writes progress (video threshold met, quiz passed, chapter complete) in D1 through Drizzle. The next chapter unlocks when the video threshold is met and the quiz is passed.

Principles of Pain Science

A learning platform for U-M's Chronic Pain & Fatigue Research Center. A chapter unlocks once a participant has watched enough of its video and passed the quiz, and that progress is stored per person in D1 so the team can report on it. The researchers write and edit all the curriculum in Sanity themselves, working from a training manual I wrote.

  • Chapters unlock on watch time and quiz results
  • Progress stored per user with Drizzle and D1
  • Vitest for the gating rules, Playwright for the full participant flow
Read the case study
in production
Node.js
Express 5
Google Cloud Run
Helmet
rate limiting
Diagram of the request path. A browser running the chat widget, which holds no Maizey credential, sends questions to a proxy on Cloud Run that checks a CORS allow-list, a per-client API key, and a per-client rate limit before forwarding to Maizey, the University's LLM service, which has its own upstream rate limit. Below, three separate credential tiers: the Maizey service key stays on the server, per-client API keys can be issued, rotated, or revoked, and an admin key covers management endpoints only.

Roybal Chat

A small Node proxy between the Roybal Center's public chat widget and the University's internal Maizey LLM service. The Maizey credentials stay on the server. Each client site gets its own API key, CORS origin, and rate limit, so adding a new site takes a config change and a new key. It runs on Cloud Run and scales to zero when idle.

  • Maizey credentials never reach the browser
  • Per-client keys, CORS allow-list, and rate limits
Read the case study
in production
Astro (hybrid)
React
Cloudflare D1
Mapbox GL
Sanity
The dashboard's heatmap on the Detroit city page: an address search and layer menu above a map of census tracts shaded by reachable destinations, with a popup showing a Sufficient accessibility rating for the Detroit Institute of Arts.

Public Transit Accessibility & Disability Dashboard

A public dashboard that scores transit accessibility for people with disabilities across 85 U.S. cities and 99 transit systems, on four dimensions: accommodation, adjacency, availability, and affordability. Visitors pick a state and city, then look up an address on the city page to see tract-level scores on a Mapbox heatmap. The scores live in D1 and get refreshed through a repeatable import path each time the research team hands off a new dataset. Disability advocates use it in conversations with local officials.

  • Live for 85 U.S. cities
  • Mapbox geocoding with a Census fallback
  • Scores at the tract, agency, and city level
Read the case study
Multi-generational. Gen 2 in production, Gen 3 in development
11ty
Sanity CMS
Tailwind CSS v4
Alpine.js (next tracker)
The Pain Guide symptom tracker start screen: a sidebar of tracking categories and a Welcome panel with icon tiles for symptoms (sleep, pain, mental health, cognitive function, energy and fatigue) and behaviors (steps, physical activity, pacing).

Pain Guide Platform

Pain Guide began as a design I made for a symptom-tracking site and grew into the platform behind about ten clinical trial sites, including BACPAC, PASC, sickle cell disease, and lupus. I led the move from markdown to Sanity, and I'm now building a shared base for the next sites, three of which are launching now.

  • Each site has its own symptom domains and auth model
  • Every site keeps its own Firebase backend, so one change never touches another trial's data
Read the case study
See the full archive: 23 more higher-ed projects

How I work

Accessibility

WCAG 2.1 AA is my minimum. pa11y-ci and axe run on every deploy, and on recent builds I've been testing against 2.2 as well.

Performance

Most of my sites are static, built with 11ty or Astro and served from Cloudflare. I set Core Web Vitals budgets at the start of a project and check against them as I build.

Servers where they're needed

Most pages stay static. When a project needs a server, like keeping an LLM key out of the browser or tracking a student's progress through a course, I add a small, typed one.