Case Study: 2021 refresh

11 January 2022

For this year’s refresh, I really wanted to tackle an idea I’ve been trying to do for years. In my 2018 and 2019 case studies, I talked about an experience that feels like you’re traveling through physical space. I imagined a mix between walking forward in a first person video game and the layered, parallax effect Disney innovated (as seen in the intro of Beauty and Beast). And as with years past, I wanted you to experience this by resizing the browser window.

Spoiler: I finally made it work. Here’s a screen recording from Eric Van Holtz that shows the final resizing experience (plus a few extra interactions):

"Wonderful Weirdos of the Web" are welcomed to resize their browsers in another epic @lynnandtonic portfolio redesign. 🤯

— Eric Van Holtz (@ericvanholtz) December 16, 2021

How it works

This effect is ultimately created by “stacking” a bunch of SVGs on top of each other and then scaling and showing/hiding them in sequence as you resize the browser.

a diagram of four stacked layers labeled scenes 1-4 and an arrow that passes through the layers via cutouts
Transitions between scenes happen through cutouts.

I always hoped I could achieve this experience with CSS alone, but every time I attempted it I ran into big performance issues. To make the artwork scale inversely to the browser width, I could set something like: height: calc(1200px - 100vw).

This works mostly fine on its own, but as the artwork became more layered and complicated, the more the effect broke down. As you resized, the browser would stop rendering the changes. Then once you stopped resizing, it would “catch up” and render the final state. Because the experience relies on things changing as smoothly and as real-time as possible, this just wasn’t going to work.

This was even before I started experimenting with blur effects. To make the effect feel more real, I wanted objects to blur and focus as they moved closer/farther away. I could do this with something like filter: blur(1vw) but this made browsers actually crash once I started resizing. Whoopsies.

I tried a few other things like using clamp (which worked ok for scaling but didn’t provide the level of control I needed) and setting tons of media queries in sequence (which has worked in the past for me, but would become very unwieldy with this one).

So I was about ready to throw in the towel until Scott Kellum tweeted at me and asked if something like Typetura could help. And it was like the clouds parting and I could finally feel the warm sun on my weary face. 🙏

Typetura uses a bit of JavaScript and makes flexible, responsive web typography a breeze (and as it turns out, zooming animations too). It was an especially nice solution here because I was still able to do all of my work in the CSS.

It allows you to set a max-width plus CSS animation keyframes and will interpolate between them as you resize the browser. So for the scaling of a layer of artwork, I could do something like this:

svg {
  --tt-max: 3000;
  --tt-key: scene0;
  --tt-ease: cubic-bezier(.17,.67,.24,.97);
  height: 300vh;
@keyframes scene0 {
  0% {
    height: 2400vh;
  43%, 100% {
    height: 5vh;

I’m setting three Typetura custom properties for the svg:

The animation for scene0 sets two height values at different keyframes for Typetura to animate between. At 0% (0px wide), the artwork will be 2400vh tall and as you scale the browser window wider, the artwork will shrink to 5vh tall around 1290px browser width (43% of the 3000px max-width).

a timeline that plots scene0, showing it as 2400vh tall at 0px browser width and 5vh at 1290px browser width
Scaling duration for scene0.

You might be wondering why I didn’t set --tt-max to 1290px then? I started things that way (with each scene having its own max-width), but soon found it difficult to quickly grok when things were happening. 50% meant something different for each scene. It was much easier to wrap my head around the entire sequence as one long timeline where any keyframe could be compared to another.

So I set the --tt-max just once (I dropped it under the body class .home in case I wanted to change it for different pages).

  .home {
    --tt-max: 3000;

This allowed me to easily “place” and stagger transitions along the main timeline spread across different elements. Here’s a simplified example of the door in the desert scene and the timing for when the scene appears and when the window pane becomes opaque (two separate layers):

  @keyframes window {
    0%, 37.5% {
      opacity: 0;
    39.5% {
      opacity: 1;

  @keyframes scene2 {
    0%, 37% {
      opacity: 0;
    37.6% {
      opacity: 1;

And here’s a visualization of what the staggered animations look like along the global timeline:

a timeline that shows the staggered animation keyframes for window and scene2

So from here, any animatable CSS property became fair game, so I was able to use opacity to fade things, transform to move things (like opening and closing the elevator doors), and filter to create blur effects like I mentioned earlier:

@keyframes scene1 {
  20% {
    filter: blur(0);
  25% {
    filter: blur(22em);
  28% {
    filter: blur(0);

Or especially fun was one transition that animates between color and grayscale:

@keyframes scene3 {
  45.6% {
    filter: grayscale(0);
  50%, 100% {
    filter: grayscale(100%);

Here’s what that looks like in action:

That transition from color to black and white. *chef’s kiss* Incredible!

— Scott Kellum (@ScottKellum) December 16, 2021

Typetura made the calculations and animations so easy to manage that I was able to spend most of my time illustrating and working out the small details with each scene transition. (Also big hat tip to Lea Verou for her cubic bezier tool which I used a bunch.)

Creating the artwork sequence

So I’ve jumped ahead a wee bit. Once I figured out I could do this, I actually spent a lot of time figuring out what this even was. I had about 2500px of browser width to account for and I wasn’t sure how many different scenes and transitions I would need.

My initial thought was to have three scenes starting with a super wide view of an outdoor landscape zooming into a building far off in the distance and then into a computer screen inside that room. But with some initial tests, zooming a “far distance” felt like it took forever and not enough was changing to make it interesting.

So I shifted to one-point perspective “rooms” you could move through quickly. The living room zooming out through a window was the first transition I figured out. You should have seen the cheering I did once it worked.

And from there I figured out the next scene and then the next and the next. I probably should have made a storyboard but I didn’t!

I did make a list of ideas and inspiration which included surrealism, Centaurworld, Wes Anderson films, the Marvel series Loki and WandaVision, and The Scary Door (the Futurama parody of The Twilight Zone).

screenshots from Centaurworld, The Grand Budapest Hotel, The French Disptach, Loki, WandaVision, The Scary Door, and a surrealist painting

It was fun coming up with the different rooms to visit and various “windows” you would travel through (like a television screen or elevator). A few ideas and transitions that didn’t make it in:

Maybe for another time. 😉


As easy as Typetura made this process, I still ran into some weird behavior and gotchas.

Scenes needed to be top level, inline SVG.
If the SVGs were <img>s, some browers would rasterize them when scaling. This helps a ton with performance, but was exactly the opposite of what I wanted to happen! So inline they went.

I also found if I wrapped the SVG with a containing <div> (for namespacing purposes) and applied transforms to the <div> instead, the scaling and transitions started to choke again. So that was out. I guess I could have still applied the transforms to the SVG and left the <div> there solely for the organizational benefit but that felt weird!

If anyone was digging into my CSS for this (which you can do on GitHub), this is why I’m using svg:nth-of-type(5) instead of class names for many of the scenes. This is not great but it allowed me to import the SVG into my index.pug file like this:

    include ../_assets/images/scene0-stars.svg
    include ../_assets/images/scene0.svg
      include ../_assets/images/scene0-airplane.svg
      include ../_assets/images/scene0-airplane2.svg
    include ../_assets/images/scene1.svg
    include ../_assets/images/scene2.svg

This is definitely a me problem. I wanted to use the SVG directly exported from Illustrator without needing to move or edit any code or having to reorganize my Illustrator layers (yikes 😱). This way I could make changes, export, and preview really quickly which I needed to do many, many times.

Balancing visual interest and file size.
I wanted the illustrations to feel fun, whimsical, and detailed while maintaining a reasonable file size. To minimize points and reduce complexity, I tried to stick with rectangles, ellipses, efficient lines, basic strokes, and solid blocks of color everywhere possible. Brushes and textures were off limits. A good, but challenging constraint.

screenshots of illustrations from the site including a slice of pizze under a cloche in space, a living room, a surrealist door in a desert, and an elevator lobby

So many screen sizes.
When you actively encourage people to resize your website you better prepare for the weird dimensions! Throughout this process, I had multiple windows open on my laptop and monitor, making sure each scene and transition made sense for narrow, wide, short, and tall windows. The one place where things really started to break is really wide + super short: a very special aspect ratio where you can start to see the edges of the artwork. I did a wee bit of extending widths for some scenes, but for the most part let it ride.

browser that is very wide but very short: artwork of an elevator lobbby doesn’t fill the entire view and the blue background is showing at the right and left edges; arrows and sad emoji label those areas
~1425px by 345px browser size

Mobile. Confusion.
As with past years, you need a browser that can resize to experience the fun. So the site on a phone isn’t broken, but it can be confusing if you got there by word of mouth. (I am exploring a way for you to watch the sequence without resizing your browser, stay tuned.)

This version of the site also has a really high chance you land on a weird inbetween state. My friend Kate arrived at the site to see a Pop-Tart against a black background. She suggested I add a little note to encourage folks to resize their browser, which does help. But! I also don’t mind a little bit of initial confusion in this case.

screenshot of a website that’s just a black background and a random Pop-Tart
Very cool website ya got there.

A few other things I considered:

Anything else?

The monitor scene at the largest width has a few fun Easter eggs. These were a last minute addition and I’m glad they made it in! It helped me learn a bit more about fine tuning exports from Illustrator and using JavaScript inside SVG.

illustration of a monitor on a desktop with various windows open, some prints are taped to the wall behind
A scene with some hidden gems.

It was fun dusting off my timeline animation skills I haven’t used much since the ol’ Flash days. I still don’t think I totally understand bezier curves, but I got much better at predicting how they would behave.

With the end of year deadline looming, the rest of the site got a new coat of paint but not much else changed structurally. I did get to try out variable fonts and do some small optimizations where I could, so that feels like a win too.

At the end of the day I’m still tickled I was able to achieve the landing page I’ve been thinking about for years. I’m again reminded of how the web is such a cool medium and I just love building for it.

As always, previous versions of the site are still viewable in the archive.

Until next year’s refresh. 👋 Thanks for reading!

Back to Thoughts