openvibers/openvibe.live Commits
Hero render cost, part two: stop promoting the entire app to one layer
Changes
@@@ -298,13 +298,10 @@100% { transform: translate3d(3px, 9px, 0) rotate(calc(var(--rot, -2deg) * -0.6)) scale(var(--depth-scale, 1)); }}--/* Background frames: no filter; the darkening is an overlay painted once. */++/* Background frames: no filter. The darkening is a gradient layered into each frame's own++ background-image (app.js), so it costs neither a filter pass nor a second full-hero layer. */.hero-bg-layer { filter: none !important; will-change: auto !important; }.hero-bg-layer.active { will-change: transform !important; }--.hero-bg::after {-- content: ''; position: absolute; inset: 0; pointer-events: none;-- background: rgba(0, 0, 0, 0.5);--}/* Aurora: the gradients are already soft; the blur and blend were redundant and expensive. */.hero-aurora, .hero-aurora::before, .hero-aurora::after {@@@ -9,7 +9,12 @@background: color-mix(in srgb, var(--bg-card) 62%, transparent);border: 1px solid rgba(255,255,255,0.08);box-shadow: 0 24px 60px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.06);-- backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);++ /* No backdrop blur: it made this 1.2MP card its own layer re-blurring the page behind it. The++ fill is opaque enough on its own. The blur was also the only thing making .tour a stacking++ context, so isolation takes over that job — without it the hub's z-index:-1 glow escapes to++ main#app and the whole app gets promoted to a layer to sit above it. */++ background: color-mix(in srgb, var(--bg-card) 86%, transparent);++ isolation: isolate;}.tour::before { content: ''; position: absolute; inset: 0; pointer-events: none; opacity: 0.35;background-image: radial-gradient(rgba(255,255,255,0.10) 0.8px, transparent 0.9px); background-size: 22px 22px;@@@ -71,14 +71,21 @@ body {background-image: linear-gradient(180deg, var(--bg-secondary) 0%, var(--bg-primary) 42%);background-attachment: fixed;}++/* Ambient corner glows. These were fixed-position squares with filter: blur(70px). Fixed makes each++ one a composited layer, and main#app — which paints on top of them — then had to be promoted to++ its own layer for overlapping them: a single 12.8MP texture of the entire app, re-rasterised as++ the page changed, plus a 70px blur (among the most expensive filters there is) on top.++ A radial gradient reaches the same soft edge with no filter, and absolute positioning keeps the++ glows in the page's own layer, so nothing above them needs promoting. They sit behind the top of++ the page, which is where they were ever visible past the hero anyway. */body::before,body::after {content: '';-- position: fixed;++ position: absolute;pointer-events: none;z-index: 0;-- filter: blur(70px);opacity: 0.32;++ border-radius: 50%;}/* ── Vibe coding widget ───────────────────────────────────── */@@@ -833,19 +840,23 @@ body::after {display: grid;gap: 7px;}++/* Both glows stay inside the page's width — an absolutely positioned box past the right edge++ widens the document and scrolls it sideways. The gradient centre sits on the corner instead. */body::before {-- top: -80px;-- left: -30px;-- width: 280px;-- height: 280px;-- background: color-mix(in srgb, var(--accent) 28%, transparent);++ top: 0;++ left: 0;++ width: min(560px, 70vw);++ height: 560px;++ border-radius: 0;++ background: radial-gradient(circle at 12% 8%, color-mix(in srgb, var(--accent) 28%, transparent) 0%, color-mix(in srgb, var(--accent) 12%, transparent) 30%, transparent 62%);}body::after {-- right: -40px;-- top: 25vh;-- width: 240px;-- height: 240px;-- background: rgba(52, 152, 219, 0.15);++ right: 0;++ top: calc(25vh - 120px);++ width: min(480px, 60vw);++ height: 480px;++ border-radius: 0;++ background: radial-gradient(circle at 92% 50%, rgba(52, 152, 219, 0.15) 0%, rgba(52, 152, 219, 0.06) 32%, transparent 62%);}a { color: var(--accent); text-decoration: none; }a:hover { color: var(--accent-light); }@@@ -2498,7 +2509,9 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }background: conic-gradient(from var(--cta-angle, 0deg), transparent 0deg, color-mix(in srgb, var(--accent) 80%, transparent) 60deg, color-mix(in srgb, var(--accent) 15%, transparent) 140deg, transparent 200deg, color-mix(in srgb, var(--accent) 55%, transparent) 300deg, transparent 360deg);-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);-webkit-mask-composite: xor; mask-composite: exclude;-- animation: ctaBorder 6s linear infinite;++ /* Held still. The sweep animated a registered custom property, which repaints a conic gradient++ across the whole ~2MP banner every frame; a fixed angle keeps the lit edge without that. */++ --cta-angle: 210deg;opacity: 0.7;}@property --cta-angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }@@@ -2511,10 +2524,11 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }radial-gradient(38% 42% at 18% 22%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 60%),radial-gradient(34% 40% at 82% 30%, color-mix(in srgb, var(--accent-dark) 14%, transparent), transparent 60%),radial-gradient(46% 50% at 60% 96%, rgba(52,152,219,0.10), transparent 62%);-- filter: blur(8px);-- animation: ctaDrift 18s ease-in-out infinite alternate;++ /* No blur (the gradients already fade to nothing) and no drift: this layer is 140% the size++ of the banner — 4MP on desktop — and an animation, even paused off screen, keeps it++ promoted. A still glow reads the same. */++ animation: none;}--@keyframes ctaDrift { from { transform: translate3d(-2%, -1%, 0) scale(1); } to { transform: translate3d(3%, 2%, 0) scale(1.08); } }/* Rising embers */.cta-embers { position: absolute; inset: 0; z-index: -1; pointer-events: none; overflow: hidden; }@@@ -88,9 +88,9 @@overlay) and is fetched without blocking the first paint: media="print" makes the browsertreat it as non-critical, and the onload flips it back to all once it has arrived. --><link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>-- <link rel="stylesheet" href="/css/style.css?v=214">-- <link rel="stylesheet" href="/css/home-fx.css?v=18">-- <link rel="stylesheet" href="/css/home-tour.css?v=11">++ <link rel="stylesheet" href="/css/style.css?v=216">++ <link rel="stylesheet" href="/css/home-fx.css?v=19">++ <link rel="stylesheet" href="/css/home-tour.css?v=12"><link rel="stylesheet" href="/css/i18n-star.css?v=11"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/chat-shared.css?v=1" media="print" onload="this.media='all';this.onload=null">@@@ -5094,7 +5094,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig<script src="/js/theme-engine.js?v=4"></script><script src="/live-notify.js?v=3" defer></script><script src="/js/ov-skeleton.js?v=1" defer></script>--<script src="/js/ov-odometer.js?v=3" defer></script>++<script src="/js/ov-odometer.js?v=4" defer></script><script src="/js/ov-density.js?v=4" defer></script><!-- ── Route bundles ──────────────────────────────────────────────────────────Measured with Chrome's precise coverage on a loaded home page: 14.3% of the JavaScript this@@@ -5139,7 +5139,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig};})();</script>--<script src="/js/app.js?v=181" defer></script>++<script src="/js/app.js?v=182" defer></script><script src="/js/stream-player.js?v=22" defer></script><script src="/js/stream-pip.js?v=1" defer></script><script src="/js/chat-shared.js?v=3" defer></script>@@@ -2147,7 +2147,7 @@ function renderHeroBackground(media, moments) {if (!frames.length) { wrap.style.display = 'none'; return; }wrap.style.display = '';_heroBgActive = 0;-- layers[0].style.backgroundImage = `url("${frames[0]}")`;++ layers[0].style.backgroundImage = `linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url("${frames[0]}")`;layers[1].classList.remove('active');// Re-trigger the Ken-Burns animation on first paint.layers[0].classList.remove('active'); void layers[0].offsetWidth; layers[0].classList.add('active');@@@ -2160,7 +2160,9 @@ function renderHeroBackground(media, moments) {const el = layers[next];const pre = new Image();pre.onload = () => {-- el.style.backgroundImage = `url("${frames[idx]}")`;++ // The darkening is part of the frame's own background rather than a filter or an overlay element:++ // either of those becomes a second full-hero layer on top of an animating one.++ el.style.backgroundImage = `linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url("${frames[idx]}")`;el.classList.remove('active'); void el.offsetWidth; el.classList.add('active');layers[_heroBgActive].classList.remove('active');_heroBgActive = next;@@@ -27,7 +27,7 @@.ovnum{--ovnum-h:1.25em;display:inline-flex;align-items:center;line-height:var(--ovnum-h);font-variant-numeric:tabular-nums}.ovnum-cell{display:inline-block;line-height:var(--ovnum-h)}.ovnum-digit{display:inline-block;overflow:hidden;height:var(--ovnum-h);width:1ch;position:relative}--.ovnum-track{display:block;will-change:transform;transition:transform var(--ovnum-dur,.62s) cubic-bezier(.22,1,.28,1)}++.ovnum-track{display:block;transition:transform var(--ovnum-dur,.62s) cubic-bezier(.22,1,.28,1)}.ovnum-track span{display:block;height:var(--ovnum-h);line-height:var(--ovnum-h);text-align:center}.ovnum.is-up .ovnum-digit.changed{animation:ovnumUp .62s ease-out}.ovnum.is-down .ovnum-digit.changed{animation:ovnumDown .62s ease-out}
@@ -298,13 +298,10 @@
@@ -298,13 +298,10 @@
100% { transform: translate3d(3px, 9px, 0) rotate(calc(var(--rot, -2deg) * -0.6)) scale(var(--depth-scale, 1)); }
100% { transform: translate3d(3px, 9px, 0) rotate(calc(var(--rot, -2deg) * -0.6)) scale(var(--depth-scale, 1)); }
}
}
-/* Background frames: no filter; the darkening is an overlay painted once. */
+/* Background frames: no filter. The darkening is a gradient layered into each frame's own
+ background-image (app.js), so it costs neither a filter pass nor a second full-hero layer. */
.hero-bg-layer { filter: none !important; will-change: auto !important; }
.hero-bg-layer { filter: none !important; will-change: auto !important; }
.hero-bg-layer.active { will-change: transform !important; }
.hero-bg-layer.active { will-change: transform !important; }
-.hero-bg::after {
- content: ''; position: absolute; inset: 0; pointer-events: none;
- background: rgba(0, 0, 0, 0.5);
-}
/* Aurora: the gradients are already soft; the blur and blend were redundant and expensive. */
/* Aurora: the gradients are already soft; the blur and blend were redundant and expensive. */
.hero-aurora, .hero-aurora::before, .hero-aurora::after {
.hero-aurora, .hero-aurora::before, .hero-aurora::after {
@@ -9,7 +9,12 @@
@@ -9,7 +9,12 @@
background: color-mix(in srgb, var(--bg-card) 62%, transparent);
background: color-mix(in srgb, var(--bg-card) 62%, transparent);
border: 1px solid rgba(255,255,255,0.08);
border: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 24px 60px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.06);
box-shadow: 0 24px 60px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.06);
- backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
+ /* No backdrop blur: it made this 1.2MP card its own layer re-blurring the page behind it. The
+ fill is opaque enough on its own. The blur was also the only thing making .tour a stacking
+ context, so isolation takes over that job — without it the hub's z-index:-1 glow escapes to
+ main#app and the whole app gets promoted to a layer to sit above it. */
+ background: color-mix(in srgb, var(--bg-card) 86%, transparent);
+ isolation: isolate;
}
}
.tour::before { content: ''; position: absolute; inset: 0; pointer-events: none; opacity: 0.35;
.tour::before { content: ''; position: absolute; inset: 0; pointer-events: none; opacity: 0.35;
background-image: radial-gradient(rgba(255,255,255,0.10) 0.8px, transparent 0.9px); background-size: 22px 22px;
background-image: radial-gradient(rgba(255,255,255,0.10) 0.8px, transparent 0.9px); background-size: 22px 22px;
@@ -71,14 +71,21 @@ body {
@@ -71,14 +71,21 @@ body {
background-image: linear-gradient(180deg, var(--bg-secondary) 0%, var(--bg-primary) 42%);
background-image: linear-gradient(180deg, var(--bg-secondary) 0%, var(--bg-primary) 42%);
background-attachment: fixed;
background-attachment: fixed;
}
}
+/* Ambient corner glows. These were fixed-position squares with filter: blur(70px). Fixed makes each
+ one a composited layer, and main#app — which paints on top of them — then had to be promoted to
+ its own layer for overlapping them: a single 12.8MP texture of the entire app, re-rasterised as
+ the page changed, plus a 70px blur (among the most expensive filters there is) on top.
+ A radial gradient reaches the same soft edge with no filter, and absolute positioning keeps the
+ glows in the page's own layer, so nothing above them needs promoting. They sit behind the top of
+ the page, which is where they were ever visible past the hero anyway. */
body::before,
body::before,
body::after {
body::after {
content: '';
content: '';
- position: fixed;
+ position: absolute;
pointer-events: none;
pointer-events: none;
z-index: 0;
z-index: 0;
- filter: blur(70px);
opacity: 0.32;
opacity: 0.32;
+ border-radius: 50%;
}
}
/* ── Vibe coding widget ───────────────────────────────────── */
/* ── Vibe coding widget ───────────────────────────────────── */
@@ -833,19 +840,23 @@ body::after {
@@ -833,19 +840,23 @@ body::after {
display: grid;
display: grid;
gap: 7px;
gap: 7px;
}
}
+/* Both glows stay inside the page's width — an absolutely positioned box past the right edge
+ widens the document and scrolls it sideways. The gradient centre sits on the corner instead. */
body::before {
body::before {
- top: -80px;
- left: -30px;
- width: 280px;
- height: 280px;
- background: color-mix(in srgb, var(--accent) 28%, transparent);
+ top: 0;
+ left: 0;
+ width: min(560px, 70vw);
+ height: 560px;
+ border-radius: 0;
+ background: radial-gradient(circle at 12% 8%, color-mix(in srgb, var(--accent) 28%, transparent) 0%, color-mix(in srgb, var(--accent) 12%, transparent) 30%, transparent 62%);
}
}
body::after {
body::after {
- right: -40px;
- top: 25vh;
- width: 240px;
- height: 240px;
- background: rgba(52, 152, 219, 0.15);
+ right: 0;
+ top: calc(25vh - 120px);
+ width: min(480px, 60vw);
+ height: 480px;
+ border-radius: 0;
+ background: radial-gradient(circle at 92% 50%, rgba(52, 152, 219, 0.15) 0%, rgba(52, 152, 219, 0.06) 32%, transparent 62%);
}
}
a { color: var(--accent); text-decoration: none; }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-light); }
a:hover { color: var(--accent-light); }
@@ -2498,7 +2509,9 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
@@ -2498,7 +2509,9 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
background: conic-gradient(from var(--cta-angle, 0deg), transparent 0deg, color-mix(in srgb, var(--accent) 80%, transparent) 60deg, color-mix(in srgb, var(--accent) 15%, transparent) 140deg, transparent 200deg, color-mix(in srgb, var(--accent) 55%, transparent) 300deg, transparent 360deg);
background: conic-gradient(from var(--cta-angle, 0deg), transparent 0deg, color-mix(in srgb, var(--accent) 80%, transparent) 60deg, color-mix(in srgb, var(--accent) 15%, transparent) 140deg, transparent 200deg, color-mix(in srgb, var(--accent) 55%, transparent) 300deg, transparent 360deg);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor; mask-composite: exclude;
-webkit-mask-composite: xor; mask-composite: exclude;
- animation: ctaBorder 6s linear infinite;
+ /* Held still. The sweep animated a registered custom property, which repaints a conic gradient
+ across the whole ~2MP banner every frame; a fixed angle keeps the lit edge without that. */
+ --cta-angle: 210deg;
opacity: 0.7;
opacity: 0.7;
}
}
@property --cta-angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
@property --cta-angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
@@ -2511,10 +2524,11 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
@@ -2511,10 +2524,11 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
radial-gradient(38% 42% at 18% 22%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 60%),
radial-gradient(38% 42% at 18% 22%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 60%),
radial-gradient(34% 40% at 82% 30%, color-mix(in srgb, var(--accent-dark) 14%, transparent), transparent 60%),
radial-gradient(34% 40% at 82% 30%, color-mix(in srgb, var(--accent-dark) 14%, transparent), transparent 60%),
radial-gradient(46% 50% at 60% 96%, rgba(52,152,219,0.10), transparent 62%);
radial-gradient(46% 50% at 60% 96%, rgba(52,152,219,0.10), transparent 62%);
- filter: blur(8px);
- animation: ctaDrift 18s ease-in-out infinite alternate;
+ /* No blur (the gradients already fade to nothing) and no drift: this layer is 140% the size
+ of the banner — 4MP on desktop — and an animation, even paused off screen, keeps it
+ promoted. A still glow reads the same. */
+ animation: none;
}
}
-@keyframes ctaDrift { from { transform: translate3d(-2%, -1%, 0) scale(1); } to { transform: translate3d(3%, 2%, 0) scale(1.08); } }
/* Rising embers */
/* Rising embers */
.cta-embers { position: absolute; inset: 0; z-index: -1; pointer-events: none; overflow: hidden; }
.cta-embers { position: absolute; inset: 0; z-index: -1; pointer-events: none; overflow: hidden; }
@@ -88,9 +88,9 @@
@@ -88,9 +88,9 @@
overlay) and is fetched without blocking the first paint: media="print" makes the browser
overlay) and is fetched without blocking the first paint: media="print" makes the browser
treat it as non-critical, and the onload flips it back to all once it has arrived. -->
treat it as non-critical, and the onload flips it back to all once it has arrived. -->
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
- <link rel="stylesheet" href="/css/style.css?v=214">
- <link rel="stylesheet" href="/css/home-fx.css?v=18">
- <link rel="stylesheet" href="/css/home-tour.css?v=11">
+ <link rel="stylesheet" href="/css/style.css?v=216">
+ <link rel="stylesheet" href="/css/home-fx.css?v=19">
+ <link rel="stylesheet" href="/css/home-tour.css?v=12">
<link rel="stylesheet" href="/css/i18n-star.css?v=11">
<link rel="stylesheet" href="/css/i18n-star.css?v=11">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/chat-shared.css?v=1" media="print" onload="this.media='all';this.onload=null">
<link rel="stylesheet" href="/css/chat-shared.css?v=1" media="print" onload="this.media='all';this.onload=null">
@@ -5094,7 +5094,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
@@ -5094,7 +5094,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
<script src="/js/theme-engine.js?v=4"></script>
<script src="/js/theme-engine.js?v=4"></script>
<script src="/live-notify.js?v=3" defer></script>
<script src="/live-notify.js?v=3" defer></script>
<script src="/js/ov-skeleton.js?v=1" defer></script>
<script src="/js/ov-skeleton.js?v=1" defer></script>
-<script src="/js/ov-odometer.js?v=3" defer></script>
+<script src="/js/ov-odometer.js?v=4" defer></script>
<script src="/js/ov-density.js?v=4" defer></script>
<script src="/js/ov-density.js?v=4" defer></script>
<!-- ── Route bundles ──────────────────────────────────────────────────────────
<!-- ── Route bundles ──────────────────────────────────────────────────────────
Measured with Chrome's precise coverage on a loaded home page: 14.3% of the JavaScript this
Measured with Chrome's precise coverage on a loaded home page: 14.3% of the JavaScript this
@@ -5139,7 +5139,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
@@ -5139,7 +5139,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
};
};
})();
})();
</script>
</script>
-<script src="/js/app.js?v=181" defer></script>
+<script src="/js/app.js?v=182" defer></script>
<script src="/js/stream-player.js?v=22" defer></script>
<script src="/js/stream-player.js?v=22" defer></script>
<script src="/js/stream-pip.js?v=1" defer></script>
<script src="/js/stream-pip.js?v=1" defer></script>
<script src="/js/chat-shared.js?v=3" defer></script>
<script src="/js/chat-shared.js?v=3" defer></script>
@@ -2147,7 +2147,7 @@ function renderHeroBackground(media, moments) {
@@ -2147,7 +2147,7 @@ function renderHeroBackground(media, moments) {
if (!frames.length) { wrap.style.display = 'none'; return; }
if (!frames.length) { wrap.style.display = 'none'; return; }
wrap.style.display = '';
wrap.style.display = '';
_heroBgActive = 0;
_heroBgActive = 0;
- layers[0].style.backgroundImage = `url("${frames[0]}")`;
+ layers[0].style.backgroundImage = `linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url("${frames[0]}")`;
layers[1].classList.remove('active');
layers[1].classList.remove('active');
// Re-trigger the Ken-Burns animation on first paint.
// Re-trigger the Ken-Burns animation on first paint.
layers[0].classList.remove('active'); void layers[0].offsetWidth; layers[0].classList.add('active');
layers[0].classList.remove('active'); void layers[0].offsetWidth; layers[0].classList.add('active');
@@ -2160,7 +2160,9 @@ function renderHeroBackground(media, moments) {
@@ -2160,7 +2160,9 @@ function renderHeroBackground(media, moments) {
const el = layers[next];
const el = layers[next];
const pre = new Image();
const pre = new Image();
pre.onload = () => {
pre.onload = () => {
- el.style.backgroundImage = `url("${frames[idx]}")`;
+ // The darkening is part of the frame's own background rather than a filter or an overlay element:
+ // either of those becomes a second full-hero layer on top of an animating one.
+ el.style.backgroundImage = `linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url("${frames[idx]}")`;
el.classList.remove('active'); void el.offsetWidth; el.classList.add('active');
el.classList.remove('active'); void el.offsetWidth; el.classList.add('active');
layers[_heroBgActive].classList.remove('active');
layers[_heroBgActive].classList.remove('active');
_heroBgActive = next;
_heroBgActive = next;
@@ -27,7 +27,7 @@
@@ -27,7 +27,7 @@
.ovnum{--ovnum-h:1.25em;display:inline-flex;align-items:center;line-height:var(--ovnum-h);font-variant-numeric:tabular-nums}
.ovnum{--ovnum-h:1.25em;display:inline-flex;align-items:center;line-height:var(--ovnum-h);font-variant-numeric:tabular-nums}
.ovnum-cell{display:inline-block;line-height:var(--ovnum-h)}
.ovnum-cell{display:inline-block;line-height:var(--ovnum-h)}
.ovnum-digit{display:inline-block;overflow:hidden;height:var(--ovnum-h);width:1ch;position:relative}
.ovnum-digit{display:inline-block;overflow:hidden;height:var(--ovnum-h);width:1ch;position:relative}
-.ovnum-track{display:block;will-change:transform;transition:transform var(--ovnum-dur,.62s) cubic-bezier(.22,1,.28,1)}
+.ovnum-track{display:block;transition:transform var(--ovnum-dur,.62s) cubic-bezier(.22,1,.28,1)}
.ovnum-track span{display:block;height:var(--ovnum-h);line-height:var(--ovnum-h);text-align:center}
.ovnum-track span{display:block;height:var(--ovnum-h);line-height:var(--ovnum-h);text-align:center}
.ovnum.is-up .ovnum-digit.changed{animation:ovnumUp .62s ease-out}
.ovnum.is-up .ovnum-digit.changed{animation:ovnumUp .62s ease-out}
.ovnum.is-down .ovnum-digit.changed{animation:ovnumDown .62s ease-out}
.ovnum.is-down .ovnum-digit.changed{animation:ovnumDown .62s ease-out}