portfolio · playground
Interaction engine · Built from scratch
This whole page is a toy. That's the point.
Every panel here is fully wired: drag the X and Y fields in the inspector, resize the frame from its bottom-right corner, switch the accent color, watch two fake cursors dodge your mouse in the side gutters. This was the previous version of this entire portfolio — built from scratch in hand-written HTML, CSS, and JavaScript, with zero dependencies and no build step.
What this used to be
For most of 2025 the whole site looked like this — a Figma-style canvas complete with a file bar, a layers panel, a draggable inspector, comment pins, and two ambient cursors labelled "Recruiter" and "Fellow designer" that wandered the margins and flinched away from the real one. It was fun to build, and it proved I could build it.
A hiring audit called it correctly: on a portfolio, the chrome was competing with the work for attention, and the work should win every time. So the case studies and the resume moved to a plain, fast, accessible site — and the canvas engine moved here, opt-in, reframed as what it actually is: an interaction engine, not a costume.
What's actually running here
Nothing below is a mockup. Open the inspector and try it.
- Pointer-capture drag scrubbing on every numeric field, the resize handle, and the hand tool's canvas pan
- Full keyboard support — arrow keys nudge each value, Home resets it, all exposed as ARIA spinbuttons and switches
- Focus management on the mobile drawers: background content goes inert, focus moves in and returns to the trigger on close
- prefers-reduced-motion is read on load and re-checked live through the Motion toggle — both fully disable transitions and cursor drift
- The whole canvas state — position, rotation, radius, width, theme, accent, toggles, zoom — persists to localStorage and survives a refresh
- A dedicated print stylesheet strips every panel back to plain content
- A noscript fallback collapses the floating panels into static, readable blocks if JavaScript never loads
- The two ambient cursors run a real collision check against the mouse, throttled to every 80ms, and dart away when you get within 130px