← Back to writing

The Final Form: Production Portfolio

From CodePen to Cloudflare Workers. SvelteKit, 19 components, Style Dictionary tokens, OKLCH color system, editorial masonry layouts, and CSS 2025 features. The 10-year arc of a portfolio site.

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

LayerChoiceWhy
FrameworkSvelteKit + Svelte 5Runes mode. Scoped styles. No runtime CSS-in-JS. Native <dialog>.
Blogmdsvex (.svx)Markdown + Svelte components. Shiki for syntax highlighting.
TokensStyle Dictionary v4DTCG format. Light + dark configs. 600+ generated CSS variables.
DeploymentCloudflare WorkersSSR at the edge. D1 database available. Global distribution.
FontsCabinet Grotesk + Manrope + Fira CodeDisplay, body, mono. Three families, each with a clear role.
ColorsOKLCH throughoutTwo-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, not translateY(-1px)
  • Top accent borders on alerts — border-top: 2px solid, not Shadcn’s border-left: 4px
  • Solid primary buttons — no gradient fills (accepted as Stratos style)
  • Keyboard navigation — full arrow-key support on Tabs, Accordion, Dropdown
  • Snippet types for composable children — {@render children()}, not <slot>

The Arc

YearWhat I BuiltWhat I Learned
2016CodePen resumeCSS custom properties. 5 colors that would last.
2017Second CodePenIteration > starting over. Typography has ratios.
2017React CodePenComponents are a thinking tool. Frameworks change; mental models persist.
2022-23Meshery contributionsDesign systems solve consistency. Documentation is design.
2023Meshery StorybookComponent APIs are contracts. Theming is infrastructure.
2025OKLCH color systemPerceptual uniformity. Chroma hierarchy. Shifted hues.
2026Production portfolioEverything 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).