From 3beab711f9a096a9604dae7cb63c2150f88dbf6b Mon Sep 17 00:00:00 2001 From: soulhackzlol Date: Sun, 5 Jul 2026 11:43:31 +0200 Subject: [PATCH 1/4] ui: smooth aurora transitions between delay states Two things made state changes feel abrupt: 1. Colors snapped. The blobs consume var(--aurora-a/b) inside a radial-gradient, and "transition: background" cannot interpolate gradient images, so the declared .6s transition never actually ran. The vars are now registered with @property (syntax '') and transitioned on the readout itself, so a state flip reads as one ~1.1s color sweep. Browsers without @property fall back to the old instant swap. 2. The blobs teleported around buffering. The arming state overrode animation-duration (14s -> 4s), which remaps the animation's elapsed time to a different progress fraction - a visible jump on every enter/exit. The drift is now identical in every state; arming's energy comes from a new dedicated .aurora-pulse layer, a bottom glow that fades in via an opacity transition and breathes via a transform-only animation, so its keyframe restart never fights the fade. Verified the @property blocks, pulse rules, and var transition survive build.rs minification in the embedded output. 235 tests green. --- CHANGELOG.md | 18 ++++++++++++++++++ web/index.html | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3c8e3d..d394c49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,24 @@ All notable changes will land here. Format loosely follows ## [Unreleased] +### Smooth aurora state transitions + +The hero's aurora glow now sweeps between state colors instead of +snapping, and the blobs no longer teleport when buffering starts or +ends. + +- The two aurora colors are registered CSS custom properties + (`@property`), so a state change interpolates the colors themselves + over ~1.1 s - the old `transition: background` never worked because + gradients aren't transitionable as images. Browsers without + `@property` keep the previous instant swap. +- The drift animation is now identical in every state. The old "arming" + speed-up overrode `animation-duration` (14 s → 4 s) mid-flight, which + remaps elapsed time and visibly teleported the blobs on every + enter/exit of buffering. Arming's energy is a new dedicated breathing + layer instead: a bottom glow that fades in over 0.9 s and pulses + gently (transform only, so its restart never fights the fade). + ### Local test sink destination **Try the whole pipeline with zero risk.** The in-binary RTMP sink diff --git a/web/index.html b/web/index.html index 5d2aaeb..c780058 100644 --- a/web/index.html +++ b/web/index.html @@ -307,9 +307,25 @@ .hero-readout{display:flex;flex-direction:column;gap:14px;padding:28px 32px;min-width:0; border-right:1px solid var(--line);position:relative;overflow:hidden} @media (max-width:880px){.hero-readout{border-right:0;border-bottom:1px solid var(--line)}} +/* Registered custom properties so the aurora COLORS interpolate on a + state change. Without @property, swapping a var() consumed inside a + radial-gradient snaps instantly - gradients aren't transitionable as + background-image, only the registered variable is. Browsers without + @property (old CEF) simply fall back to the old hard swap. */ +@property --aurora-a{syntax:'';inherits:true;initial-value:#5a7a8a} +@property --aurora-b{syntax:'';inherits:true;initial-value:#5a7a8a} +/* The vars live (and transition) on the readout; the ::before/::after + blobs inherit the interpolating value and repaint every frame, so a + state flip reads as one continuous color sweep instead of a cut. */ +.hero-readout{transition:--aurora-a 1.1s var(--ease-out),--aurora-b 1.1s var(--ease-out)} .hero-readout::before,.hero-readout::after{content:'';position:absolute;pointer-events:none; border-radius:50%;filter:blur(60px);opacity:.22;z-index:0; - transition:background .6s var(--ease-out),opacity .6s var(--ease-out)} + transition:opacity .9s var(--ease-out)} +/* The drift animations are deliberately IDENTICAL in every state and + never overridden per-state: changing animation-duration mid-flight + remaps elapsed time to a different progress fraction, teleporting the + blobs on every arming enter/exit (the old behaviour). State "energy" + is conveyed by the separate .aurora-pulse layer instead. */ .hero-readout::before{width:360px;height:360px;left:-80px;top:-80px; background:radial-gradient(circle at 30% 30%,var(--aurora-a,var(--accent)),transparent 70%); animation:aurora-drift-a 14s ease-in-out infinite alternate} @@ -321,11 +337,22 @@ .hero-readout[data-state="armed"] {--aurora-a:var(--accent);--aurora-b:oklch(0.78 0.13 200)} .hero-readout[data-state="active"]{--aurora-a:var(--live);--aurora-b:oklch(0.75 0.16 165)} .hero-readout[data-state="active"]::before,.hero-readout[data-state="active"]::after{opacity:.32} -.hero-readout[data-state="arming"]::before{animation-duration:4s;opacity:.30} -.hero-readout[data-state="arming"]::after{animation-duration:5s;opacity:.30} +.hero-readout[data-state="arming"]::before,.hero-readout[data-state="arming"]::after{opacity:.30} @keyframes aurora-drift-a{0%{transform:translate(0,0) scale(1)} 100%{transform:translate(80px,40px) scale(1.15)}} @keyframes aurora-drift-b{0%{transform:translate(0,0) scale(1.1)} 100%{transform:translate(-60px,-40px) scale(.9)}} .hero-readout>*{position:relative;z-index:1} +/* Breathing layer: the "buffer is filling" heartbeat. Fades in/out via + the opacity transition (smooth entry/exit even though its keyframes + restart), and only animates transform - opacity stays owned by the + transition so the two never fight. Sits under the content like the + blobs; the `.hero-readout>*` z-index lift above is overridden here. */ +.hero-readout>.aurora-pulse{position:absolute;inset:0;z-index:0;pointer-events:none;opacity:0; + background:radial-gradient(70% 90% at 50% 115%,var(--aurora-a,var(--accent)),transparent 72%); + filter:blur(46px);transform-origin:50% 100%; + transition:opacity .9s var(--ease-out)} +.hero-readout[data-state="arming"]>.aurora-pulse{opacity:.26; + animation:aurora-breathe 2.4s ease-in-out infinite} +@keyframes aurora-breathe{0%,100%{transform:scaleY(1)}50%{transform:scaleY(1.18)}} .hero-control{display:flex;flex-direction:column;gap:14px;padding:28px 32px;min-width:0; justify-content:space-between; @@ -1428,6 +1455,9 @@

Welcome to InstantClone