Colophon
My work is making silicon inspectable. It would be inconsistent to ship a portfolio that hides how it runs, so this page names every engine, what single job it does, and the constraints that actually shaped the architecture.
Rendering and framework
The default is zero client JavaScript. Everything past that default is an explicit decision.
Next.js
16.2.9
App Router. Every route is a React Server Component until a file opts out, so a page of prose ships no component JavaScript at all. Interactivity is declared where it is needed rather than assumed everywhere.
React
19.2.4
The server and client component split is the load-bearing decision on this site. Content renders on the server; only the labs, the palette, and the motion layers cross into the browser bundle.
TypeScript
5, strict
strict: true everywhere. An ESLint no-restricted-imports rule fences off lib/, so the TAP state machine, the fault model, and the cost model cannot import React or Next. That keeps the simulation logic portable and testable on its own.
Tailwind CSS
4, CSS-first
There is no tailwind.config file. The whole design system lives in one @theme block: colors, accent channels, radii, and typography scale are CSS custom properties, readable in devtools and by the compiler alike.
MDX
3.1.1
Long-form writing compiles through @next/mdx with remark-gfm, so essays stay in version control as text next to the components that render them.
Vitest
4.1.9
Unit tests cover the parts where being wrong is silent: the IEEE 1149.1 finite state machine, its session wrapper, the fault propagation model, and the cost curves.
Motion and 3D
GSAP owns the scroll. Framer Motion owns component state. The boundary between them is structural, not stylistic.
Scroll layer
GSAP 3.15.0 with ScrollSmoother and ScrollTrigger wraps the document in a smoother wrapper and drives scrolling with a transform, at a smoothing factor of 1.2. Nested scrollers keep native wheel and touch input, and mobile viewport-height changes are ignored so pinned sections do not jump when the URL bar hides.
Component layer
framer-motion 12.42.2 handles what GSAP should not: enter and exit transitions, spring-damped progress, staged reveals, and the guided sequences inside the labs. It also supplies the reduced-motion hook that the whole site branches on.
The constraint worth writing down
ScrollSmoother works by writing a transform onto its wrapper. Per the CSS spec, a transformed element becomes the containing block for its fixed-position descendants, so anything using position:fixed inside that wrapper quietly stops being fixed to the viewport and starts scrolling away with the content. There is no error and no warning; the layer simply drifts. Every persistent layer therefore mounts outside the smoother in the root layout:
Second-order effect 01
position:sticky goes inert for the same reason. The labs page uses plain document flow instead of a sticky description column, because a sticky column would have worked only for reduced-motion visitors, who bypass the smoother entirely.
Second-order effect 02
CSS scroll-driven animation never activates. The smoother pins the wrapper and writes an explicit pixel height onto the root element, so a root scroll timeline reports a null current time. The reading progress bar uses the framer-motion scroll hook instead, which tracks the same source as the rest of the site.
3D, and the flag in front of it
The Spline runtime renders the authored WebGL hero, with three.js pinned as the renderer beneath it. The 3D path is behind a build-time flag and off by default: a WebGL runtime can take down a tab on some GPUs, and a hero is not worth that risk. A hand-drawn SVG hero always renders as both poster and fallback, the Spline scene cross-fades over it only after its load event fires, and any load error falls back permanently for the session. Desktop widths and motion-permitting visitors are the only ones who ever request the scene.
Simulation engines
Nothing here is a general-purpose library doing five things adequately. Each engine owns a single layer of the interactive labs.
Deterministic lifecycles
The IEEE 1149.1 TAP is a real 16-state machine, so it is modeled as one instead of being approximated with booleans. The statecharts live in lib/, drive the labs through @xstate/react 6.1.0, and are covered by unit tests.
Graph rendering
State graphs and pipeline topologies, with node and edge rendering under direct control so a TAP state and a pipeline stage can share one visual language.
Layered layout
Positions in the fault-injection pipeline are computed, not hand-placed. The topology can gain a stage without anyone redrawing a diagram.
Timing traces
TCK, TMS, TDI, and TDO render in the waveform notation hardware engineers already read, synchronized to the same cycle counter the state graph uses.
Quantitative curves
Cost, uncertainty, and threshold curves for the compute-economics lab. The sweep math runs in a Web Worker so dragging a slider never blocks the main thread.
WebGL hero
The Spline runtime renders the authored 3D scene. three.js is pinned directly alongside it, with @types/three, so the renderer version underneath is a decision in package.json rather than whatever a transitive resolution happens to pick.
Performance posture
Nothing expensive loads because it exists. It loads because it is about to be seen.
Viewport-gated mounting
Each lab sits behind an IntersectionObserver with a 700px rootMargin, so its engine starts fetching about one screen before it is visible, then the observer disconnects. Until then the bezel shows an idle placeholder rather than an empty box.
Dynamic import with ssr disabled
The simulators and the WebGL hero are browser-only, so they load through next/dynamic with ssr set to false. Skipping the server render avoids paying to generate markup that hydration would immediately discard.
The scroll engine is code-split too
GSAP and its plugins are imported inside an effect, not at module scope, so no scroll machinery sits in the initial bundle for a visitor who never scrolls past the hero.
Failure is a first-class state
Every lab is wrapped in an error boundary with a retry. Retry mints a fresh lazy component instead of reusing the old one, because React caches a rejected dynamic import permanently: a naive retry would rethrow the same chunk error forever.
The smoother has a deadline
If GSAP has not initialized within 1200 ms, the page unlocks native scrolling rather than staying pinned to its first viewport. An optional enhancement is never allowed to become a hard dependency.
Fonts are variable cuts
Inter, Space Grotesk, and JetBrains Mono load through next/font with display swap. One variable file per family covers the full weight range instead of six static instances.
Accessibility and motion
The preference is checked before the expensive systems are constructed, not applied on top of them once they are already running.
The smoother never mounts
SmoothScrollProvider checks prefers-reduced-motion before it constructs anything and returns early. Reduced-motion visitors get plain native scrolling with no transform on the document, which also means every fixed layer behaves conventionally for them.
Reveals render static
The Reveal primitive returns a plain container under reduced motion. Content is never gated behind an animation that will not play.
Labs collapse their timing
Animated edges, pulses, and staged delays are disabled rather than merely shortened, and CSS keyframes carry motion-reduce:animate-none so nothing loops in the background.
Capability gating, not just preference
The WebGL path also checks for webgl2 support, device memory, core count, and coarse pointers. Low-tier and mobile devices get the lightweight native hero, and the 3D scene fades in only after its load event fires.
Visualizations carry text
Diagrams and glyphs expose a role of img with an aria-label, lab controls are real focusable elements with labels and aria-current state, and a skip link opens the tab order.
Open-source stack
Versions are the ranges declared in package.json. If a claim on this page is wrong, the repository is the tiebreaker.