Skip to main content

Michael Warner

Web Developer

Personal Projects

Here are some personal projects that I’m pleased with. Most but not all are related to web development; I have other interests like physics, writing, history, and music, and occasionally I find ways to combine them.

App: Electromagnetic Lorentz Transformations

This web app demonstrates how the electric and magnetic fields change depending on the motion of the observer who’s measuring them. If you’re not a physics person, you may not understand that (or care about it), but you can still have fun playing with the sliders and the camera!

It’s a React app, built on Next.js with TypeScript. I used zustand to manage state, three.js for the 3D visualization, Tailwind CSS for the styling, Playwright for testing, and MathJax to display equations.

I’m rather happy with how this one turned out. It’s feature-packed, performant, quite usable, and fully accessible. Best viewed on a wide screen, so that the visualization and the controls fit side-by-side.

Paper and Blog Series: Covariant Electrodyadics

This is a long physics paper (okay, a book) that I’ve been slowly writing for many years. It started as self-study notes, but I learned to love LaTeX, and I suppose things have gotten a little out of hand. I think it’s pretty good, but I’m not a physicist, so, grain of salt.

I once reworked portions of it into a series of blog posts called “Covariant Electrodynamics the Easy Way”: Part I extends the concept of the vector triple product to Minkowski spacetime, and Part II introduces dyadic notation (sorry if that’s all Greek to you). I’ve thought about adding a third part, but I haven’t gotten around to it. By the way, that old blog is built with Jekyll and uses MathJax for the equations.

Tailwind CSS Plugin: tailwind-grid-inner

tailwind-grid-inner is a Tailwind plugin for making grids with “inner” borders only, like a tic-tac-toe board. That might not sound like something you’d need a special plugin for, but it’s actually rather difficult to pull off, especially if you don’t know how many items will be in the grid ahead of time (because then you can end up with an “unbalanced” grid whose last row doesn’t fill all columns).

Using the plugin is simple: just put class="grid-inner-3" on your grid-wrapper (instead of the class="grid grid-cols-3" that you’d use for a normal grid in Tailwind), and you’ve got a three-column grid with inner borders. Under the hood, however, it uses pseudo-elements, negative margins, and complex :nth-child() and :nth-last-child() selectors to get the borders right while maintaining equally sized grid-items.

Below is an example of the plugin in action. Note that the “empty” slots in the unbalanced final row don’t have borders, while the “overhanging” items in the penultimate row do (with bottom-borders of the correct width). Also note that it switches between a 3- and 4-column layout when the window crosses the 1024px breakpoint; the required responsive logic is handled automatically with syntax like class="grid-inner-3 lg:grid-inner-4".

TypeScript Utility: el-is

elIs() is a tiny, zero-dependency TypeScript function for narrowing a DOM element’s type by its tag-name. It’s mainly useful for tag-types without a dedicated interface, like <section>. If you want to test whether an HTMLElement corresponds to a <section> tag with “vanilla” TypeScript, then you can’t use syntax like if (el instanceof HTMLSectionElement), since HTMLSectionElement doesn’t exist! Instead, you’re stuck with something like if (el.tagName === 'SECTION'), but now you’re in string-literal territory where case matters and typos are likely, and you get no type-narrowing (el remains a generic HTMLElement in the true-branch).

My elIs() type guard solves these problems. The syntax is if (elIs(el, 'SECTION')). Although you still must supply a string literal for the tag-name (or a variable whose type is a string literal), TypeScript will force the string to be a valid tag-name, and you’ll get autocomplete suggestions. Additionally, in the true-branch, el will be narrowed to HTMLElement & { tagName: 'SECTION' }, which makes for improved developer experience. Tag-names for SVGElement and MathMLElement are supported, too (not just HTMLElement).

Paper: A Little Prelude and the Scherzo That Almost Wasn’t

Once upon a time I was a musicologist. This paper, published in the Journal of Musicological Research, explores a Bach quotation I discovered in a Beethoven piano sonata. It’s not legal for me to post the full article here, but I’m entitled to send a limited number of copies to individuals, so if you’re interested you can ask me (send me a message on LinkedIn). The DOI is 10.1080/01411896.2013.791800.

Composition: Sienna Rag

I wrote this ragtime piece in honor of a Doberman named Sienna. In the video embedded below, you can hear it in MIDI format and follow along with the score.