April 2026. The site is live at acaldwell.dev, running on Cloudflare Workers via SvelteKit’s adapter-cloudflare. It has 19 UI components, 600+ CSS variables, a three-layer token architecture, 23 blog posts in mdsvex, and an OKLCH color system derived from 5 hex values I picked in October 2016.
This is the production version. Not a wireframe, not a prototype, not a CodePen. A shipped product that represents 10 years of accumulated decisions — some intentional, most discovered along the way.
The Stack
| Layer | Choice | Why |
|---|---|---|
| Framework | SvelteKit + Svelte 5 | Runes mode. Scoped styles. No runtime CSS-in-JS. Native <dialog>. |
| Blog | mdsvex (.svx) | Markdown + Svelte components. Shiki for syntax highlighting. |
| Tokens | Style Dictionary v4 | DTCG format. Light + dark configs. 600+ generated CSS variables. |
| Deployment | Cloudflare Workers | SSR at the edge. D1 database available. Global distribution. |
| Fonts | Cabinet Grotesk + Manrope + Fira Code | Display, body, mono. Three families, each with a clear role. |
| Colors | OKLCH throughout | Two-layer palette. @property for animation. light-dark() for themes. |
This stack wasn’t chosen at once. It accumulated across the migration from Next.js, documented in real time across the other posts on this blog.
Why Svelte, Not Next.js
I used Next.js at Meshery. This portfolio was on Next.js before the Svelte rewrite. The switch wasn’t a reaction to Next.js. It was about what I wanted from a personal project.
Svelte 5’s runes mode gives me reactive state without hooks, derived values without useMemo, and component composition without children prop threading. The $state and $derived primitives map directly to how I think about UI state. I don’t need to explain to the framework when things should re-render.
Scoped <style> blocks mean I write CSS, not a CSS-in-JS abstraction. The styles for a Button component live in Button.svelte, scoped automatically, no class name hashing, no runtime injection, no build step beyond what Svelte already does. For someone who started with legacy.css in 2016, this is the most natural way to write component styles.
The <dialog> element with showModal() gives me a modal with free focus trapping, Escape handling, and backdrop click — no portal, no focus-lock library, no useEffect cleanup. This is the web platform doing what libraries used to do.
The Token Architecture
Three layers, each with a clear job:
Primitives define values with no opinion about context. --palette-aurora-50: oklch(52% 0.20 245) is a color. --spacing-4: 1rem is a distance. These are facts, not decisions.
Semantic tokens assign meaning. --color-primary-main references a primitive but adds context: this is the primary action color. --bg-body is the page background. Semantic tokens change between light and dark themes; primitives don’t.
Component tokens encode design decisions. --button-primary-background resolves through --color-primary-main to a primitive, but it carries the specific decision that primary buttons use this color. Component tokens are the contract between the design system and the component implementation.
The bridge variable — --color-primary-main: var(--primary) in globals.css — connects the runtime ColorSlider (which sets --primary dynamically) to the static token chain. Without this, component tokens always resolve to the static aurora palette regardless of the active slider position.
CSS 2025
The site uses features that the State of CSS 2025 survey confirmed as production-ready:
@layer reset, tokens, components orders the cascade. Reset styles have the lowest specificity, component styles the highest. No more specificity battles between token defaults and component overrides.
light-dark() collapses what used to be two separate [data-theme] blocks into single declarations. --bg-body: light-dark(oklch(0.98 0.003 252), oklch(0.14 0.012 252)) defines both modes in one line, controlled by color-scheme on the root element.
:has() enables P8 (parent temperature response) without JavaScript. When you hover a blog entry, sibling entries dim their category label chroma: .editorial:has(.piece:hover) .piece:not(:hover) .piece-category { --cat-chroma: 0.025; }. The most-used and most-loved CSS feature in the 2025 survey.
@starting-style animates the modal and colophon dialog from display: none to visible — the entry transition that previously required JavaScript timing hacks.
@property registers custom properties as typed values so the browser can interpolate them during transitions. Without @property --surface-chroma { syntax: '<number>'; ... }, the chroma-on-hover effect would snap instead of animating.
color-mix(in oklch, ...) creates the dialog backdrop: color-mix(in oklch, oklch(0.08 0.008 252) 85%, transparent). OKLCH-based transparency without rgba.
The Editorial Layout
The homepage moved from a card-based zone grid to editorial masonry — CSS columns with column-span: all for featured content. Blog entries flow naturally into columns with varying heights, creating a magazine/newspaper feel instead of a dashboard.
Temperature mapping (P2) shifted from zone background tinting to category label typography. Each blog entry has a small category label — SECURITY, SYSTEMS, AI & ML — whose color carries the temperature hue. Rose for frontend, Navy for systems, Teal for cloud, Red for security. The label’s chroma is animated via @property --cat-chroma and responds to :has() sibling interactions.
Light mode uses a cool H:252 base — not the warm H:65 (Stone) that earlier iterations used. Warm light backgrounds read as parchment/notebook, which feels dated. The cool base makes the temperature zone colors more deliberate: a warm label on a cool background is clearly a design choice, not an accidental tint.
The 19 Components
All built from scratch in Svelte 5. No headless library, no MUI, no Shadcn. Each component consumes its Style Dictionary component tokens — zero hardcoded values.
Simple: Button, Badge, Alert, Card, Avatar, Divider, Breadcrumb, Progress, Skeleton Medium: Input, Table, Pagination, Slider, RangeSlider Complex: Tabs, Accordion, Tooltip, Dropdown, Modal, Toast
Design patterns that distinguish them from generic scaffolding:
- Flat interactions —
filter: brightness()on hover, nottranslateY(-1px) - Top accent borders on alerts —
border-top: 2px solid, not Shadcn’sborder-left: 4px - Solid primary buttons — no gradient fills (accepted as Stratos style)
- Keyboard navigation — full arrow-key support on Tabs, Accordion, Dropdown
Snippettypes for composable children —{@render children()}, not<slot>
The Arc
| Year | What I Built | What I Learned |
|---|---|---|
| 2016 | CodePen resume | CSS custom properties. 5 colors that would last. |
| 2017 | Second CodePen | Iteration > starting over. Typography has ratios. |
| 2017 | React CodePen | Components are a thinking tool. Frameworks change; mental models persist. |
| 2022-23 | Meshery contributions | Design systems solve consistency. Documentation is design. |
| 2023 | Meshery Storybook | Component APIs are contracts. Theming is infrastructure. |
| 2025 | OKLCH color system | Perceptual uniformity. Chroma hierarchy. Shifted hues. |
| 2026 | Production portfolio | Everything ships. The palette survived. Nothing else did. |
The 2016 CodePen and the 2026 portfolio share exactly one thing: the five colors. #0b1d51, #797596, #a1869e, #bbada0, #d1c6ad. Everything else — the framework, the build system, the layout strategy, the typography, the animation model, the deployment target, the component architecture — was replaced at least once.
The colors survived because they were the only decision I made in 2016 that was about what I liked rather than what I knew how to do. Technical knowledge expires. Aesthetic instinct compounds.
This is Part 7 of 7 in the Resume Evolution series. The beginning is Part 1 — The Beginning: A CodePen Resume (2016).