Classic Scoundrel
May 2026
A complete single-player card game for the browser — game logic, rendering, and interaction built with TypeScript and Canvas 2D. No engine, no frameworks; just the platform.
SF Bay Area · Software Engineer
Ten years building for the web — design systems, product engineering, and applied AI. I care about interfaces that are fast, accessible, and genuinely pleasant to use.
I'm a product-minded software engineer currently doing integration engineering at Emcor. Before that I spent four years at Intuit as a Software Engineer 2 on Payments — working across a microservice architecture, design system, and plugin ecosystem — and two and a half years at Tailored Brands leading front-end development, where I helped design and develop the code powering 100+ A/B tests that drove over $50 million in projected incremental revenue in a single year.
My background is a little unusual: dual degrees in Computer Science and Cognitive Science from UC Santa Cruz, with upper-division coursework in machine learning, AI, and human–computer interaction. I've been working with generative models since the Deep Dream era — back when "neural style transfer" meant a Torch script and a lot of patience.
I also care deeply about the details most people never notice: I maintain reference
sites for the prefers-reduced-motion and prefers-color-scheme
CSS media queries, because accessibility is a feature, not a checkbox. This very page
practices what I preach — semantic HTML, no frameworks, full keyboard support, and it
respects both your motion and color-scheme preferences.
Things I've built and maintain, in my own time.
May 2026
A complete single-player card game for the browser — game logic, rendering, and interaction built with TypeScript and Canvas 2D. No engine, no frameworks; just the platform.
February 2021
A reference site for the prefers-reduced-motion CSS media feature — what it is, how to honor it, and how to test it. Written for working developers who want to get accessibility details right.
January 2026
Companion reference for the prefers-color-scheme media feature — the right way to build dark-mode-aware interfaces that respect user preference.
March 2023
A 2D roguelike survival game running on a bespoke game engine, including enemy AI. An exercise in building everything from the ground up — and in making bad decisions fun.
July 2025
Find your pitch from what you see in the sheet music. Set the clef, key signature, and note position on a rendered staff, then hear the pitch. Built for my own choir practice.
October 2026
Give words a little color. A self-contained English sentiment highlighting library — no APIs, no models, no dependencies — with a live playground and a downloadable Chrome extension.
October 2026
Companion reference for the prefers-reduced-transparency media feature: when frosted-glass interfaces should go solid for people who ask for less transparency. Live glass demos flatten on request, with the honest note that no browser implements the query yet.
October 2026
Companion reference for the prefers-reduced-data media feature: designing for metered connections and data-saver mode. The demo swaps a 592 KB hero image for a 19 KB one when data is tight, and the hero itself is the first example.
October 2026
Companion reference for the prefers-contrast media feature: honoring requests for more, less, or custom contrast. A live sample card restyles itself to each value; the feature has been Baseline in every major browser since 2021.
Interactive demos from my twice-weekly LinkedIn series — web accessibility features on Mondays, underused browser features on Fridays.
September 2026
Poke a live DOM sandbox and watch mutation records stream in as they happen. Toggle the observer config (childList, attributes, characterData, subtree) and see exactly what each option captures — plus a polling-vs-observer comparison for detecting third-party widgets.
September 2026
Watch elements light up as they cross your visibility threshold, tune threshold and rootMargin live, then see the killer use case: enrolling visitors in an A/B test only when the experiment zone actually enters their viewport — so your measurements count viewers, not page loads.
September 2026
Feel the difference with your own keyboard: a broken modal that lets focus wander off behind the overlay, next to a fixed one that moves focus in, traps Tab inside, and returns focus on close — plus a live document.activeElement readout and a :focus-visible comparison.
October 2026
A three-step flow where upcoming steps stay visible but dimmed. With inert off, keyboard focus wanders into steps you haven't reached, logged in red. Flip it on — one React prop per step — and the future steps leave the tab order and the accessibility tree entirely.
September 2026
Browse on a metered connection and this page listens: it reads the prefers-reduced-data signal and swaps a 592 KB hero image for a 19 KB one. A data-saver override lets you try both paths, with a live readout of what the browser reports and the matchMedia pattern to copy.
October 2026
Click a card and watch it morph into the detail view: the View Transitions API turns a hard DOM swap into an animated transition with one function call. Compare the plain cut against the transitioned version, try the slide and zoom presets, and see the free fallback for browsers without support.
October 2026
Drag a button around and watch a tooltip stay glued to it with zero positioning JavaScript: anchor-name, position-anchor, and anchor() handle the measuring, while position-try-fallbacks flips the tooltip automatically near edges. Includes the old hand-rolled scroll-listener version for comparison.
October 2026
One bulletproof <picture> snippet covering art direction, AVIF/WebP/JPEG format fallback, and resolution switching, with a live readout of the exact file your browser picks as you resize. Includes the file-size table that makes the format argument for you.
October 2026
I hand-built a game engine in vanilla JavaScript, then let AI refactor it without changing how it plays. Eight real before/after diffs: four bugs fixed, a silent typo, dead globals, new touch input, and the "inverted" keyboard controls explained. The playable cleaned build is embedded.
October 2026
Week 2 of the build log: canvas-drawn buttons, containers, and modals with keyboard focus navigation, plus a one-scale-factor layout system. Sixteen automated UI tests pass. The frozen week 2 build is embedded.
October 2026
Week 3 of the build log: a Slay-the-Spire-style act structure. Three free-choice rooms per act, three room types, three shuffle-bag bosses, then endless acts. Forty-four automated tests pass. The frozen week 3 build is embedded.
October 2026
Week 4 of the build log: spawn telegraphs so hits feel fair, a best score and time that persist in localStorage, and synthesized WebAudio sound effects with zero audio files. The frozen week 4 build is embedded.
October 2026
A tuner you read instead of hear: match the note on your sheet music, hear your exact pitch. Built for my own choir rehearsals. The live app is embedded, with camera mode coming next.
October 2026
One attribute kills the stale-sizes problem: with loading="lazy" and sizes="auto", the browser measures the image's laid-out slot and picks the right srcset file. The same image in four containers, each with a live readout of which file it pulled, plus the honest caveats: lazy-only, no Safari, and it picks the file, not the crop.
October 2026
What ARIA is, where it came from, the five rules, and four working demos: a live announcer, an aria-pressed toggle, proper tabs, and an accessible-name inspector.
October 2026
Week 1 of a Thursday shader series: two WebGL fragment shaders that run on any DOM element. Fire is layered noise scrolling upward through a black-red-orange-yellow ramp; drip pulls pixels down with a traveling melt wave and falling droplets. Rasterized via SVG foreignObject, 60fps, reduced-motion aware.
October 2026
Click any pixel on your screen and get its hex color: new EyeDropper().open() in four lines of JavaScript, no permission prompt, no screenshot plumbing. Includes a swatch history with click-to-copy and an honest fallback for browsers without support.
October 2026
What the government's ICT Testing Baseline actually is: Section 508 is the law, WCAG is the definition, and the Baseline is the shared step-by-step test script. Try five real baseline checks (keyboard, focus, alt text, contrast, labels) with live pass/fail verdicts.
October 2026
Sixteen dots scattered by the stylesheet alone: random() rolls each dot's position, size, rotation, and hue per element, fresh on every load. Includes a live support readout and a JavaScript fallback for browsers that do not have it yet.
October 2026
Your browser introduces you to every website you visit. This is the whole introduction, catalogued: every navigator property with a live readout from your own browser, the history of each one, honest compatibility from MDN data, and try-its for the properties you can actually poke. Open it on your phone and compare.
October 2026
The screen reader status zone from Classic Scoundrel, isolated in the simplest card game there is. War drawn entirely on canvas, with every flip, war, and score spoken through a hidden live region. Cover the canvas and play it by ear.
October 2026
Three fixes for Classic Scoundrel: screen-reader announcements for card plays and room changes, a game loop that pauses when the tab hides, and seeded runs. Type a seed and watch the deck shuffle into the exact order the game deals, every time.
October 2026
Negation split across two paragraphs used to flip nothing. Now the previous block lends its ending as context, dropped emoji reactions retry instead of vanishing, and the extension's behavior inside editable regions is certified in a real browser.
October 2026
Every three levels the game grants a permanent upgrade for the rest of the run, announced in the HUD. Plus auto-pause when the tab hides, and a playfield that re-fits on resize without advancing the game.
October 2026
Week 2 of a Thursday shader series: a chromatic glitch shader that runs on any DOM element. One headline, three offset texture samples drifting in and out of registration like a misprinted page. Rasterized via SVG foreignObject, 60fps, reduced-motion aware.
October 2026
A 100vh hero looks right on desktop, then gets cut off on iPhone as the toolbar moves. Switch the hero between vh, svh, lvh, and dvh and watch live viewport readouts as you scroll: the one-line fix is 100vh as fallback, 100dvh on top.
October 2026
The Classic Scoundrel card game as a command line game: play all 44 cards in a browser terminal, then see the same pure logic drive a tiny React component. One core, two shells, no rewrites.
November 2026
CSS could never style a parent from its child, so card highlights and form warnings needed JavaScript. Three live examples show :has() doing it in the stylesheet, plus a support readout from your own browser.
November 2026
A top-down herding game modeled on aerial sheep-yard footage: push sheep, goats, pigs, and chickens through the gates into their target pens. Each species has its own AI, flocking, wandering, rooting, or scattering.
I'm currently exploring what's next. If you're building something interesting — especially at the intersection of the web and AI — I'd love to hear about it.