2025-06-16 12:00:00
I’ve got some new design work to announce, but it’s still very much in progress. Here’s where it stands right now:
For the last couple months I’ve been helping out over at Unbreaking, a project aiming to document the attacks on American institutions waged by the current far-right administration. Unbreaking’s cofounders have tremendous amounts of expertise in journalism, mutual aid, and technology, not to mention large-scale volunteer-powered documentation projects. My contributions have been focused almost entirely website-shaped: I worked with the team on the new site’s design and branding, and I’ve been helping out with new design tasks Unbreaking grows.
The site’s been live for some time now, but it’s changed quite a bit since launch. And with everything else the team is working on, I can’t wait to see how it’ll change next.
If you’re curious about Unbreaking’s work, here’s a short excerpt from their About page:
The United States is experiencing institutional collapse at a speed and scale that are difficult to understand, especially through feeds and updates that atomize our attention. We believe that mapping the damage done and its human costs — and the pushback and resilience work already underway — is necessary groundwork for building and retaining political agency.
In our work at Unbreaking, we’ll help orient and ground our communities in clear and rigorously cited explanations of what’s happening to our government and why it matters. To that end, we’re building a set of pages that will serve as a backgrounders for the issues we cover. Each page is written by and for ordinary people, and conveys essential context, a sense of what’s happened so far, and what countermoves are in play.
I don’t know about you, but this really speaks to how I’ve felt since the end of last year: every day feels like it brings a new set of terrible headlines, with crises unfolding on several dozen fronts at once. Keeping up with the issues I care about has felt nigh on impossible, as I’ve spent most of my days feeling awash in news coverage, social media posts, and concerned texts from friends. My attention has absolutely felt atomized.
From what I’ve seen, Unbreaking’s working to bring a little more sense to the senselessness. Their current homepage has a list of the issue pages they’ve published so far. If you’re wondering about the hollowing-out of the federal workforce, the latest attacks on transgender healthcare, or the threats to the postal service, there’s an exhaustively researched page that describes what’s happening.
And I do mean researched. Their About page makes it clear that this isn’t original reporting. Instead, it’s something I’ve found almost more valuable: it’s a team of researchers, community leads, writers, and editors reviewing countless sources, pulling together relevant updates and threads into each issue page. The first time I read the Medicaid page, I sighed at my desk — I mean, it’s a page filled with horrors and harms, but it felt like I finally had a measure of clarity amid all the chaos. Unbreaking is making sense of what’s happening, at a time when those in power are investing in confusion.
That’s all to say that I think Unbreaking is doing vital work, and it’s felt incredibly good to contribute to that work. The team also needs more hands on deck. If you’re looking for something to do right now, I can strongly recommend getting involved. It’d be good to have you on board. Besides, I bet we can build a little more clarity together.
This has been “Unbreaking.” a post from Ethan’s journal.
2025-05-25 12:00:00
Hey, here’s a thing that’s fun to say:
Happy fifteenth birthday, responsive web design!
Well, fun and weird. You know how it goes.
But yes, it’s true: the original “Responsive Web Design” article was published fifteen years ago. Fifteen whole entire years. As the kids say: dang.1
I did a little retrospective when the article turned ten, so I don’t think I need to do another one. I’ll just say again that I coined the phrase “responsive design” well over a month before the article was published, at a conference in early April of that year. Thankfully Mandy Brown, then an editor at A List Apart, heard the talk, and told me it really needed to be an article. Everything sort of followed on from that.
And frankly, I am still surprised by what exactly followed on from that. Ever since the article came out, I tell folks that all I did was meet a publishing deadline; responsive design only became a thing because of the people who got excited about the concept, experimented with it, wrote about it, and moved the idea forward. People like you.
Fifteen years on, it feels like responsive design’s become something truly mundane — something that’s just kind of expected. Is there still work to do? Sure. Absolutely. Urgently, even. But the idea of designing sites that work across mobile, desktop, and whatever else is just kind of seen as the thing you’re supposed to do. And to be clear, that’s not a vindication of any ideas I might have had fifteen years ago. Rather, it’s a testament to just how compelling the Web’s own flexibility is. We’ve stopped designing against a truly fluid design medium, and now we see it as an asset — something we can design both with and for. And I don’t know about you, but I think that’s pretty neat.
If you can’t tell, I don’t talk to many kids. ↩︎
This has been “Responsive web design turns fifteen.” a post from Ethan’s journal.
2025-05-17 12:00:00
Okay, sorry, I just need a hole to scream into. I’ll be done in a minute.
[inhales]
If you read about the current crop of “artificial intelligence” tools, you’ll eventually come across the word “hallucinate.” It’s used as a shorthand for any instance where the software just, like, makes stuff up: An error, a mistake, a factual misstep — a lie.
I have a semantic quibble I’d like to lodge.
Everything — everything — that comes out of these “AI” platforms is a “hallucination.” Quite simply, these services are slot machines for content. They’re playing probabilities: when you ask a large language model a question, it returns answers aligned with the trends and patterns they’ve analyzed in their training data.1 These platforms do not know when they get things wrong; they certainly do not know when they get things right. Assuming an “artificial intelligence” platform knows the difference between true and false is like assuming a pigeon can play basketball. It just ain’t built for it.
I’m far from the first to make this point. But it seems to me that when we use a term put forward by the people subsidizing and selling these so-called tools — people who would very much like us to believe that these machines can distinguish true from false — we’re participating in a different kind of hallucination.
And a far worse one, at that.
Well, taking into account any subsequent “fine-tuning” of the model that humans may have performed. ↩︎
This has been “Hallucinating.” a post from Ethan’s journal.
2025-04-22 12:00:00
Note: This post is very long, and it gets into some arcane-looking code stuff. If that’s not your thing, feel free to skip this one!
Since leaving 18F, I’ve spent much of the last month thinking about what’s next for me work-wise. And of course, I’ve been doing no small amount of doomscrolling. But amid all of that, I’ve been remembering how to treat my website like a worry stone. When things get too stressful I’ll spend some time sanding down a few rough edges in the redesign. A tightened-up header layout here, a little more spacing there — doing a bit of design doesn’t fix a single thing about the world, but it gives me a little space to breathe, and to get my feet back under me.
In one recent bout of site work, I spent some time trying to get better Open Graph images working on my site. If you’ve ever shared a link in Slack or on social media, and the post expands out into a lovely preview image, that’s an Open Graph image.1 If a page has an <meta property="og:image" content="image.jpg" />
tag somewhere in its <head>
, then image.jpg
will be shown on link previews on social media sites, and on services like Discord and Slack.
Previously, every single preview image on my site was, well, a big weird photo of my face. Which, my feelings about my face aside, was a little unhelpful! There’s no indication of where you’d land if you followed the link. What site even is this? What’s the page title? These are both valid questions! But instead of answering them, my site was basically just HEY HERE’S A GENERIC PHOTO OF THE AUTHOR.
Ahem. Anyway.
After a bit of sketching in Sketch (shh; shhhhhhh) I came up with a new design for my link previews:
Nothing especially fancy, mind. I thought it’d be nice to pull in some colors and textures from my site’s new design, while showing information about the linked page: namely, its title and, if it’s a blog post, the date it was posted. I also wanted to be able to replace the photo of my face if I needed to: if a page or post specified a custom image for its social media previews, I’d use that instead of my ugly mug.
That was the brief, anyway. After spending about a week on this problem, I eventually settled on a solution using an open source software package called ImageMagick. I’m going to write up what I landed on, mainly as a reference for myself. But if the post is useful to others, well, awesome.
Before I explain what’s happening here, I want to underline three things.
First and foremost, I’m a novice with both ImageMagick and Eleventy. I’m confident there are much, much better ways to do what I’m doing; in fact, I bet I’m doing some things incorrectly! What follows is simply an explanation of what I’ve gotten working — I am not even remotely suggesting there are any “best practices” at play here. (This website is, as you may know, held together with naught else but twine and anxiety, and I wouldn’t have it any other way.)
Second! I’m afraid to say I can’t provide any tech support for this post. This is partly related to the first point: some of this is still opaque to me, so I’m not confident I can be helpful to you. But I also need to be clear about my limits here. If something isn’t working, I hope some of the resources I’ve linked are useful to you.
And finally, I want to acknowledge there are other — and arguably, much, much better — options for tackling this with Eleventy. There’s an excellent-looking plugin named eleventy-plugin-og-image
that will (you guessed it) generate Open Graph images. I started playing around with it, helped in large part by Robb Knight’s excellent blog post about the plugin. But after experimenting a bit, it didn’t feel like the right option for me. For one, running the plugin on my wizened laptop took quite a long time; but even more importantly, I found myself wanting more visual flexibility than the plugin seemed to offer. That’s why I started investigating other ways to generate the images.
In other words, dear reader: everything that follows, I did to myself.
Okay! Let’s dive in.
I’m going to start with where I landed. This is the ImageMagick2 command I’m using to generate one social media image:
magick background.png \
\( foreground.jpg -gravity center -crop 600x630+0+0 -geometry +0+0 +gravity \) -composite \
\( -size 500x216 -background transparent -gravity SouthWest -font YWFTVermont-Light -pointsize 48 -interline-spacing 5 -fill white caption:'The World-Wide Work.' -geometry +652+336 \) -composite \
\( -background transparent -gravity SouthWest -font Untitled-Sans-Bold -pointsize 20 -fill white label:'10 MARCH 2024' -geometry +652+570 \) -composite \
-layers flatten merged.png
A quick editorial sidebar, if you’ll permit me.
ImageMagick is powerful, and it’s fast as hell. It can edit, convert, and even generate images, all from the command line, and all in a matter of seconds. With all of that said, ImageMagick’s been around for well over three decades. In that time it has accumulated a dizzying number of features, many of which feel like entire products in their own right. And to top it all off, I personally found the documentation nigh on incomprehensible, which I assume is because of the software’s age and complexity. It felt like it took me ages to get to a working solution, trawling through forum posts and documentation for code samples I could start editing.
This is all to say that I absolutely see what the code looks like. I realize it looks like a robot’s cry for help.
But! At the end of the day, that behemoth magick
command only does four things:
background.png
image, which will act as the [checks notes; shuffles papers; coughs] background for our social media images. Everything created by the following lines? It gets layered on top of it.foreground.jpg
down to a specific size before positioning it over the left half of the background image, thereby covering up the photograph of my face.-layers flatten merged.png
) takes all the layers we generated, and smooshes them down into a single merged.png
file.Now, full disclosure: I wrote another thousand words about how this all works, reread it, and realized absolutely nobody needed to read a slightly more in-depth review of all those little flags and switches. Can anyone ever truly know what -crop 600x630+0+0 -geometry +0+0
means?3 Does anyone care about my opinions on using +gravity
as a reset to an earlier -gravity
statement? The answer to both of those questions is “no,” but you already knew that.
That’s why I decided to slam the entire section into a details
element. For any sickos who want to dive into a line-by-line breakdown, here you go.
I know it’s only been a few paragraphs, but let’s take one more look at the whole command:
magick background.png \
\( foreground.jpg -gravity center -crop 600x630+0+0 -geometry +0+0 +gravity \) -composite \
\( -size 500x216 -background transparent -gravity SouthWest -font YWFTVermont-Light -pointsize 48 -interline-spacing 5 -fill white caption:'The World-Wide Work.' -geometry +652+336 \) -composite \
\( -background transparent -gravity SouthWest -font Untitled-Sans-Bold -pointsize 20 -fill white label:'10 MARCH 2024' -geometry +652+570 \) -composite \
-layers flatten merged.png
Phew. Still a lot! But let’s look at what each of those five lines are doing, one by one.
Here’s the first line:
magick background.png
Simple enough. I’m invoking the magick
command, and feeding it the an image as an input. Eventually, this background.png
file will be, well, the background for my social media images. The results of the next few lines are going to be layered on top of it.
With that said, let’s create our first layer! And it has an important job to do: namely, some of my posts have custom social media images. When and if they do, I want to load in that custom image, and drop it into place over the left-hand side of the background image — effectively covering up my photo in the background.
Here’s what that looks like in practice:
\( foreground.jpg -gravity center -crop 600x630+0+0 -geometry +0+0 +gravity \) -composite \
Oh my goodness, sorry, I know: it’s a lot. But let’s see if we can decipher this a bit.
Within those parentheses, we’re grabbing foreground.jpg
as another input image, and then performing a series of ImageMagick operations on it. In each step, we’re using those operators — gravity
, crop
, geometry
, and composite
to modify the image as we go. And as Greg Dunlap kindly helped me understand when I was first researching this, those commands are run in the sequence they’re specified: do this, then this, and then this.
Again, I admit that this one line looks like a lot: ImageMagick’s syntax is, frankly, arcane. But amid all the wild punctuation, this line does four things:
foreground.png
down to 600px by 630px, relative to the center of the image. (-gravity center -crop 600x630+0+0
)background.png
image. (-geometry +0+0
) And because we’ve already sized it to 600×630 dimensions, it perfectly covers up my ugly mug.+gravity
doesn’t modify the image. Rather, it’s a kind of reset, one that prevents some of our edits from spilling over into some of the lines below it.-composite
operator signals that the results of everything inside the parentheses should be treated as one single image layer.Here’s what it looks like so far:
So! With our first line deciphered, let’s move onto the next:
\( -size 500x216 -background transparent -gravity SouthWest -font YWFTVermont-Light -pointsize 48 -interline-spacing 5 -fill white caption:'The World-Wide Work.' -geometry +652+336 \) -composite \
As you might’ve guessed, this generates the big serif’d headline displayed on our images. We’re using a similar structure as we did in the previous line: ImageMagick will perform all of the operations inside the parentheses and then — thanks to our closing -composite
— treat the result as a single layer to be stacked onto our background.
Inside the parentheses, things look a little different from the preceding line. We’re not cropping existing images; instead, we’re using ImageMagick itself to draw something onto our layer.
-size 500x216 -background transparent
creates a 500px by 216px canvas with — you guessed it! — a transparent background.-gravity SouthWest
aligns whatever’s inside that canvas to its bottom left corner. (The “southwest” corner. Get it? 🧭)caption
command, which draws some text inside the canvas we specified. All the operators that precede it are passing along typesetting instructions for how that text should render: choosing a typeface (-font YWFTVermont-Light
), a font size (-pointsize 48
), leading (-interline-spacing 5
), and a color. (-fill white
)-geometry +652+336
to define the pixel coordinates for our new layer, positioning it relative to background.png
’s dimensions.And here’s how things are shaping up:
A quick note here: the caption
interface allows you to render a string of text, and to have it reflow automatically within a given box. However, it can only apply one font to a given string — so if your page title has, say, a book title that you’d like to italicize, you’re outta luck. There is an alternative to caption
called pango
, which does allow for more text formatting options. However, I couldn’t figure out how to get it to bottom-align text to its container, which caption
does do quite nicely. So I’m sticking with caption
for now, even though I lose the italicized text.
(If you were able to read that paragraph without having your eyes start to cross, you’re doing much better than I did.)
Anyway! Two layers done, one more to go! Here’s the next line in our monster command:
\( -background transparent -gravity SouthWest -font Untitled-Sans-Bold -pointsize 20 -fill white label:'10 MARCH 2024' -geometry +652+570 \) -composite \
This one might feel familiar to you. Just like the line preceding it, we’re generating some text here for our blog post dates, and giving ImageMagick some instructions for how and where it should be typeset. The main difference here is that instead of caption
to render our text, we’re using label
, which is a different (and simpler) interface for generating type.
Now that we’ve generated our label, we turn to our final line:
-layers flatten merged.png
That line tells ImageMagick to take all of the layers generated by the previous lines — the background image, the optional foreground image, the text layers for our page title and our date — and to flatten
them into a file named merged.png
. And here’s the final result:
Now that we’ve written that command, our work’s finally done.
…well, okay, no. It’s not really done.
I’ve created one ImageMagick command to create one single image. But I now need to transform it into a template: one that can automatically create OpenGraph images for every page on my website, and then make those images available to Eleventy.
If you’d like to see it, here’s the code for the solution I landed on. And here’s a quick rundown of what’s happening.
<head>
, which causes the image to appear in social media previews.I’ll note that this is a slightly streamlined version of the workflow. In order to guarantee that every OpenGraph image’s filename was unique, I referenced some work of Bob Monsour’s to append md5 hashes to the generated images; I’m also using sharp to compress the images a bit. I’ve stripped all that code out in order to simplify the examples — and to, well, make a long story a little less long. I’ll also say that I’m the last person who’d describe this workflow as elegant. It requires Eleventy to build the CSV file before the node script can run, which then makes the files available to Eleventy.
But! I’m still really happy with what I managed to get working, imperfect though it is. Besides, this is just the current state; I’m sure it’ll change as I learn more about these technologies. And for now, this setup feels like it works well for my little website: I’ve got some nice customization options, it’s very fast, and I’m delighted with how the new images look.
Anyway, whew. Thanks so much for reading through all of that — and thanks, as always, for reading.
There used to be competing standards for these images, namely Facebook’s Open Graph specification and Twitter’s own “Cards”-style previews. But since Twitter turned into a literal Nazi bar, I don’t worry anymore about making previews for that platform. Fascists don’t deserve pretty links. ↩︎
This post assumes you’re running ImageMagick 7, and not the “legacy” ImageMagick 6. I haven’t tested my work on the older syntax. (Yes, there are two versions of ImageMagick; yes, both are still available; yes, this made searching for support…challenging.) ↩︎
I mean, after all this research, I kinda do. But I wouldn’t recommend the experience to anyone else. ↩︎
This has been “Magick images.” a post from Ethan’s journal.
2025-04-03 12:00:00
As the horse once said, everything happens so much. In my country, a far-right administration is waging war on students, researchers, activists, workers, immigrants, and every marginalized population you can name, all while strip-mining what remains of our already insufficient social safety net.
In short, the world’s on fire. So I want to suggest that — now more than ever — you need a union.
A union is many things: it’s an engine of democracy in your workplace; it’s a path to a contract; it’s a community. A union also provides a critical layer of protection and stability. And that’s something you could use right now.
Worried about layoffs amid the rollout of astronomical tax hikes that could crater the economy? A union contract can’t prevent a layoff, but it can determine the kind of layoff you get: how much advance notice you’re given, how much severance you’re given, whether or not you can be a priority rehire, and more. Sick of being an at-will employee that can be dismissed for any reason? Unionize, and fight for a contract with “just cause” protections. Want a say over whether or not you’re surveilled at work? Unionize, and get a contract. Tired of “generative AI” in the workplace? You can guess what I’m going to say here.
A union can be useful for other fights, too. When graduate student Rumeysa Öztürk was abducted by federal agents last week, her union took to the streets before launching a nationwide pressure campaign. Last year, Cornell University suspended Momodou Taal for participating in campus protests. In response, his union went to war: they held multiple mass actions on campus, eventually forcing the university to the bargaining table to fight Taal’s suspension. When people talk about “worker power,” this is quite literally what it looks like: it’s built by workers, standing together not just to win a better form of work, but also to protect each other.
You and your coworkers deserve that power, and you can start building it today. Have a quiet conversation with a coworker — discuss what’s working, what isn’t, and what you’d like to change. Invite some trusted work friends to join you at some organizer training. Start a book club at work, and leave plenty of time for informal discussion. The best time to start forming a union was five years ago; the second best time is right now. And there are so many ways to get started.
Nobody is coming to save us, except us. And together, we’re enough.
This has been “It’s time to organize.” a post from Ethan’s journal.
2025-03-25 12:00:00
I recently rereleased You Deserve a Tech Union as a self-published title, featuring a brand new design. For those who didn’t catch the announcement, here’s how the book looks now:
I’m just so happy with how it came out. I hope you like it, too.
As I mentioned in the announcement, redesigning the book was part of the agreement I made when I reacquired the rights to my books: basically, if I wanted to keep selling any of my books, they’d need to look like my books. Easy enough, right?
Well, there’s a terrible trick here. You see, Jason Santa Maria created the design for my former publisher’s titles, and I just loved the visual language he came up with. I’ll never forget opening my box of Responsive Web Design, picking up one of the copies, and thinking just how right everything felt as I leafed through its pages. Frankly, there was no chance I’d improve on Jason’s work; but just as importantly, I didn’t want to erase it.
This new version of You Deserve a Tech Union attempts to split that difference. If I’ve done my job right, this design should feel like a noticeable refresh, while still connecting with where the book began. The color is the primary carryover, and the interior layouts haven’t changed too much.1 The biggest departures were on the cover and — most importantly — the type. Here’s a short excerpt from the book’s colophon:
The text is set in Tiempos Text and Untitled Sans, both by Klim Type Foundry. The headlines and book cover use Cambon Condensed by General Type Studio, as well as Klim Type Foundry’s Söhne.
Longtime readers will be unsurprised to see some of those names — heck, maybe some of you are rolling your eyes a bit. But, hey, look: if someone told me that I could only use Klim typefaces for the rest of my days, I’d die happy. Tiempos Text, Söhne, and Untitled Sans are all old favorites of mine, and they’re often starting points for me when I’m doing type exploration. And together, I think they made the book’s new interior shine.
Here’s how the text of You Deserve a Tech Union used to look, and how it looks now:
Want a closer look? Well, here you go.
Ron Bilodeau and I spent a couple rounds finessing the text, but we were making small adjustments around the edges. The new type just felt solid from the very first pass.
But! We still needed a display face. Enter General Type’s Cambon, whose flared terminals I always feel I could get lost in. For the book’s more prominent numbers, like those in the lead-in for every chapter, Cambon’s condensed family felt like a natural fit — especially after seeing how well it sidled up to the Söhne-set chapter titles.
Cambon Condensed worked a treat on the table of contents, too. I really do love how it sits alongside Tiempos and Untitled Sans.
Here’s a side-by-side of the two tables of contents2, just to home in on what has (and hasn’t) changed.
And of course, Cambon and Söhne felt like such a good pairing on You Deserve a Tech Union’s new cover. This is the one area of the book where the departure’s more radical than what came before — but if you squint, I think you can see where it all started.
I still deeply love the original design, and always will: after all, it’s my first memory of the finished book. But I feel like this new design’s something that still stands on its own, while still feeling like it’s of a part with what came before.
If you’re interested, you can read more about this new refreshed version, or buy your copy of You Deserve a Tech Union anywhere books are sold.
Thanks, as always, for reading.
This has been “You deserve a new book design.” a post from Ethan’s journal.