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.
2025-03-10 12:00:00
Note: This post gets into recent American political events. If that’s not your cup of tea, or if that’s a stressful topic for you, please feel free to skip this one.
Two weeks ago, 18F was abruptly and suddenly shut down. Around midnight on March 1st, the entire staff was informed via email that they were being placed on “administrative leave.” No chance to notify clients, no chance to wind down the flurry of projects they’d staffed — no chance for friends and coworkers to say goodbye to each other. Just one email, sent under cover of night, telling roughly one hundred hardworking people they weren’t welcome in government any more.
This isn’t my story to tell; I’d left 18F before this happened. Instead, you should read an open letter from the affected 18F staffers; Christian Crumlish and Matt Henry both wrote valedictions for 18F, as did Lindsay Young, 18F’s former executive director and longest-serving employee.
What I do want to share is that before it was shuttered, 18F’s regular all-hands meeting was called Team Coffee, held at the end of every other Friday. Now: if you noticed “all-hands meeting” next to “end-of-day Friday,” your eyebrow may have raised the same way mine did during my first week. I’ve worked in this industry for well over two decades, and I’ve rarely been excited about an all-hands meeting. But I always, always looked forward to Team Coffee. This was mostly because the humans of 18F were just, well, darned good people to spend time with. But it was also expertly run and moderated. It was a meeting filled with and focused on work updates, sure, but smartly peppered with jokes and visits from kids and cooing at adorable animals. Also? Jokes. (Good jokes? Well, sometimes. But the groaners were my favorite.)
Almost exactly one week after 18F’s closure, I heard that the entire team decided to hold another Team Coffee, right on their usual schedule. I found out about the call a few hours beforehand — if I’d had any conflicts on my calendar, I would’ve canceled them.
I almost started crying when I entered the video call, and seeing my old coworkers’ faces again; I definitely started crying when team leads started reading out names of their reports, and thanking them for their service. I just thought I was going to have so much more time with these people: more projects, more discussions, more good/terrible jokes at Team Coffee. I’m trying to be grateful for the time I did have with them, while also wishing it hadn’t been taken away from me. From all of us.
But at least for a couple hours last week, I got to sit alongside my coworkers one more time. Whatever happens next — for me, for them, for all of us — I’ll be grateful for that.
Here’s to what was, and to what’s still to come.
This has been “Commencement.” a post from Ethan’s journal.
2025-03-06 12:00:00
I don’t even remember when this redesign started. The Sketch file (shh; shhhhhhh) says I started it all the way back in January of 2024, well over a year and one non-collapsed government ago. I did find some paper sketches older than that, so who knows. But for the better part of a last year, I’d been picking at the new design here and there, and just idk like happily lazing away amid colors and textures and type and weird layout ideas. Figured I’d get it out the door when it was, y’know, time to get it out the door.
Well, I suddenly find myself with a lot of time on my hands, so it seems to be time to get it out the door.
How’s that old saying go? “When the world’s on fire and the fascists are at your door, stop waffling and just finish your redesign.”
Pretty sure it’s something like that. More or less.
As it happens I quietly pushed the redesign live last Friday, and then hopped offline for a few days with old friends.1 Since getting back online I’ve spent the last few days fixing bugs, finding new bugs, and rethinking some old design ideas. The site’s not finished, mind you — when will it ever be? — but it feels like something I can build upon.
And this isn’t something I typically say, but I find myself happy with so many bits of the new design: I’m excited to have a little blogroll again; I like the bloop-y effect when I’m scrolling down the journal on wider screens; I didn’t think I’d be able to build any of the more angular pieces of this design, and I’m glad (proud?) I figured them out.
Anyway. World’s on fire, but I have a clean(er) slate in front of me, and a whole bunch of ideas I’m excited to try. Might as well get to work.
A couple colophon-y things, which I’ll lodge here until I have an actual colophon:
This has been “Parker.” a post from Ethan’s journal.