How the eye in my nav became a theme toggle

👁️ Feeling watched

The eye from the navigation bar, mid-blink

The first version of this site, back in 2022, had a little eye in the navigation bar. It was decorative — a logo with a white iris that shifted on hover — but people remembered it. When I rebuilt the site this year, retiring the eye felt wrong. Promoting it felt right. So now it runs the lights.

Three spans and no images

The whole eye is CSS. A rounded container is the eyeball, a 12px circle is the iris, and an absolutely-positioned overlay is the eyelid, scaled to zero until it's needed:

<button class="eye-toggle" aria-pressed="false" aria-label="Switch to dark theme">
  <span class="eye" aria-hidden="true">
    <span class="iris"></span>
    <span class="lid"></span>
  </span>
</button>

Because it's a real <button>, keyboard support is free, and aria-pressed tells screen readers it's a toggle rather than a picture.

An iris that watches you

One pointermove listener on the document drives every eye on the page. For each eye I take the vector from its centre to the cursor, normalise it, and clamp how far the iris can travel — a distance divided by eight, capped at the eyeball's radius. The vertical axis gets damped to 60% because eyes are wider than they are tall. The result is written to two CSS custom properties, throttled through requestAnimationFrame so the handler never does layout work twice in a frame.

On touch screens there's no cursor worth following, and for people who prefer reduced motion a wandering iris is noise — in both cases the script simply never starts, and the iris stays centred.

The blink is the trick

Clicking the eye plays a 280ms eyelid animation: scale to closed at 45%, hold, reopen. The theme flips at 130ms — while the lid is fully shut. The eye closes on the light theme and opens onto the dark one, which reads as the eye itself changing its mind. It's a setTimeout and a CSS keyframe; the entire effect is maybe fifteen lines.

With reduced motion enabled, the blink is skipped and the theme just flips instantly. Same outcome, no theatrics.

Means to an end

None of this needed a framework — the site is hand-written HTML and one stylesheet. The eye is a reminder I keep at the top of every page: small details are allowed to do real work.