Mark Mode tutorial

Thursday, October 10, 2019 :: Tagged under: engineering culture. ⏰ 3 minutes.


Hey! Thanks for reading! Just a reminder that I wrote this some years ago, and may have much more complicated feelings about this topic than I did when I wrote it. Happy to elaborate, feel free to reach out to me! πŸ˜„


🎡 The song for this post is Uptown Funk, by Mark Ronson feat. Bruno Mars. 🎡

Users today expect you to keep up with changing trends; and these days, users want the option to navigate your product in Mark mode. This step-by-step tutorial will teach you how to activate Mark mode on your site!

If you want to see it in action, activate it here! (you'll probably want to refresh the page to actually, uh, read the article, if you're not one of those Mar[kc]-loving users).

Overview of the process

Remember the goal of Mark Mode:

For every letter i, dot the i with the head of a famous /Mar[ck]/

CSS is extremely powerful: we can do custom bullets for our lists with list-style-image:

And we can do all sorts of amazing things with shapes, as evidence by A Single Div:

There's not yet a CSS property that provides custom dots on i's. So we need to do it ourselves.

Find the i's

For this, we'll use Javascript. We'll do a tree descent into the DOM, and for any text nodes, use regular expressions on their contents. If we find i's, we'll surround them with custom <spans>. The whole thing looks like this:


function markMode() {

  function getRandomWithin(min, max) {
    return Math.floor(Math.random() * (max - min) + min);
  }

  function wrapInSpans(textNode) {
    let text = textNode.nodeValue;
    if (text.trim() == '') {
      return;
    }

    const regex = /[i]/g;
    let replacementText = "";
    let startIndex = 0;
    while (regex.exec(text) !== null) {
      replacementText += text.substring(startIndex, regex.lastIndex - 1);
      let identifier = getRandomWithin(0, 10);
      replacementText += `<span class="mark-i"><span class="mark-dot-${identifier}"></span>i</span>`;
      startIndex = regex.lastIndex;
    }
    replacementText += text.substring(startIndex);

    let replacement = document.createElement('span');
    replacement.innerHTML = replacementText;
    textNode.replaceWith(replacement);
  }

  function markModeRecursive(element) {
    element.childNodes.forEach((child) => {
      if (child.nodeType == Node.TEXT_NODE) {
        wrapInSpans(child);
      } else {
        markModeRecursive(child);
      }
    });
  }

  markModeRecursive(document.querySelector('body'));
}

This takes markup that looks like

<h1>Try it today!</h1>

and transforms it to:

<h1><span>Try <span class="mark-i"><span class="mark-dot-9"></span>i</span>t today!</span></h1>

Style them

Meanwhile, in our CSS, we'll style the classes we've attached to our spans. Each mark-dot-n class has a common set of rules:


.mark-dot-0,
.mark-dot-1,
.mark-dot-2,
.mark-dot-3,
.mark-dot-4,
.mark-dot-5,
.mark-dot-6,
.mark-dot-7,
.mark-dot-8,
.mark-dot-9 {
  display: inline-block;
  background: url('/img/static/mark_sprites_smol.png') no-repeat;
  overflow: hidden;
  text-indent: -9999px;
  text-align: left;
  z-index: 999;
  position: absolute;
}

These point to a CSS sprite sheet, a very old technique that was popular in the HTTP/1 days, where we had to minimize requests, and CSS background images were a popular way to style text (it was all over the CSS Zen Garden to get fancy fonts for your headlines, for example).

Here is our sheet of Mar[ck]s:

If you want to see larger Mar[ck]s, I had an older version that used bigger heads. "DK mode" for my little Mark Mode project:

These get pretty easy to generate with a site like this.

Finally, either manually (or generated from the link above), you give individual offsets for each Mar[kc] on the sprite sheet. I also added some negative margins for top and left to place it roughly over the letter. Mine looks like this:


.mark-dot-0 { background-position: -2px -0px; width: 20px; height: 23px;   margin-top: -13px; margin-left: -10px; }   /* bismarck */
.mark-dot-1 { background-position: -24px -0px; width: 20px; height: 29px;  margin-top: -13px; margin-left: -10px; } /* marc andreeson */
.mark-dot-2 { background-position: -46px -0px; width: 20px; height: 26px;  margin-top: -13px; margin-left: -10px; } /* marc anthony */
.mark-dot-3 { background-position: -2px -25px; width: 20px; height: 24px;  margin-top: -13px; margin-left: -10px; } /* marc maron */
.mark-dot-4 { background-position: -46px -28px; width: 20px; height: 22px; margin-top: -13px; margin-left: -10px; } /* marc cuban */
.mark-dot-5 { background-position: -24px -31px; width: 20px; height: 28px; margin-top: -13px; margin-left: -10px; } /* mark mcgrath */
.mark-dot-6 { background-position: -2px -51px; width: 20px; height: 28px;  margin-top: -13px; margin-left: -10px; } /* mark ruffalo */
.mark-dot-7 { background-position: -46px -52px; width: 20px; height: 21px; margin-top: -13px; margin-left: -10px; } /* mark twain */
.mark-dot-8 { background-position: -24px -61px; width: 20px; height: 27px; margin-top: -13px; margin-left: -10px; } /* mark zuckerburg */
.mark-dot-9 { background-position: -46px -75px; width: 20px; height: 28px; margin-top: -13px; margin-left: -10px; } /* oh hi mark */

And violΓ ! Mark mode, and happy users!

Thanks for the read! Disagreed? Violent agreement!? Feel free to join my mailing list, drop me a line at , or leave a comment below! I'd love to hear from you πŸ˜„