<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kaomojikan</title>
    <description>The latest articles on DEV Community by Kaomojikan (@kaomojikan).</description>
    <link>https://dev.to/kaomojikan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3986912%2F91cc57c7-5eac-4659-8e7d-2effe18e11d8.jpg</url>
      <title>DEV Community: Kaomojikan</title>
      <link>https://dev.to/kaomojikan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kaomojikan"/>
    <language>en</language>
    <item>
      <title>The funny story behind the kaomoji developers love: shrug, table flip, Lenny, and ಠ_ಠ</title>
      <dc:creator>Kaomojikan</dc:creator>
      <pubDate>Mon, 22 Jun 2026 01:16:40 +0000</pubDate>
      <link>https://dev.to/kaomojikan/the-funny-story-behind-the-kaomoji-developers-love-shrug-table-flip-lenny-and-tthtth-4ni9</link>
      <guid>https://dev.to/kaomojikan/the-funny-story-behind-the-kaomoji-developers-love-shrug-table-flip-lenny-and-tthtth-4ni9</guid>
      <description>&lt;p&gt;You already use these. The shrug &lt;code&gt;¯\_(ツ)_/¯&lt;/code&gt; in a commit message. The table flip &lt;code&gt;(╯°□°)╯︵ ┻━┻&lt;/code&gt; when CI goes red again. Maybe Lenny &lt;code&gt;( ͡° ͜ʖ ͡°)&lt;/code&gt; in a Slack thread you regret. They feel native to programmer culture, like they were always there.&lt;/p&gt;

&lt;p&gt;Almost none of them were. Most of these faces came out of Japan, some of them decades ago, and took a genuinely strange road to reach your keyboard. I run &lt;a href="https://kaomojikan.com/en/" rel="noopener noreferrer"&gt;a little site for copying them&lt;/a&gt;, so I went looking for where a few actually come from. One warning before we start: meme origins are murky and full of confident misattribution, so where the trail goes cold, I'll say so rather than make something up.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, why they look "upright"
&lt;/h2&gt;

&lt;p&gt;There are two family trees here, and they started four years apart.&lt;/p&gt;

&lt;p&gt;The Western emoticon is the one you tilt your head for. Scott Fahlman proposed &lt;code&gt;:-)&lt;/code&gt; on a Carnegie Mellon bulletin board in &lt;a href="https://en.wikipedia.org/wiki/Emoticon" rel="noopener noreferrer"&gt;September 1982&lt;/a&gt;, to mark jokes. It reads sideways, and it's all about the mouth.&lt;/p&gt;

&lt;p&gt;The Japanese branch went the other way. The commonly cited birthday is June 1986, when a user on the ASCII-NET service posted &lt;code&gt;(^_^)&lt;/code&gt;, read straight on, no head-tilt required. These are &lt;em&gt;kaomoji&lt;/em&gt;, from 顔 (kao, "face") and 文字 (moji, "character"), and they emphasize the &lt;strong&gt;eyes&lt;/strong&gt;, not the mouth. Through the 1990s they exploded on &lt;a href="https://en.wikipedia.org/wiki/2channel" rel="noopener noreferrer"&gt;2channel&lt;/a&gt;, Japan's giant textboard, where people stitched characters into faces, animals, and whole scenes.&lt;/p&gt;

&lt;p&gt;That eye-first, upright style is the family almost every face below belongs to. Keep it in mind, because it explains why they feel more expressive than a sideways &lt;code&gt;:-)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¯_(ツ)_/¯ — the shrug
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6kd936j692rbdkiypasy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6kd936j692rbdkiypasy.webp" alt="A cat mascot doing the ¯\_(ツ)_/¯ shrug" width="720" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look closely at the face in the middle. That's &lt;code&gt;ツ&lt;/code&gt;, the Japanese katakana character "tsu," drafted in purely because it looks like a little smile. The arms and &lt;code&gt;¯&lt;/code&gt; shoulders are just typography around it.&lt;/p&gt;

&lt;p&gt;The shrug is old in kaomoji terms, but its jump into Western everyday use is usually credited to a literary agent named Caroline Eisenmann around 2010, who used it on an OkCupid profile before it spread on Twitter. I'd treat the exact "who first" as fuzzy. What's not fuzzy is why it stuck with developers: it says "I don't know," "not my problem," and "it is what it is" in one gesture, and it's pure text, so it survives a commit message and a terminal without turning into a tofu box.&lt;/p&gt;

&lt;h2&gt;
  
  
  (╯°□°)╯︵ ┻━┻ — the table flip
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fulc1s98jhd5rvw5ky89x.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fulc1s98jhd5rvw5ky89x.webp" alt="A cat mascot flipping a table (╯°□°)╯︵ ┻━┻" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This one is a little drawing. &lt;code&gt;(╯°□°)╯&lt;/code&gt; is a person with their arms thrown up, and &lt;code&gt;┻━┻&lt;/code&gt; is the underside of an overturned table.&lt;/p&gt;

&lt;p&gt;It comes from a real Japanese comedy and rage trope called &lt;em&gt;chabudai-gaeshi&lt;/em&gt; (ちゃぶ台返し), literally flipping the low dining table, the kind of dramatic blow-up you'd see from a furious father in old manga and anime. The exact first text version is lost, but it grew out of the same 2channel emoticon tradition and later &lt;a href="https://knowyourmeme.com/memes/flipping-tables-%E2%95%AF%E2%96%A1%E2%95%AF%EF%B8%B5-%E2%94%BB%E2%94%81%E2%94%BB" rel="noopener noreferrer"&gt;crossed to the West&lt;/a&gt; through online games and forums. My favorite part is the sequel that someone inevitably posts underneath: &lt;code&gt;┬─┬ ノ( ゜-゜ノ)&lt;/code&gt;, calmly putting the table back.&lt;/p&gt;

&lt;h2&gt;
  
  
  ( ͡° ͜ʖ ͡°) — Lenny
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcfwm6b98fsljjenk6okg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcfwm6b98fsljjenk6okg.webp" alt="A cat mascot doing the Lenny face ( ͡° ͜ʖ ͡°)" width="720" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After all that murk, here's one with an almost exact birth certificate. Lenny was &lt;a href="https://knowyourmeme.com/memes/%CD%A1-%CD%9C%CA%96-%CD%A1-lenny-face" rel="noopener noreferrer"&gt;posted to the Finnish imageboard Ylilauta on November 18, 2012&lt;/a&gt;, in a thread about spam settings. Within days it had derailed the thread, jumped to 4chan, and landed on Reddit, where people got temp-banned for spamming it. Where the name "Lenny" came from, nobody really knows.&lt;/p&gt;

&lt;p&gt;One thing worth knowing as a developer: Lenny is built from Unicode &lt;em&gt;combining&lt;/em&gt; characters (those &lt;code&gt;͡&lt;/code&gt; marks sit on top of the others). That's why he occasionally smears across a line or breaks in an input that doesn't handle combining marks well. The shrug never does that, because it's all simple characters.&lt;/p&gt;

&lt;h2&gt;
  
  
  ಠ_ಠ — the look of disapproval
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnus58ra0z4j6u4e60k9a.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnus58ra0z4j6u4e60k9a.webp" alt="A cat mascot giving the look of disapproval ಠ_ಠ" width="720" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is my favorite piece of trivia in the whole set. Those flat, judging eyes are the character &lt;code&gt;ಠ&lt;/code&gt;, which is a real letter, &lt;em&gt;ṭha&lt;/em&gt;, from &lt;a href="https://en.wikipedia.org/wiki/Kannada" rel="noopener noreferrer"&gt;Kannada&lt;/a&gt;, one of the major languages of southern India. It carries actual meaning to millions of people. The internet borrowed it for one reason only: it looks exactly like a glaring eye with a heavy brow. The face is &lt;a href="https://knowyourmeme.com/memes/%E0%B2%A0_%E0%B2%A0-look-of-disapproval" rel="noopener noreferrer"&gt;traced back to 2channel&lt;/a&gt;, then spread through 4chan around 2007 and Reddit in the early 2010s.&lt;/p&gt;

&lt;p&gt;A Japanese smile, an Indian consonant, some ASCII furniture. None of it designed for this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thread tying them together
&lt;/h2&gt;

&lt;p&gt;Here's what I find genuinely nice about all this. Almost none of these were &lt;em&gt;made&lt;/em&gt;. They're found objects. A katakana tsu that happened to smile, a Kannada letter that happened to glare, a drawing of a flipped table. People picked up whatever characters were lying around and made faces out of them.&lt;/p&gt;

&lt;p&gt;And because they're only characters, they go everywhere characters go. A terminal, a &lt;code&gt;git log&lt;/code&gt;, a monospace diff, a commit message, a chat box. That's the quiet reason the shrug outlived fancier emoji in places like that: there's nothing to render, so nothing breaks.&lt;/p&gt;

&lt;p&gt;If you want to fall down this rabbit hole, I keep a whole catalogue of them sorted by mood over at &lt;a href="https://kaomojikan.com/en/" rel="noopener noreferrer"&gt;Kaomojikan&lt;/a&gt;, and the data (readings, tags, categories) is &lt;a href="https://github.com/kaomojikan/kaomoji-data" rel="noopener noreferrer"&gt;open under MIT&lt;/a&gt; if you're building something. Go flip a table &lt;code&gt;(╯°□°)╯︵ ┻━┻&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://kaomojikan.com/en/blog/kaomoji-for-developers" rel="noopener noreferrer"&gt;kaomojikan | 顔文字館 (=^・ω・^=)&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>history</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I gave my side project a mascot: she follows your cursor, blinks, and breathes (plain SVG, no libraries)</title>
      <dc:creator>Kaomojikan</dc:creator>
      <pubDate>Tue, 16 Jun 2026 08:45:08 +0000</pubDate>
      <link>https://dev.to/kaomojikan/i-gave-my-side-project-a-mascot-she-follows-your-cursor-blinks-and-breathes-plain-svg-no-3ec6</link>
      <guid>https://dev.to/kaomojikan/i-gave-my-side-project-a-mascot-she-follows-your-cursor-blinks-and-breathes-plain-svg-no-3ec6</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://kaomojikan.com/en/blog/eye-tracking-mascot-vanilla-svg" rel="noopener noreferrer"&gt;my site&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've loved Japanese kaomoji for years. The little upright faces like &lt;code&gt;(｡•ᴗ•｡)&lt;/code&gt; and &lt;code&gt;(っ´ω`c)&lt;/code&gt;, not the sideways &lt;code&gt;:-)&lt;/code&gt;. They warm up a message just a little, and that always stuck with me.&lt;/p&gt;

&lt;p&gt;A while back I went looking for a good place to grab them, and honestly I couldn't find one I liked. There are plenty of sites, but most of them feel old. Cramped pages, tiny text, ads everywhere, a stray link sitting right where you meant to tap. The kaomoji were cute. Using the sites was not.&lt;/p&gt;

&lt;p&gt;So I figured I'd just build my own. But a straight copy of what already existed felt pointless. Same list, fewer ads, who cares about that.&lt;/p&gt;

&lt;p&gt;Then I thought about the small shops I used to wander into in Japan. The quiet, warm ones, each with its own little personality. You step inside and your shoulders drop, and somehow half an hour goes by. That was the feeling I wanted. Not a database, but a little shop you can walk into, with someone actually in it.&lt;/p&gt;

&lt;p&gt;So I put someone in it. A small 看板娘 (&lt;em&gt;kanban-musume&lt;/em&gt;), the kind of shop girl who says hi when you come in. I call her Nyamoji. She watches your cursor, blinks when she feels like it, and even sort of breathes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feeveejd7sor7pst48mxp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feeveejd7sor7pst48mxp.gif" alt="kaomojikan-showcase" width="720" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the thing, though: none of it is hard. It's plain SVG, one &lt;code&gt;pointermove&lt;/code&gt;, and a bit of CSS. The tricky part was getting her to stop looking dead. Let me walk you through the bits that actually mattered.&lt;/p&gt;

&lt;h2&gt;
  
  
  One face, swapped parts
&lt;/h2&gt;

&lt;p&gt;She has sixteen expressions, and they change depending on the page. But her outline and her cheeks never change. So I draw the base once, and only swap out the eyes and the mouth. An expression is really just data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Each face only carries eyes + mouth. The base SVG is shared.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FACES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;eyes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="na"&gt;mouth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SMILE&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;cry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;eyes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TEARY_EYES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mouth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WAVER&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;sing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;eyes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SING_EYES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;mouth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NOTE&lt;/span&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ...16 total&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want a new expression? Add an eyes path and a mouth path. I never redraw the whole face. It's the cheapest decision in the whole project, and it kept paying off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Following the cursor
&lt;/h2&gt;

&lt;p&gt;This is the part I cared about most.&lt;/p&gt;

&lt;p&gt;The page listens to a single &lt;code&gt;pointermove&lt;/code&gt;. Every time the cursor moves, I work out the direction from the center of her face to the pointer, and nudge just her eyes that way:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn4ljjpupixo28zc7cwe.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn4ljjpupixo28zc7cwe.gif" alt="The mascot's eyes following the cursor as it moves around her, with an occasional blink" width="799" height="511"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hypot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;eyes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`translate(&lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;MAX&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things I got wrong the first time.&lt;/p&gt;

&lt;p&gt;First, that &lt;code&gt;0.85&lt;/code&gt; on the vertical. If her eyes travel up and down as far as they go side to side, she looks like her eyes are rolling back into her head. It's honestly a little creepy. Pulling the vertical in a bit keeps her looking at you instead of through you.&lt;/p&gt;

&lt;p&gt;Second, the eyes group has &lt;code&gt;transition: transform .14s ease-out&lt;/code&gt; on it. Without that, the eyes jump from spot to spot and it feels robotic. Give them a seventh of a second to catch up and they glide, and suddenly it reads like she's actually watching you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blinking, never in sync
&lt;/h2&gt;

&lt;p&gt;Blinking is just CSS. I squash her eyes flat for a single instant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;m-blink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;93&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scaleY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;96&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;         &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scaleY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the part that surprised me. There's a mascot up in the header, and another one down in the page. The first time I shipped this, every one of them blinked on the exact same frame. The whole page sort of twitched at once. It looked like a bug, not a living thing.&lt;/p&gt;

&lt;p&gt;The fix is one line. Give each mascot a random delay when it gets drawn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;blink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;animationDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;4.8&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now they each blink on their own little schedule, and she feels a lot more alive. Breathing works the same way: a slow scale from &lt;code&gt;1.0&lt;/code&gt; to &lt;code&gt;1.025&lt;/code&gt; over five seconds, also offset so they're never in lockstep.&lt;/p&gt;

&lt;h2&gt;
  
  
  The white dot
&lt;/h2&gt;

&lt;p&gt;There's a tiny white dot in each eye. A catchlight.&lt;/p&gt;

&lt;p&gt;That one dot is the whole difference between alive and dead. Take it away and her eyes look like buttons. Put it back and she's looking right at you. If I could keep only one detail from this whole post, I'd keep the dot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reduced motion
&lt;/h2&gt;

&lt;p&gt;Not everyone enjoys movement on a page, and that's fair. So when &lt;code&gt;prefers-reduced-motion&lt;/code&gt; is set to &lt;code&gt;reduce&lt;/code&gt;, I stop all of it. No blinking, no breathing, no cursor following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.m-blink&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.mascot&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-breathe&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.m-eyes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cute movement is a bonus, not the point. I didn't want it to be a tax on anyone, so this went in early rather than as an afterthought.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that cost me the most: an empty span shakes the page
&lt;/h2&gt;

&lt;p&gt;Let me end on the one that really got me.&lt;/p&gt;

&lt;p&gt;Her face gets drawn after the JS loads. At first I used an empty &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; as the placeholder. The moment the JS dropped her face in, that span popped open to full size and the whole page jumped &lt;code&gt;(╯°□°)╯︵ ┻━┻&lt;/code&gt;. On the header logo it was even worse: flicking between pages, you'd catch a glimpse of the empty shell.&lt;/p&gt;

&lt;p&gt;The fix was boring, but it worked. Write her face into the HTML from the start, and make it match what the JS draws, right down to the character. Then when the JS takes over, nothing moves at all.&lt;/p&gt;

&lt;p&gt;One warning. If you copy that placeholder by hand, it drifts out of sync sooner or later, and you get a little jump when the JS kicks in. So I just call the same function that builds her face, and paste its output straight into the HTML. Don't try to write it yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's it
&lt;/h2&gt;

&lt;p&gt;That's really all of it. Plain SVG, &lt;code&gt;pointermove&lt;/code&gt;, and some CSS. The 0.85 squash, the offset blinks, that little white dot. That's where she stops being a drawing and starts feeling like she's there.&lt;/p&gt;

&lt;p&gt;If you want to say hi, she's over at &lt;a href="https://kaomojikan.com/en/" rel="noopener noreferrer"&gt;Kaomojikan&lt;/a&gt;. She'll follow you around the page &lt;code&gt;(・ω・)&lt;/code&gt;. And if it's useful to you, the kaomoji data (readings, tags, and categories, all as JSON) is &lt;a href="https://github.com/kaomojikan/kaomoji-data" rel="noopener noreferrer"&gt;open under MIT&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>svg</category>
      <category>css</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
