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.
2025-03-16 12:00:00
A little over a year ago, I wrote a book called You Deserve a Tech Union. It’s a short book that describes the labor movement sweeping the tech industry, and shows that you — yes, you — can be part of it. That you deserve to be part of it.
Here’s what it looked like when it first launched:
But it doesn’t look like that any more. Now, You Deserve a Tech Union has a whole new look.
And here it is.
You might remember that not long after the book launched, my publisher closed its doors. Shortly after that, I reacquired the rights to the three titles I’d published with them. As part of that rights transfer, I agreed to remove the publisher’s branding from any books I planned to keep selling, and to make them look like, well, mine.
And after several months of work, I’m delighted to say that’s exactly what I’ve done: the redesign of You Deserve a Tech Union is finished, and I couldn’t be happier with it. What’s more, it’s available for purchase anywhere books are sold.
Prefer pixels to paper? Here’s a peek at the updated ebook:
Even cats love it.
Sorry for all the photos; I’m just really, really excited. As you might imagine, it’s been a heavy few months on several fronts. But throughout it all, working on this refresh has been a steady source of joy. I love looking at the finished product, and I hope you do too.
Much of that joy came from the folks who helped make this redesign happen. First and foremost, Ron Bilodeau turned my ramblings into InDesign magic, and expertly shepherded this book through production. Many of the authors affected by my former publisher’s closure fielded my questions while they were trying to rescue their own books, and I’ll always be grateful to them for that. Jason Santa Maria gave me some invaluable help as I designed the new cover, and throughout the entire process. And of course, She patiently weighed in on updated proofs, new cover ideas, and just generally made everything possible by being her typical wonderful, supportive self. Every book takes a village, and this little refresh is no exception.
And I do mean refresh. If you bought the earlier version of You Deserve a Tech Union, this is essentially the same book. It’s not a new edition. But at the same time, it’s much more than a simple visual update. The book’s design has changed, sure, but I’ve also made a host of tiny fixes throughout: typos have been fixed, outright errors corrected, and the book’s index has been completely redone.1 All that’s to say I’ve been thinking about this as a refinement of the original version, and an important one at that.
Even with all those updates, this little book hasn’t changed in one important way. It still argues that you — yes, you — are a tech worker. It also argues that by standing with your fellow tech workers, you can build a tremendous amount of power together. And then, it shows you how to do just that.
I just don’t have the words to say how excited I am by this latest version, and what it’s turned out to be. And now that it’s out in the world?
I can’t wait to see where You Deserve a Tech Union goes next. Where you’ll take it next.
Thank you, as always, for reading.
That last one’ll have my mailbox overflowing with invitations to all the cool parties. I just know it. ↩︎
This has been “Refresh.” a post from Ethan’s journal.