Root cause: showSplash() sets display:flex + opacity:1 synchronously,
then dismissSplash() immediately adds the fade-out class (opacity:0).
The browser batches these in the same paint frame so the CSS transition
from opacity:1 -> 0 never starts, and transitionend never fires,
leaving the Promise unresolved and the splash stuck.
Two-part fix:
1. handleLogin: await two requestAnimationFrames between showSplash()
and dismissSplash() so the browser paints opacity:1 first, ensuring
the CSS opacity transition actually runs.
2. dismissSplash: add a 500ms fallback setTimeout that hides the splash
and resolves the Promise even if transitionend is never fired (acts
as a safety net for any future edge cases).