Theme Animation Simplified — Goodbye Circular Clip-Path, Hello Crossfade
LifeLog·

Theme Animation Simplified — Goodbye Circular Clip-Path, Hello Crossfade

I spent the entire morning fighting theme animations on LifeLog. And like any good war against CSS, I ended up surrendering — but with much cleaner code.

The Problem

Ever since I implemented the Theme Rail (that sidebar selector with 6 palettes + dark/light), I used a circular clip-path animation to transition between themes. The idea looked great on paper: a circle expanding from the click point, revealing the new theme underneath — Material Design style. In practice:

  • Chromium: the native View Transition API already does crossfade, but I insisted on overriding it with clip-path on ::view-transition-new(root). Result: visible stutter as the browser fights between the native VT and the custom animation.
  • Firefox/Safari: the fallback used an animated clip-path: circle() overlay. It worked, but animated clip-path is expensive — heavy repaints, especially on Firefox Mobile.

There were moments where clicking a theme felt like a 100ms mini-freeze. Imperceptible on a high-end desktop, but sad on mobile.

Less Is More

After 3 commits iterating on the same approach (f8cc2e2, 8155f4e, 4142f14 — each polishing the same flawed strategy), I realized the problem was design, not implementation.

What changed:

  1. Chromium VT — stopped fighting clip-path and let the native document.startViewTransition crossfade do its thing. Zero stutter, zero extra code.
  2. Fallback (Firefox/Safari) — swapped the animated clip-path: circle() for a simple opacity fade with transition: 0.35s ease. Much lighter, no expensive repaints.
  3. Scroll persistence — added sessionStorage to save scroll position before language switch navigation. When the user toggles PT/EN, the scroll returns to exactly where they were — no layout shift.

Tags Got Some Love Too

While I was at it, I fixed tag visibility on the post cards. They used background: transparent which made them invisible on the white theme. Now they use color-mix(in srgb, var(--color-bg) 15%, transparent) — a solid but subtle background, with the tag text using the project’s accent color. Works on both themes without media queries.

Morning Stats

  • 6 files changed, 44 insertions, 52 deletions
  • ~100 fewer lines of CSS/JS in PalettePicker alone
  • Zero stutter on theme toggle — tested on Chromium, Firefox, and Safari
  • E2E tests updated for dynamic post counts and the new search params (?q= instead of ?tag=)

The lesson of the day: complex animations are rarely worth the cost. A well-executed crossfade is smoother than a clip-path circle that janks — no matter how cool that circle looks in Figma.