Thoughts From Eric Archive

Accordion Rows in CSS Grid

Published 4 years, 4 months past

Another aspect of the meyerweb redesign I’d like to explore is the way I’m using CSS Grid rows to give myself more layout flexibility.

First, let’s visualize the default layout of a page here on meyerweb.  It looks something like this:

A page layout diagram showing a header stretching across the top of the page, a footer stretching across the bottom of the page, and three columns of content between them: two sidebars to either side, and a main content column in the middle.

So simple, even flexbox could do it!  But that’s only if things always stay this simple.  I knew they probably wouldn’t, because the contents in those two sidebars were likely to vary from one part of the site to another — and I would want, in some cases, for the sidebar pieces to line up vertically.  Here’s an example:

A page layout diagram showing a header stretching across the top of the page, a footer stretching across the bottom of the page, and three columns of content between them. There is a main content column in the middle which stretches full-height between the header and footer. In the right sidebar, there are three boxes, labeled 'navigation', 'feeds', and 'categories', arranged in that order, top to bottom. In the left sidebar, there is a single box labeled 'archives' that isn’t as tall as the main content column. Its top edge is vertically aligned with the top edge of 'feeds', the second box in the right-hand sidebar.

That’s the basic layout of archive pages.  See how the left sidebar’s Archives lines up with the top of the Feeds box in the right sidebar?  That’s Grid for you.  I thought about lumping the Feeds and Categories into a the same grid cell (thus making them part of the same grid row), which would have meant wrapping them in a <div>, but decided keeping them separate allows more flexibility in terms of responsive rearrangement of content.  I can, for example, assign the Feeds to be followed by Archives and then Categories at mobile sizes.  Or to reverse that order.

More to the point, I also wanted the ability to place things along the bottoms of the sidebars, down near the footer but still next to the main content column, like so:

A page layout diagram similar to the previous diagram, with header and footer, main content column, and the 'archives' box in the left sidebar; and 'navigation, 'feeds', and 'categories' in the right sidebar. This figure adds 'box1' and 'box2' to the bottom of the left sidebar, and 'box3' to the bottom of the right sidebar. The bottom edges of 'box2' and 'box3' are vertically aligned with the bottom of the main column.

An early design prototype for the blog archives put the “Next post” and “Previous post” links in some of those spots, before I moved the links into the bottom of the main content column.  So at the moment, I don’t have anything making use of those spots, although the capability is there.  I could cluster content along the tops and the bottoms of the sidebars, as needed.

But here’s the important thing, and really the point of this article: I’m not rewriting the row structure and grid cell assignments for each page type.  There’s a unified row template applied to the body on every page that uses the Hamonshū design.  It is:

grid-template-rows: repeat(7,min-content) 1fr repeat(3,min-content);

The general idea here is, the first seven rows are sized to be the minimum necessary to contain content inside those rows.  This is also true of the last three rows.  And in between those sets, a 1fr row that takes up the rest of the grid container’s height, pushing the two sets apart.

In the simplest case, where there’s just a header, main content column, and a footer, with nothing in the sidebars (the layout has three columns, remember), the content will fill the rows like so:

A page layout diagram showing a header stretching across the top of the page, a footer stretching across the bottom of the page, and a main content column. There is open space to the sides of the main content column.

Here’s the Grid CSS to make that happen:

header {grid-row: 1; grid-column: 1 / -1;}
footer {grid-row: -2; grid-column: 1 / -1;}
main {grid-row: 2; grid-column: 2;}

Thus: The header fills all of row one.  The content expands row two from its placement in the center column.  The footer fills all of the last row, which is specified via grid-row: -2 (because grid-row: -1 would align its top with the bottom edge of the grid container).  There’s no more content, so all the other min-content rows have no content, so their height is zero.  And there’s no leftover height to soak up, so the 1fr row also has a height of zero.  Seems like a lot of rows specified to no real purpose, doesn’t it?

But now, let’s add some sidebar content to columns one and three; that is, the sidebars.  For example, you might remember this layout from before:

The same as figure 2. To recap: A page layout diagram showing a header stretching across the top of the page, a footer stretching across the bottom of the page, and three columns of content between them. There is a main content column in the middle which stretches full-height between the header and footer. In the right sidebar, there are

Given this setup, we can’t just assign the main content column to grid-row: 2 and leave it at that — it’s going to have to span rows.  Really, it needs to span all but the last, thus ensuring it reaches down to the footer.  So the CSS ends up like this:

header, footer {grid-row: 1; grid-column: 1/-1;}
footer {grid-row: -2;}
main {grid-column: 2; grid-row: 2/-2;}
nav {grid-row: 2; grid-column: 3;}
.archives {grid-row: 3 / span 3;}
.feeds {grid-row: 3; grid-column: 3;}
.categories {grid-row: 4; grid-column: 3;}

And as a result, the rows end up like this:

The same as the previous diagram, but in this case the main content column is taller. Purple dashed lines show where the grid lines are placed in this layout; in particular, they make it clear that the 'navigation', 'feeds', 'categories' boxes in the right-hand sidebar are placed in separate grid rows, and the 'archives' box in the left-hand sidebar spans three grid rows.
Grid-line visualization courtesy the Firefox Web Inspector.

The first set of min-content rows are all gathered up against the bottom of the top part of the layout, and the second set are all pushed down at the bottom.  Between them, the 1fr row eats up all the leftover space, which is what pushes the two sets of min-content rows apart.

I like this pattern.  It feels good to me, having two sets of rows where the individual rows accordion open to accept content when needed, and collapse to zero height when not, with a “blank” row in between the sets that pushes them apart.  It’s flexible, and even allows me to add more rows to the sets without having to rewrite all my layout styles.

As an example, suppose I decided I needed to add a few more rows to the bottom set, for use in a few specialty templates.  Because of the way things are set up, all I have to do is change the row template like this:

grid-template-rows: repeat(7,min-content) 1fr repeat(5,min-content);

That’s everything. I just changed the number of repeats in the second set of rows.  All the existing pages will continue on just fine, no layout changes, no CSS changes.  In the few (currently hypothetical) pages where I need to put a bunch of stuff along the bottom of the main content column, I just plug them in using grid-row values, whether positive or negative.  It all just works.

The same is true if more rows are added to the first set, for whatever reason.  Everything gets managed in a single CSS rule, where you can add rows for the whole site instead of having to write, track, and maintain a bunch of variants for various page types.  (Subtracting rows is harder without causing layout upset, but could still be done in some scenarios.)

As a final note, you’re probably wondering: Is that one 1fr row actually necessary to get a layout like this one?  Not really, no.  Let’s take it out, like this:

grid-template-rows: repeat(11,min-content);

What happens as a result is the rows that aren’t directly occupied by content (the ones that previously collapsed to zero height), but are still spanned by content (the center column), divvy up the leftover space the 1fr row used to consume.  This leads to a situation like so:

The same layout diagram as the previous figure, but with some of the grid rows placed differently.

To the user, there’s no practical difference.  Things go to the same places either way.  You just get the “extra” rows stretching out, instead of being pushed apart by the 1fr row.

I certainly could have left it at that, and it arguably would have been cleaner to do so.  But something about this approach doesn’t sit quite right with me; there’s a tickly feeling in the back of my instinct that tells me there’s a downside to this.  Admittedly, it could be a vestigial instinct from the Age of Floats; I doubtless have many things I still have not unlearned.  On the other hand, it could be something about Grid I’ve picked up on subconsciously but haven’t yet brought into full realization.

If I ever pin the tickle down enough to articulate it, I’ll update the post to include it.

Thanks to Kitt Hodsden and Laura Kalbag for their assistance with this article.


Forever and Ever

Published 4 years, 5 months past

We had been awake since one in the morning.  She had been unconscious since three in the morning.

We’d been taking turns sleeping with Rebecca in the nights leading up to that day, six years ago today, and on the final night it had been my turn.  I was woken by a sound, from her or by her I still don’t know, and I saw that she was awake.  And had vomited.  And still could barely move.  And I knew this was it.

Kat called the hospice nurse to come and give her the drugs to take away the pain, and we waited, sick with despair and doing everything we could not to show it.

We gently held her as she trembled.  We kissed her head through her curly hair, told her that we were here with her and that she could go, not to be afraid, we’d be there with her soon.  Lying to her, now, finally, so far past the point where truth was kind or helpful, telling her whatever we thought would take away even a tiny slice of the fear.  Shamelessly, literally shamelessly, weaponizing all the trust we had built up over the years by telling her the truth her whole life, to ease, if we could, the last hours of it.

Her eyes were wide, even for her.  They barely blinked.  We could still catch her attention, but it was so much harder now.  The trembling increased, and then, eyes somehow still wider, she started to claw frantically at her temples, trying to dig out the thing that was killing her.  Or just to somehow relieve the pain.

I had to restrain her, catch her small wrists in my hands, wrap my arms around her arms and shaking small body and brokenly tell her no, stop, I know, I know, I’m so sorry, we want it to stop too, I know, we love you.

The nurse was finally there and administered a lot of narcotics.  Not enough to kill her outright, but enough to take away the pain.  And consciousness.  I almost asked him to double the dose, or triple it, whatever was necessary to end things quickly and painlessly.  I stopped only because I realized the position that would put him in.  And that if it were an option, he’d already have offered it.

Waiting for the drugs to work, we read her her favorite stories, holding books up one at a time and asking if she wanted this one, this one, this one.  Her head would jerk in a prototype of a nod, and we’d open that book and read, taking turns, voices somehow as clear and steady as if we were doing normal bedtime stories.  I made a list as we went, because I knew I’d want to know later.

  1. Mike Mulligan and His Steam Shovel
  2. How the Sun Was Brought Back to the Sky
  3. Madeline
  4. Jump Rope Magic

Halfway through that last, the drugs finally took hold, and she slipped into unconsciousness.

Or really, semi-consciousness: some part of her was still aware of her surroundings.  Every time Kat shifted or drew away for a moment, Rebecca would whimper quietly, and only stop when she could hear and feel Kat reassuring her.  She only did so for me when I got up from the bed entirely.  I would assure her that I would be back in a minute or two, that I wasn’t going away for good.  That was enough to quiet her.  She trusted us so much.

Shortly before dawn, we started sending texts to loved ones.  This is it.  If you want to say goodbye, you need to come today.  She has hours left.  And asked them to spread the word, so others would know to come.

Just after dawn, at 7:22 in the morning, she crossed another threshold.

Happy birthday, Rebecca, we told her with quietly trembling voices.  You made it.  You really did.  You’re a great big six year old now.  We love you so much.

There was no response.

So many people came to see her one last time on her sixth birthday, drifted in and out of her room, a slowly shifting cast of characters supporting her, us, each other, themselves.  I don’t really remember how it felt then, barely remember who was there and who was not, but looking back now it’s like those movie scenes where one person is still, sharply in focus, while long-exposure blurs of people moving around them draw a contrast between what matters and what is incidental, between transience and permanence.

Sometimes, what is still is the most transient of all.

By late afternoon, there were signs the drugs weren’t enough, that she was going to start seizing despite everything.  Which in turn meant there was a risk she’d come back to a consciousness filled with nothing but incomprehensible pain.  We weren’t sure what we could do.  She’d been lying on her back all day, so nearly motionless, body flaring with fever, shallow breaths coming and going in a rhythm I kept falling into, the way you do when your child is in distress.

Kat looked across at me and said, She’s a side sleeper.

We rolled Rebecca onto her sleeping side, her right side, and her breath hitched, drew in slowly, and then flowed out deeply, contentedly.  Her breathing stayed slow and deep, the rhythm of sleep, comforting, except it gradually got slower and slower and slower.  Stretched out more and more over half an hour, eventually slow enough that when it stopped altogether, we almost didn’t notice.

We checked for pulse.  Breath.  Pulse again.  Nothing.  For a minute.  Two.

A sound came from my throat that I know now is the true sound of ultimate suffering — and as if startled by it, she drew in another breath, choking me off.

We begged her to stop fighting, told her it was okay to go, we loved her, we loved her, we loved her.

A few minutes later, pulse and breath stopped again.  And Rebecca was gone forever.

Rebecca, who we knew so well.

Rebecca, who we had barely gotten to know.

Rebecca, who we will never get to know.

Time has moved on, as we have, and yet some parts of us have not.  Some parts of us are caught there, six years ago today, able to do nothing but watch her slowly slip away, literally helpless to do anything except try to soothe her through the haze of drugs and thickening darkness with our words, our touch, our presence, our love.  Parts of us frozen in that passage, unchanging.

Like Rebecca is now, forever, and should not be.

When I was One, I had just begun. When I was Two, I was nearly new. When I was Three I was hardly me. When I was Four, I was not much more. When I was Five, I was just alive. But now I am Six, I’m as clever as clever, So I think I’ll be six now for ever and ever.

 — ‘Now We Are Six’, A.A. Milne


Better Image Optimization by Restricting the Color Index

Published 4 years, 6 months past

Let’s talk about image optimization.  There are a few images used in meyerweb’s new design, and while I wanted them to be pleasing to the eye, I also wanted them to be lightweight.  My rough goal was to not have the design elements (images plus CSS) be more than half the total page weight for a typical blog post, not counting any post-specific images like photos or diagrams.  Thus, if a typical blog post’s page weight was 500KB, I didn’t want the images and CSS to add up to more than 250KB or so.

Spoiler: I achieved my goal, but at the same time fell short.  What I had overlooked was custom fonts, which I’ll get to in a later post.

I found out that how you optimize images matters a whole lot.  Let’s consider one example: the spiral-like image (which, yes, is a quiet callback to past work) at the center of the previous-next links at the bottom of blog posts and archive pages.  After I extracted a full-resolution copy of that particular sketch from pages 13-14 of Hamonshū, Vol. 1 and did a little cleanup with filters and so on, it became a 1.4MB Acorn file.

The full-size image in Acorn.

(Side note: Acorn’s “Transparentomatic” filter was an enormous time-saver on this project — it made dropping out the page texture a breeze, and easily adjustable, without forcing me to create and retouch mask layers and whatnot.  Thanks, Flying Meat!)

With the image ready to be tested in-browser, I would use Acorn’s Web Export dialog to save it as a PNG.  The nice thing about this dialog is its built-in Resize feature, which let me keep the Acorn file at its native size (almost a thousand pixels on each side) and export it to the size I wanted — in this case, 200 pixels across.  I did this sort of thing a lot, because I tested a variety of images for every design element.

Once I settled on the image I wanted, I’d drop it on ImageOptim to optimize it.  This usually slammed all eight cores in my aged laptop’s CPU for a good few seconds, and resulted in up to 5% size savings.

That last paragraph probably looks like an indictment of ImageOptim, but wait!  It’s fully redeemed by the end of the post, by which time I will have indicted myself instead.

At this point, my spiral image had gone from a 1.4MB Acorn file to a 30KB PNG.  That’s pretty good, even if 30KB feels a tad bulky for a 200×200 image.  I just assumed, what with all the transparency and shades of color and all that, it wasn’t too far out of line.  But as the whole design started to come together, I discovered that when you added up all the illustration images on, say, the home page, I’d let image bloat sneak up on me in the worst way.  They were totalling close to a megabyte, and they’d all been through ImageOptim already.

I went back to Acorn to see if I could squeeze any more out of the file size, maybe convert some of the images to JPGs if they didn’t need the transparency.  As I flipped between file formats in the Web Export dialog, I noticed something I’d previously overlooked in the PNG export options: a bit depth slider.  I’d been saving the PNGs with no bit depth restrictions, meaning the color table was holding space for 224 colors.  That’s… a lot of colors, roughly 224 of which I wasn’t actually using.

When I clicked the “Index PNG Colors” checkbox and changed the slider until I started getting dithers or obvious color loss, then brought it up a notch or two, the difference was astounding.  Instead of a 30KB file, I got a 4.4 KB file.  Instead of saving at 75% the original size, it was now 11%.

Wait a second… how long has THAT been there?

So I went back through the directory with all my design elements and repeated the process.  I do have batch image processing software installed, but I elected to do this manually so I could pick the best color depth for each file by eye.  It could be that some would be okay at 4 colors instead of 8; others might need 16 or 32 to retain visual fidelity.  Fortunately for me, I only had a couple dozen images to go through, but it would have been worth it even at 10 times that many.

Once I’d gone through all the images and saved them with restricted color depth, my theme’s image directory was down to 242KB total.  The biggest of them, the separator wave illustrations, had gone from being ~150KB each to ~25KB each — all five of them together now totalled less than just one of them had before I did the color indexing.

The directory, well and fully smooshed.

At this point, I thought, “All right, let’s see what ImageOptim does with these.”  It squeezed them down even further, taking my total from 242KB to 222KB, a nine percent reduction.  Which is to say, the percentage savings I got on these already-small files was larger than I’d been getting on the much bigger files — plus, ImageOptim processed them quickly and with a minimum of CPU slamming.  Which is honestly pretty great, given the age of my laptop (about seven years).

So: did I meet my performance goal?  As I said at the outset, yes, but also no.  For a single blog post with around 10KB of text content and no embedded media, the page weight is around 460KB, with the size varying a bit depending on how much markup is needed for the 10KB of text.  (Here’s one recent example with some content variety.)  Of that, the CSS, split across two files, totals 35.2KB.  The images add up to 102.9KB.  Add them together, and you get just a hair over 138KB, or right around 30%.  Huge success!

Except I hadn’t factored in custom fonts, which all by themselves currently total 203.6KB (44% total page weight), mostly due to the three faces of IM Fell I’m using.  That’s right: The fonts weigh more than the CSS and images put together.  Once they’re added in with the CSS and images, the design elements end up being almost 75% the total page weight — about 341.6KB of the 460KB total.  Most of the rest is the 104.4KB chewed up by showdown.js, the enhancement script I’m using to allow the use of Markdown in post comments.

Thus, next up on my performance quest is looking into subsetting the fonts I’m using in order to get their weight down, and finding out if there’s anything I can do to subset Showdown as well.

But as of now, I’m well pleased with where I ended up on image optimization.  I just need to go back and do the same for post-specific images I’d left at unrestricted color depths, where I anticipate a similar 90% savings in file sizes.  If you’ve got a lot of images, particularly PNGs, try running them through a process that lets you restrict the color depth, and see how much it saves.  The results might surprise you!


Pseudo-Randomly Adding Illustrations with CSS

Published 4 years, 7 months past

I’ve been incredibly gratified and a bit humbled by the responses to the new design.  So first of all, thank you to everyone who shared their reactions!  I truly appreciate your kindness, and I’d like to repay that kindness a bit by sharing some of the techniques I used to create this design.  Today, let’s talk about the ink-study illustrations placed between entries on the site, as well as one other place I’ll get to later.

Very early in the process, I knew I wanted to separate entries with decorations of some sort, as a way of breaking up the stream of text.  Fortunately, Hamonshū provided ample material.  A little work in Acorn and I had five candidate illustrations ready to go.

The five illustrations.

The thing was, I wanted to use all five of them, and I wanted them to be picked on a random-ish basis.  I could have written PHP or JS or some such to inject a random pick, but that felt a little too fiddly.  Fortunately, I found a way to use plain old CSS to get the result I wanted, even if it isn’t truly random.  In fact, its predictability became an asset to me as a designer, while still imparting the effect I wanted for readers.

(Please note that in this article, I’ve simplified some aspects of my actual CSS for clarity’s sake; e.g., removing the directory path from url() values and just showing the filenames, or removing declarations not directly relevant to the discussion here.  I mention this so that you’re prepared for the differences in the CSS shown in this piece versus in your web inspector and/or the raw stylesheet.)

Here’s how it starts out:

#thoughts .entry + .entry::before {
   content: "";
   display: block;
   height: 10em;
   background:
      url(separator-big-05.png) 50% 100% / contain no-repeat;
}

That means, for every blog entry except the first, a block-level bit of generated content is inserted at the beginning of the entry, given a height, and the image separator-big-05.png is dropped into the generated box and sized to be contained within it, which means no part of the image will spill outside the background area and thus be clipped off.  (The file has the number 05 because it was the fifth I produced.  It ended up being my favorite, so I made it the default.)

With that in place, all that remains is to switch up the background image that’s used for various entries.  I do it like this:

#thoughts .entry:nth-of-type(2n+1)::before {
   background-image: url(separator-big-02.png);
}
#thoughts .entry:nth-of-type(3n+1)::before {
   background-image: url(separator-big-03.png);
}
#thoughts .entry:nth-of-type(4n+1)::before {
   background-image: url(separator-big-04.png);
}
#thoughts .entry:nth-of-type(5n+1)::before {
   background-image: url(separator-big-01.png);
}

So every second-plus-one entry (the third, fifth, seventh, etc.) that isn’t the first entry will use separator-big-02.png instead of -05.png.  Unless the entry is an every-third-plus-one (fourth, seventh, tenth, etc.), in which case separator-big-03.png is used instead.  And so on, up through every-fifth-plus-one.  And as you can see, the first image I produced (separator-big-01.png) is used the least often, so you can probably guess where it stands in my regard.

This technique does produce a predictable pattern, but one that’s unlikely to seem too repetitious, because it’s used to add decoration separated by a fair amount of text content, plus there are enough alternatives to keep the mix feeling fresh.  It also means, given how the technique works, that the first separator image on the home page (and on archive pages) is always my favorite.  That’s where the predictability of the approach helped me as a designer.

I use a similar approach for the separator between posts’ text and their comments, except in that case, I add a generated box to the end of the last child element in a given entry:

.single #thoughts article .text > *:last-child:after {
   content: "";
   display: block;
   height: 10em;
   background:
      url(separator-big-05.png) 50% 100% / contain no-repeat;
}

That is, on any page classed single (which is all individual post pages) after the last child element of a .text element (which holds the text of a post), the decoration box is generated.  The default, again, is separator-big-05.png — but here, I vary the image based on the number of elements in the post’s body:

.single #thoughts article .text > *:nth-child(2n+1)::after {
   background-image: url(separator-big-02.png);
}
.single #thoughts article .text > *:nth-child(3n+1)::after {
   background-image: url(separator-big-03.png);
}
.single #thoughts article .text > *:nth-child(4n+1)::after {
   background-image: url(separator-big-04.png);
}
.single #thoughts article .text > *:nth-child(5n+1)::after {
   background-image: url(separator-big-01.png);
}

In other words: if the last child element of the post text is a second-plus-one, separator-big-02.png is used.  If there are 3n+1 (one, four, seven, ten, thirteen, …) HTML elements in the post, separator-big-03.png is used.  And so on.  This is an effectively random choice from among the five images, since I don’t count the elements in my posts as I write them.  And it also means that if I edit a piece enough to change the number of elements, the illustration will change!  (To be clear, I regard this as a feature.  It lends a slight patina of impermanence that fits well with the overall theme.)

I should note that in the actual CSS, the two sets of rules above are merged into one, so the selectors are actually like so:

#thoughts .entry + .entry::before,
   .single #thoughts article .text > *:last-child:after {…}

#thoughts .entry:nth-of-type(2n+1)::before,
   .single #thoughts article .text > *:nth-child(2n+1)::after {…}

#thoughts .entry:nth-of-type(3n+1)::before,
   .single #thoughts article .text > *:nth-child(3n+1)::after {…}

In all honesty, this technique really satisfies me.  It makes use of document structure while having a random feel, and is easily updated by simply replacing files or changing URLs.  It’s also simple to add more rules to bring even more images to the mix, if I want.

And since we’re talking about using structure to vary layout, I also have this @media block, quoted here verbatim and in full:

@media (min-width: 50em) {
   #thoughts .entry:nth-of-type(2n) {
      transform: translate(-1vw,0);
   }
   #thoughts .entry:nth-of-type(3n) {
      transform: translate(3vw,0);
   }
}

This means on the home page and blog archive pages, but only at desktop-browser widths, some entries are shifted a bit to the left or right by fractions of the viewport width, which subtly breaks up the strict linearity of the content column on long pages, keeping it from feeling too grid-like.

To be honest, I have no idea if that side-shifting effect actually affects visitors’ experience of using meyerweb, but I like it.  Sometimes the inter-entry wave art fits together with the side-shift so that it looks like the art flows into the content.  That kind of serendipity always delights me, whether it comes by my hand or someone else’s.  With luck, it will have delighted one or two of you as well.


Hamonshu

Published 4 years, 7 months past

I ended my observance of CSS Naked Day 2020 by launching an entirely new design for meyerweb.  I’m calling it Hamonshū after the source from which I adapted most of the graphic elements.  I’ve been working on it sporadically in my free time since mid-January, finally coming to a place I thought was ready to launch in late March.

Naked Day was a convenient way to change over the structure of pages while there was no design, which probably makes it sound like that’s the only reason I even observed it.  To the contrary, I hadn’t planned to launch the new design until June 8th of this year — but once I decided on going style-naked, I realized it was the perfect opportunity to make the switch.

I might still have delayed, if not for everything happening in the world right now.  But Cameron Moll said it best as he recently launched a new design: “Deploying in the middle of a pandemic seems so unimportant at the moment. Or maybe there’s no better time for it.”  That last sentence resonated with me unexpectedly deeply, and came to mind again as I took the CSS away for Naked Day.

I’ll have quite a few things to say about the design in the future: things I learned, techniques I used, bits I really like, that sort of thing.  In this post, I want to say a bit about its genesis.

It all started when someone — I’ve since lost track of who, or even where it happened — brought my attention to Hamonshū, Vols. 1-3, available on the Internet Archive thanks to the Smithsonian Institution.  Hamonshū, a word which I understand roughly translates into English as “wave forms” or “wave design”, is a three-volume set of art studies of water.  Created by Yūzan Mori and published in 1903, I had never heard of it before, but the sketches immediately appealed to me.  You can get an preview of some of Yūzan’s art in this article from Public Domain Review, or just go to the source (linked previously, as well as in the footer of the site) and immerse yourself in it.

As I absorbed Yūzan’s ink studies of ocean waves, rivers, fountains, and more, the elements of a design began to form in my head.  I won’t say I saw it — being aphantasic, I couldn’t — but certain sketches suggested themselves as components of a layout, and stuck with me.

Tall Bamboo and Distant Mountains, after Wang Meng, Wang Hui 王翬, 1694

Early on, I had thought to combine elements from Hamonshū with other artwork, primarily ink landscape paintings from the Qing Dynasty and Edo periods: two such examples being Tall Bamboo and Distant Mountains, after Wang Meng (Wang Hui 王翬, 1694) and View of West Lake (Ike Taiga, 1700s).  I made attempts, but the elements never really combined properly.  I eventually realized I was trying to combine close-up studies of water with adaptations of much larger works, and the scale of the brush strokes was clashing.  At that point, I abandoned the paintings and concentrated exclusively on Hamonshū.

As the various design elements came together, I went looking for fonts to use.  I originally thought to use variable fonts, but I kept coming back to IM Fell, a typeface I’d seen Simon St. Laurent use and had put to my own purposes in an experimental typeset of Neal Stephenson’s Mother Earth Mother Board.  IM Fell has a sort of nautical feel to it, at least to me, which fit nicely with the water elements I was adapting from Hamonshū, so I ended up using it as a “site elements” typeface.  It’s what’s used for the site name in the header, the main navigation links, metadata for posts, sidebar heading text, the h1 on most pages, and so on.

Originally I used IM Fell for the titles of blog posts like this one, but it didn’t feel quite right.  I think it caused the titles to blend into the rest of the design a little too much unless I kept it relatively huge.  I needed something that felt consistent, but distinguished itself at the smaller sizes I needed for post titles.  I went back to Google Fonts and scrolled through the choices until I narrowed down to a few faces, of which Eczar was the eventual winner.  In addition to using Eczar for post titles, I also employ it in the site’s footer, at least wherever IM Fell isn’t used.  The general body copy of the site is Georgia Pro, falling back to Georgia or a generic serif as needed.

One of the limitations I set for myself was to be reasonably lightweight, and that was a major part of the process.  The details merit a post or two of their own, but my overall goal was to get even the post archive pages under a megabyte in total.  I’m pleased to say I was able to get there, for the most part.  As an example, the main post archive page is, as I write this (but before I posted it) 910.98KB, and that includes the various photographs and other images embedded in posts.  The time to DOMContentLoaded over WiFi is consistently below 200ms, 400-500ms on “Regular 3G”, and 500-600ms on “Regular 2G”, all with the local cache disabled, at least when the server is responding well.  I still have work to do in this area, but I was comfortable enough with the current state to launch the design publicly.

Since I was redesigning anyway, I did some sprucing up of various subpages.  Most notable are the Toolbox and Writing pages, which use a number of techniques to improve organization and appearance.  I still think the top part of the Writing page could use some work, but it’s leagues better than it used to be.  The one major page I’d like to further upgrade is CSS Work, but I’m still looking for an approach that is distinct from the other pages, yet thematically consistent.  If I can’t find one, I’ll probably take the same general approach I did for Toolbox.  I also rewrote some of the microcopy, such as the metadata (publication date, categories, etc.) at the bottom of blog posts, to be more evocative of the feel I was going for.

Late in the process, I got a welcome assist from Jesse Gardner, who had seen a preview of article design.  He had the idea to make a traced SVG version of the “Hand Made With Love” necklace charm from the masthead of the previous design, and then he just up and did it and sent me the file.  You’ll find it in the footer of the site.  It isn’t interactive, although it may in the future.  I haven’t decided yet.

I really hope you enjoy the new look.  It’s the first design I’ve done that wasn’t cribbed off someone else’s site in, oh, 15-20 years, give or take, and I’m rather proud of it.  It won’t win any awards, but it makes the statement I want it to make, and visiting my own site gives me a little glow of satisfaction.  I don’t know if I could ask for more than that.


CSS Naked Day 2020

Published 4 years, 7 months past

If you’re here on meyerweb on April 9th, 2020, then you’re seeing the site without the CSS I wrote for its design and layout.  Why?  It’s CSS Naked Day!  To quote that site:

The idea behind this event is to promote Web Standards. Plain and simple. This includes proper use of HTML, semantic markup, a good hierarchy structure, and of course, a good old play on words. It’s time to show off your <body> for what it really is.

So last night, I removed the links to the main stylesheets for the site.  There are, I should note, scattered pages where local CSS is still in play. I could have tracked them all down and removed their CSS as well, and I considered doing so, but in the end I decided against it.

There’s a history to this day.  In the late Aughts, it was an annual thing, not unlike Blue Beanie Day, to draw attention to web standards and reinforce among the community that good, solid, robust development practices are a good thing.  Because after all, if your site isn’t usable without the CSS, then it almost certainly has structure and accessibility problems you probably haven’t been thinking about.

In all honesty, I had forgotten about it until just a couple of days prior, I suddenly thought, “Wait, early April, isn’t that when CSS Naked Day was observed?”  I went looking, and discovered that yes, it was.  I had remembered April 7th, but apparently April 9th was the actual date.  Or became it over time.  Either way, here we are, feeling fancy-free!

What’s been interesting has been what CSS Naked Day has revealed about browsers as well as about our HTML.  To pick one example, suppose you have a very large SVG logo, which you size to where you want it with CSS.  This is ordinarily a best practice: the SVG is the same file size whether it renders huge or tiny, so there’s no downside to having an SVG that renders 1200 x 1000 when you view it directly — thus allowing you to see all the little details — but is sized to 120 x 100 via CSS for layout purposes.

But take away the CSS, and the SVG will become 1200 x 1000 again.  That might tell you to resize it for production, sure, and you probably should.  But it also points out that browsers will not constrain that image, not even to the viewport.  If your window is only 900 pixels wide, the SVG could well spill outside, forcing a horizontal scrollbar.  Is that good?  Maybe!  Maybe not!  We might wish browsers would bake something like img {max-width: 100%; height: auto;} into their user-agent stylesheet(s), but maybe that would have unforeseen downsides.  The point is, this is a thing about browsers that CSS Naked Day reveals, and it’s worth knowing.

Similarly, this reveals that browsers don’t have a way to restrict the width of lines of text.  Thus, if the browser window is wide, the lines get very long — long enough to make reading more difficult.  This isn’t a problem on handheld devices like smartphones, but on desktop (use of which has risen significantly in areas locked down to limit the spread of SARS-CoV-2) it can be a problem.  Again, if browsers had something like body {max-width: 70em;} or max-width: 100ch or suchlike, this wouldn’t be a problem.  Should they?  Maybe!  It’s worth thinking about for your own work, if nothing else.

(For much more thinking about these kinds of browser behaviors and how to address them, you should absolutely check out the CSS Remedy project.  “CSS Remedy sets CSS properties or values to what they would be if the CSSWG were creating the CSS today, from scratch, and didn’t have to worry about backwards compatibility.”)

If I’d remembered sooner, I might have contacted the maintainers of the CSS Naked Day site and posted about it ahead of time and thought about stuff like a hashtag to spread the word.  Maybe that will happen next year.  Until then, enjoy all the nudity!


Get Static

Published 4 years, 7 months past

If you are in charge of a web site that provides even slightly important information, or important services, it’s time to get static.  I’m thinking here of sites for places like health departments (and pretty much all government services), hospitals and clinics, utility services, food delivery and ordering, and I’m sure there are more that haven’t occurred to me.  As much as you possibly can, get it down to static HTML and CSS and maybe a tiny bit of enhancing JS, and pare away every byte you can.

Because too many sites are already crashing because their CMSes can’t keep up with the traffic surges.  And too many sites are using dynamic frameworks that drain mobile batteries and shut out people with older browsers.  That’s annoying and counter-productive in the best of times, but right now, it’s unacceptable.  This is not the time for “well, this is as performant as our stack gets, so I guess users will have to live with it”.  Performance isn’t just something to aspire to any more.  Right now, in some situations, performance could literally be life-saving to a user, or their family.

We’re in this to serve our users.  The best service you can render at this moment is to make sure they can use your site or service, not get 502/Bad Gateway or a two-minute 20%-battery-drain page render.  Everything should take several back seats to this effort.

I can’t tell you how best to get static — only you can figure that out.  Maybe for you, getting static means using very aggressive server caching and a cache-buster approach to updating info.  Maybe it means using some kind of static-render plugin for your CMS.  Maybe is means accelerating a planned migration to a static-site CMS like Jekyll or Eleventy or Grav.  Maybe it means saving pages as HTML from your browser and hand-assembling a static copy of your site for now.  There are a lot of ways to do this, but whatever way you choose, do it now.


Addendum: following are a few resources that can help. Please suggest more in the comments!


Trying to Work From Home

Published 4 years, 8 months past

I’ve been working from home for (checks watch) almost 19 years now, and I’d love to share some tips with you all on how to make it work for you.

Except I can’t, because this has been incredibly disruptive for me.  See, my home is usually otherwise empty during the day — spouse at work, kids at school — which means I can crank up the beats and swear to my heart’s content at my code typos.  Now, not only do I have to wear headphones and monitor my language when I work, I also am surrounded by office-mates who basically play video games and watch cat videos all day, except for those times when I really get focused on a task, when they magically sense it’s the perfect time to come ask me random questions that could have waited, derailing my focus and putting me back at square one.

Which, to most of you used to working in an office setting, I suppose might seem vaguely familiar.  I’m not used to it at all.

Here’s what I can tell you: if you’re having trouble focusing on work, or anything else, it’s not that you’re terrible at working from home or bad at your job.  It’s that you’re doing this in a set of circumstances completely unprecedented in our lifetimes.  It’s that you’re doing this while worried not only about keeping yourself and your loved ones safe from a global pandemic, but probably also worried about your continued employment — not because you’re doing badly, but because the economy is on the verge of freezing up completely.  No spending means no business income means no salaries means no money to spend.

We can hope for society-level measures to unjam the economic engine, debt leniency or zero-interest loans or Universal Basic Income or what have you, but until those measures exist and begin to work together, we’re all stumbling scared in a pitch-black forest.  Take it from someone who has been engulfed by overwhelming, frightening, pitiless circumstances before: Work can be a respite, but it’s hard to sustain that retreat.  It’s hard to motivate yourself to even think about work, let alone do a good job.

Be forgiving of yourself.  Give yourself time and space to process the fear, to work through it and you.  Find a place for yourself in relation to it, so that you can exist beside it without it always disrupting your thoughts.  That’s the only way I know to free up any mental resources to try to do good work.  It also puts you in a place where you can act with some semblance of reason, instead of purely from fear.

Stay safe, friends.  We have a long, unknown road ahead.  Adjustment will be a long time in coming.  Support each other as much as you can.  Community is, in the end, the most resilient and replenishing force we have.


Browse the Archive

Earlier Entries

Later Entries