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.
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.
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".
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).
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.
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.
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.