Adopt a fully CSS-driven strategy for the theme transition:
- Use CSS variables (--tt-x, --tt-y, --tt-r) injected onto documentElement
- Define static @keyframes in style.css using these variables
- Key logic: the Dark layer is ALWAYS placed on top (z-index: 2).
- Light -> Dark: NEW layer is Dark, so NEW expands from circle(0) to cover OLD.
- Dark -> Light: OLD layer is Dark, so OLD shrinks to circle(0) to reveal NEW.
This completely eliminates reliance on Web Animations API (which had bugs
with clip-path when animation:none is set) and eliminates dynamic <style>
injection (which caused timing gaps and style residue).
The result is perfectly symmetrical and bug-free in both directions.