openvibers/openvibe.live Commits

Move hand-set motion timings onto shared motion tokens (#514)

OP OpenVibers committed 12 days ago · 717620f106 · 29 files, +619 −619

* The home page: hand-set motion timings onto the shared motion tokens

* A channel: hand-set motion timings onto the shared motion tokens

* The broadcast desk: hand-set motion timings onto the shared motion tokens

* Chat: hand-set motion timings onto the shared motion tokens

* The dashboard: hand-set motion timings onto the shared motion tokens

* The rest of Live: hand-set motion timings onto the shared motion tokens

Changes

ddiff --git a/public/banned.html b/public/banned.html
iindex 45106a9d..69d6abf0 100644
---- a/public/banned.html
++++ b/public/banned.html
@@@ -14,7 +14,7 @@
background: #0b0a0d; overflow-x: hidden; position: relative; }
/* Breathing red ground + slow drifting grain */
.bg { position: fixed; inset: 0; z-index: 0; pointer-events: none;
-- background: radial-gradient(120% 90% at 50% 115%, #4a0b0b 0%, #1a0708 45%, #0b0a0d 75%); animation: breathe 6s ease-in-out infinite; }
++ background: radial-gradient(120% 90% at 50% 115%, #4a0b0b 0%, #1a0708 45%, #0b0a0d 75%); animation: breathe 6s var(--ov-ease-in-out, ease-in-out) infinite; }
.bg::after { content: ''; position: absolute; inset: -50%; opacity: 0.07;
background-image: radial-gradient(rgba(255,255,255,0.9) 0.6px, transparent 0.7px); background-size: 3px 3px; animation: grain 1.4s steps(6) infinite; }
.vignette { position: fixed; inset: 0; z-index: 0; pointer-events: none; box-shadow: inset 0 0 160px 40px rgba(0,0,0,0.85); }
@@@ -26,13 +26,13 @@
/* BANNED stamp slams in, the card shakes from the hit */
.stamp { display: inline-block; padding: 8px 24px; border: 4px solid var(--red); color: var(--red); border-radius: 10px; font-weight: 900; font-size: clamp(2rem, 8vw, 3.4rem); letter-spacing: 0.18em;
-- transform: rotate(-4deg); text-shadow: 0 0 20px rgba(231,76,60,0.5); animation: slam 0.55s cubic-bezier(.2,1.6,.4,1) both; }
++ transform: rotate(-4deg); text-shadow: 0 0 20px rgba(231,76,60,0.5); animation: slam var(--ov-dur-4, 0.55s) var(--ov-ease-spring, cubic-bezier(.2,1.6,.4,1)) both; }
@keyframes slam { 0% { transform: rotate(-14deg) scale(4); opacity: 0; } 60% { transform: rotate(-3deg) scale(0.96); opacity: 1; } 80% { transform: rotate(-5deg) scale(1.03); } 100% { transform: rotate(-4deg) scale(1); } }
-- .card.shake { animation: shake 0.4s 0.35s both; }
++ .card.shake { animation: shake var(--ov-dur-3, 0.4s) 0.35s both; }
@keyframes shake { 0%,100% { transform: translate(0,0); } 20% { transform: translate(-7px,2px); } 40% { transform: translate(6px,-3px); } 60% { transform: translate(-4px,2px); } 80% { transform: translate(3px,-1px); } }
/* Reveal sequence */
-- .rv { opacity: 0; transform: translateY(18px); animation: rise 0.7s ease-out forwards; animation-delay: var(--d, 0s); }
++ .rv { opacity: 0; transform: translateY(18px); animation: rise var(--ov-dur-4, 0.7s) var(--ov-ease-out, ease-out) forwards; animation-delay: var(--d, 0s); }
@keyframes rise { to { opacity: 1; transform: none; } }
.plea { margin: 26px 0 8px; font-size: clamp(2.2rem, 8.5vw, 4.6rem); line-height: 1.02; font-weight: 900; letter-spacing: -0.02em; color: #fff; text-shadow: 0 6px 30px rgba(0,0,0,0.6); }
@@@ -41,9 +41,9 @@
/* What happened — lines land one after another like messages */
.story { display: grid; gap: 10px; max-width: 640px; margin: 0 auto 24px; text-align: left; }
.msg { position: relative; padding: 12px 16px; border-radius: 16px 16px 16px 4px; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1); color: #f3efec; font-size: clamp(1rem, 2.8vw, 1.15rem); line-height: 1.45; font-weight: 600;
-- opacity: 0; transform: translateX(-24px) scale(0.98); animation: land 0.55s cubic-bezier(.2,.9,.3,1.2) forwards; animation-delay: var(--d); }
++ opacity: 0; transform: translateX(-24px) scale(0.98); animation: land var(--ov-dur-4, 0.55s) var(--ov-ease-out, cubic-bezier(.2,.9,.3,1.2)) forwards; animation-delay: var(--d); }
.msg.hot { background: rgba(231,76,60,0.16); border-color: rgba(231,76,60,0.55); color: #fff; }
-- .msg.hot::before { content: ''; position: absolute; inset: -1px; border-radius: inherit; animation: ring 1.6s ease-out 1; animation-delay: calc(var(--d) + 0.3s); pointer-events: none; }
++ .msg.hot::before { content: ''; position: absolute; inset: -1px; border-radius: inherit; animation: ring 1.6s var(--ov-ease-out, ease-out) 1; animation-delay: calc(var(--d) + 0.3s); pointer-events: none; }
@keyframes land { to { opacity: 1; transform: none; } }
@keyframes ring { 0% { box-shadow: 0 0 0 0 rgba(231,76,60,0.7); } 100% { box-shadow: 0 0 0 18px rgba(231,76,60,0); } }
.msg small { display: block; font-size: 0.7rem; letter-spacing: 0.12em; text-transform: uppercase; color: #c9a19b; margin-bottom: 3px; font-weight: 800; }
@@@ -51,7 +51,7 @@
/* The verdict — heartbeat */
.hurt { margin: 8px 0 14px; padding: 22px 18px 24px; border-radius: 18px; background: rgba(231,76,60,0.14); border: 2px solid rgba(231,76,60,0.75);
box-shadow: 0 0 40px rgba(231,76,60,0.25) inset, 0 0 30px rgba(231,76,60,0.2); }
-- .hurt.beat { animation: rise 0.7s ease-out forwards, heartbeat 1.7s ease-in-out infinite; animation-delay: var(--d), calc(var(--d) + 0.7s); }
++ .hurt.beat { animation: rise var(--ov-dur-4, 0.7s) var(--ov-ease-out, ease-out) forwards, heartbeat 1.7s var(--ov-ease-in-out, ease-in-out) infinite; animation-delay: var(--d), calc(var(--d) + 0.7s); }
@keyframes heartbeat { 0%, 100% { transform: scale(1); box-shadow: 0 0 40px rgba(231,76,60,0.25) inset, 0 0 30px rgba(231,76,60,0.2); } 14% { transform: scale(1.025); box-shadow: 0 0 50px rgba(231,76,60,0.35) inset, 0 0 60px rgba(231,76,60,0.45); } 28% { transform: scale(1); } 42% { transform: scale(1.018); box-shadow: 0 0 50px rgba(231,76,60,0.3) inset, 0 0 50px rgba(231,76,60,0.35); } 56% { transform: scale(1); } }
.hurt-head { font-size: clamp(2rem, 8vw, 4rem); line-height: 1.02; font-weight: 900; letter-spacing: -0.02em; color: var(--red-soft); text-shadow: 0 0 30px rgba(231,76,60,0.6); margin: 0 0 12px; }
.hurt-body { font-size: clamp(1.1rem, 3.1vw, 1.55rem); line-height: 1.4; color: #fff; font-weight: 700; margin: 0; }
@@@ -62,30 +62,30 @@
.sorry-head { margin: 0 0 6px; font-size: clamp(1.3rem, 4vw, 1.8rem); font-weight: 900; color: #fff; }
.sorry-body { margin: 0 0 16px; font-size: clamp(0.98rem, 2.8vw, 1.1rem); line-height: 1.5; color: #d9d4ef; font-weight: 600; }
.go { position: relative; display: inline-flex; align-items: center; gap: 10px; padding: 14px 30px; border: 0; border-radius: 999px; cursor: pointer; color: #fff; font: inherit; font-weight: 900; font-size: 1.1rem; letter-spacing: 0.02em;
-- background: linear-gradient(120deg, #7c3aed, #a78bfa 50%, #22d3ee); background-size: 200% 100%; box-shadow: 0 10px 30px rgba(124,58,237,0.45), 0 0 0 0 rgba(167,139,250,0.6); animation: sheen 3s linear infinite, pulse 2.2s ease-out infinite; transition: transform 0.15s; overflow: hidden; }
++ background: linear-gradient(120deg, #7c3aed, #a78bfa 50%, #22d3ee); background-size: 200% 100%; box-shadow: 0 10px 30px rgba(124,58,237,0.45), 0 0 0 0 rgba(167,139,250,0.6); animation: sheen 3s linear infinite, pulse 2.2s var(--ov-ease-out, ease-out) infinite; transition: transform var(--ov-dur-2, 0.15s); overflow: hidden; }
.go:hover { transform: translateY(-2px) scale(1.03); }
.go:active { transform: scale(0.97); }
.go:disabled { cursor: default; animation: none; opacity: 0.85; }
-- .go .arrow { display: inline-block; transition: transform 0.2s; }
++ .go .arrow { display: inline-block; transition: transform var(--ov-dur-2, 0.2s); }
.go:hover .arrow { transform: translateX(4px); }
-- .go .rip { position: absolute; border-radius: 50%; background: rgba(255,255,255,0.5); transform: scale(0); animation: rip 0.7s ease-out forwards; pointer-events: none; }
++ .go .rip { position: absolute; border-radius: 50%; background: rgba(255,255,255,0.5); transform: scale(0); animation: rip var(--ov-dur-4, 0.7s) var(--ov-ease-out, ease-out) forwards; pointer-events: none; }
@keyframes sheen { to { background-position: -200% 0; } }
@keyframes pulse { 0% { box-shadow: 0 10px 30px rgba(124,58,237,0.45), 0 0 0 0 rgba(167,139,250,0.6); } 100% { box-shadow: 0 10px 30px rgba(124,58,237,0.45), 0 0 0 22px rgba(167,139,250,0); } }
@keyframes rip { to { transform: scale(30); opacity: 0; } }
.go-hint { display: block; margin-top: 10px; font-size: 0.8rem; color: #9b93b8; }
/* The lift: red drains out, card breathes out, "welcome back" takes over */
-- body.lifted .bg { animation: none; background: radial-gradient(120% 90% at 50% 115%, #2a1656 0%, #0f0a1c 45%, #0b0a0d 75%); transition: background 1.4s ease; }
++ body.lifted .bg { animation: none; background: radial-gradient(120% 90% at 50% 115%, #2a1656 0%, #0f0a1c 45%, #0b0a0d 75%); transition: background 1.4s var(--ov-ease-out, ease); }
body.lifted .card { border-color: rgba(167,139,250,0.55); box-shadow: 0 30px 80px rgba(0,0,0,0.7), 0 0 90px rgba(167,139,250,0.35); transition: border-color 1.2s, box-shadow 1.2s; }
body.lifted .stamp { animation: crack 0.9s ease-in forwards; }
@keyframes crack { 0% { transform: rotate(-4deg); } 30% { transform: rotate(-9deg) scale(1.05); } 100% { transform: rotate(18deg) translateY(120px) scale(0.6); opacity: 0; } }
body.lifted .plea, body.lifted .plea-body, body.lifted .story, body.lifted .hurt, body.lifted .who, body.lifted .reason, body.lifted .perm, body.lifted .sorry { animation: melt 1s ease-in forwards !important; }
@keyframes melt { to { opacity: 0; transform: translateY(30px) scale(0.98); filter: blur(6px); } }
.welcome { position: fixed; inset: 0; z-index: 5; display: grid; place-items: center; text-align: center; padding: 24px; opacity: 0; pointer-events: none; }
-- body.lifted .welcome { animation: rise 0.9s ease-out 1s forwards; }
++ body.lifted .welcome { animation: rise 0.9s var(--ov-ease-out, ease-out) 1s forwards; }
.welcome h2 { margin: 0 0 8px; font-size: clamp(2.4rem, 9vw, 5rem); font-weight: 900; letter-spacing: -0.02em; color: #fff; text-shadow: 0 0 40px rgba(167,139,250,0.8); }
.welcome p { margin: 0; font-size: clamp(1.1rem, 3.4vw, 1.5rem); color: #d9d4ef; font-weight: 600; }
-- .spark { position: fixed; z-index: 4; width: 6px; height: 6px; border-radius: 50%; background: #c4b5fd; box-shadow: 0 0 12px #a78bfa; pointer-events: none; animation: fly 1.4s ease-out forwards; }
++ .spark { position: fixed; z-index: 4; width: 6px; height: 6px; border-radius: 50%; background: #c4b5fd; box-shadow: 0 0 12px #a78bfa; pointer-events: none; animation: fly 1.4s var(--ov-ease-out, ease-out) forwards; }
@keyframes fly { to { transform: translate(var(--x), var(--y)) scale(0); opacity: 0; } }
.who { font-size: 1.05rem; color: #cfc9c4; margin: 18px 0 6px; }
ddiff --git a/public/css/broadcast.css b/public/css/broadcast.css
iindex e9d855c1..6beec1f6 100644
---- a/public/css/broadcast.css
++++ b/public/css/broadcast.css
@@@ -30,7 +30,7 @@
align-items: center;
gap: 8px;
position: relative;
-- transition: color 0.2s ease;
++ transition: color var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease);
white-space: nowrap;
}
@@@ -45,7 +45,7 @@
height: 3px;
background: var(--accent);
border-radius: 3px 3px 0 0;
-- transition: all 0.3s ease;
++ transition: all var(--ov-dur-3, 0.3s) var(--ov-ease-out, ease);
width: 0;
}
@@@ -120,7 +120,7 @@
justify-content: center;
color: var(--text-primary);
z-index: 10;
-- transition: background 0.2s ease;
++ transition: background var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease);
}
.bc-modal-close:hover {
@@@ -394,7 +394,7 @@
background-color: currentColor;
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 6 6 6-6 6'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 6 6 6-6 6'/%3E%3C/svg%3E") center / contain no-repeat;
-- transition: transform 0.2s ease;
++ transition: transform var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease);
}
.bc-screen-advanced[open] summary::before {
transform: rotate(90deg);
@@@ -468,7 +468,7 @@
gap: 4px;
z-index: 10;
opacity: 0;
-- transition: opacity 0.25s;
++ transition: opacity var(--ov-dur-2, 0.25s);
}
.bc-pip-controls button {
background: rgba(0,0,0,0.65);
@@@ -555,12 +555,12 @@
align-items: center;
gap: 14px;
cursor: pointer;
-- transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease, box-shadow 0.18s ease;
++ transition: transform var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease), border-color var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease), background var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease), box-shadow var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease);
}
/* ── Restream Control Panel ─────────────────────────────────── */
.bc-restream-status-badge i { font-size: 0.65rem; }
--.bc-restream-status-live i { animation: restream-pulse 1.5s ease-in-out infinite; }
++.bc-restream-status-live i { animation: restream-pulse 1.5s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes restream-pulse {
0%, 100% { opacity: 1; }
@@@ -594,7 +594,7 @@
/* Legacy RS control panel (kept for backward compat) */
.bc-rs-status i { font-size: 0.7rem; }
--.bc-rs-live i { animation: restream-pulse 1.5s ease-in-out infinite; }
++.bc-rs-live i { animation: restream-pulse 1.5s var(--ov-ease-in-out, ease-in-out) infinite; }
/* ── Settings Rows ────────────────────────────────────────────── */
@@@ -633,7 +633,7 @@
background: var(--bg-hover);
border-radius: 22px;
border: 1px solid var(--border);
-- transition: 0.2s;
++ transition: var(--ov-dur-2, 0.2s);
}
.bc-toggle input:checked + .bc-toggle-slider {
@@@ -796,7 +796,7 @@
max-height: clamp(240px, 46dvh, 460px);
border-radius: 0;
aspect-ratio: unset; /* let video size naturally on mobile */
-- transition: min-height 0.3s ease, max-height 0.3s ease;
++ transition: min-height var(--ov-dur-3, 0.3s) var(--ov-ease-out, ease), max-height var(--ov-dur-3, 0.3s) var(--ov-ease-out, ease);
}
/* When streaming vertically on phone, give the preview more room */
@@@ -812,7 +812,7 @@
background: var(--bg-secondary);
z-index: 1002;
transform: translateY(100%);
-- transition: transform 0.3s ease;
++ transition: transform var(--ov-dur-3, 0.3s) var(--ov-ease-out, ease);
border-radius: 16px 16px 0 0;
box-shadow: 0 -8px 32px rgba(0,0,0,0.15);
}
@@@ -1058,7 +1058,7 @@
border: 2px solid var(--border);
border-radius: var(--radius);
cursor: pointer;
-- transition: all 0.2s;
++ transition: all var(--ov-dur-2, 0.2s);
text-align: center;
user-select: none;
}
@@@ -1169,7 +1169,7 @@
color: var(--text-secondary);
font-size: 0.75rem;
cursor: pointer;
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
}
.bc-secret-token-block {
@@@ -1193,7 +1193,7 @@
cursor: pointer;
list-style: none;
user-select: none;
-- transition: background 0.15s;
++ transition: background var(--ov-dur-2, 0.15s);
}
.bc-app-guide summary::-webkit-details-marker { display: none; }
@@@ -1333,7 +1333,7 @@
font-size: 0.82rem;
color: var(--text-secondary);
line-height: 1.5;
-- transition: all 0.25s ease;
++ transition: all var(--ov-dur-2, 0.25s) var(--ov-ease-out, ease);
}
.bc-method-explainer i {
@@@ -1373,7 +1373,7 @@
display: flex;
align-items: center;
gap: 8px;
-- transition: background 0.15s, color 0.15s;
++ transition: background var(--ov-dur-2, 0.15s), color var(--ov-dur-2, 0.15s);
}
.bc-help-guide summary::-webkit-details-marker { display: none; }
@@@ -1597,7 +1597,7 @@
/* ── External Method Live Action Bar ───────────────────── */
.bc-ext-live-badge i {
font-size: 8px;
-- animation: bc-live-pulse 1.5s ease-in-out infinite;
++ animation: bc-live-pulse 1.5s var(--ov-ease-in-out, ease-in-out) infinite;
}
.bc-ext-live-info strong {
color: var(--text-primary);
@@@ -1695,12 +1695,12 @@
border-radius: 10px;
backdrop-filter: blur(12px);
box-shadow: 0 8px 32px rgba(0,0,0,0.4), 0 2px 8px rgba(0,0,0,0.2);
-- animation: bc-save-bar-in 0.25s ease;
++ animation: bc-save-bar-in var(--ov-dur-2, 0.25s) var(--ov-ease-out, ease);
max-width: 500px;
width: auto;
}
.bc-ws-save-bar.hidden {
-- animation: bc-save-bar-out 0.2s ease forwards;
++ animation: bc-save-bar-out var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease) forwards;
pointer-events: none;
}
@keyframes bc-save-bar-in {
@@@ -1724,7 +1724,7 @@
align-items: center;
justify-content: center;
padding: 16px;
-- animation: bc-confirm-fade-in 0.15s ease;
++ animation: bc-confirm-fade-in var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
@keyframes bc-confirm-fade-in {
from { opacity: 0; }
@@@ -1842,7 +1842,7 @@
background: rgba(6, 8, 16, 0.72);
backdrop-filter: blur(4px);
display: flex; align-items: center; justify-content: center;
-- padding: 20px; animation: bcPermIn .14s ease-out;
++ padding: 20px; animation: bcPermIn var(--ov-dur-1, .14s) var(--ov-ease-out, ease-out);
}
@keyframes bcPermIn { from { opacity: 0 } to { opacity: 1 } }
.bc-perm-head h3 { margin: 0 0 4px; font-size: 1.05rem; }
@@@ -1891,7 +1891,7 @@ body:not(.ws-has-powerchat) .bc-ws-pc-only { display: none !important; }
flex: 0 0 auto; white-space: nowrap; gap: 7px;
padding: 6px 12px 6px 9px; border-radius: 999px;
background: var(--bg-tertiary); border: 1px solid var(--border);
-- font-size: 0.84rem; line-height: 1; transition: border-color .15s ease, background .15s ease;
++ font-size: 0.84rem; line-height: 1; transition: border-color var(--ov-dur-2, .15s) var(--ov-ease-out, ease), background var(--ov-dur-2, .15s) var(--ov-ease-out, ease);
}
.bc-ws-toggle-chip:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.bc-ws-toggle-chip:has(input:checked) {
ddiff --git a/public/css/chat-shared.css b/public/css/chat-shared.css
iindex 5d6777cb..ba1942ae 100644
---- a/public/css/chat-shared.css
++++ b/public/css/chat-shared.css
@@@ -14,7 +14,7 @@
border-radius: var(--radius);
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
overflow: hidden;
-- animation: ctxFadeIn 0.12s ease-out;
++ animation: ctxFadeIn var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease-out);
}
@keyframes ctxFadeIn {
from { opacity: 0; transform: scale(0.95); }
@@@ -93,7 +93,7 @@
color: var(--text-primary); font-size: 0.85rem;
border-radius: var(--radius-sm);
cursor: pointer; text-align: left;
-- transition: background 0.1s;
++ transition: background var(--ov-dur-1, 0.1s);
}
.ctx-btn:hover { background: var(--bg-hover); }
.ctx-btn i { width: 16px; text-align: center; color: var(--text-muted); font-size: 0.8rem; }
@@@ -108,7 +108,7 @@
.chat-msg-deleted {
opacity: 0;
transform: translateX(-20px);
-- transition: opacity 0.3s, transform 0.3s;
++ transition: opacity var(--ov-dur-3, 0.3s), transform var(--ov-dur-3, 0.3s);
max-height: 0;
overflow: hidden;
margin: 0;
@@@ -118,7 +118,7 @@
/* ── Rename submenu ── */
.ctx-rename-group { position: relative; }
.ctx-rename-group > .ctx-btn { justify-content: flex-start; }
--.ctx-rename-arrow { margin-left: auto; font-size: 0.65rem; transition: transform 0.15s; }
++.ctx-rename-arrow { margin-left: auto; font-size: 0.65rem; transition: transform var(--ov-dur-2, 0.15s); }
.ctx-rename-group.open .ctx-rename-arrow { transform: rotate(90deg); }
.ctx-rename-submenu {
display: none; flex-direction: column;
@@@ -134,7 +134,7 @@
text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent);
text-underline-offset: 2px;
cursor: pointer;
-- transition: color 0.15s, text-decoration-color 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), text-decoration-color var(--ov-dur-2, 0.15s);
word-break: break-all;
}
.chat-link:hover {
@@@ -158,7 +158,7 @@
padding: 0;
max-width: 440px; width: 92%;
box-shadow: 0 16px 48px rgba(0,0,0,0.5);
-- animation: linkTrustIn 0.18s ease-out;
++ animation: linkTrustIn var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease-out);
overflow: hidden;
}
@keyframes linkTrustIn {
@@@ -220,7 +220,7 @@
cursor: pointer; padding: 4px 6px;
border-radius: 4px;
font-size: 0.85rem;
-- transition: color 0.15s, background 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
}
.link-trust-copy:hover {
color: var(--text-primary, #eee);
@@@ -239,7 +239,7 @@
border-radius: 8px;
font-size: 0.82rem; font-weight: 500;
cursor: pointer;
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
background: var(--bg-secondary, #2e2e38);
color: var(--text-primary, #eee);
}
@@@ -280,7 +280,7 @@
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
overflow: hidden;
padding: 4px;
-- animation: ctxFadeIn 0.12s ease-out;
++ animation: ctxFadeIn var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease-out);
}
.link-ctx-btn {
@@@ -292,7 +292,7 @@
font-size: 0.82rem;
cursor: pointer;
border-radius: 6px;
-- transition: background 0.12s;
++ transition: background var(--ov-dur-1, 0.12s);
text-align: left;
}
.link-ctx-btn:hover {
@@@ -329,7 +329,7 @@
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
display: flex; flex-direction: column;
overflow: hidden;
-- animation: linkTrustIn 0.2s ease-out;
++ animation: linkTrustIn var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease-out);
}
.link-preview-header {
@@@ -364,7 +364,7 @@
color: var(--text-muted, #777);
cursor: pointer; padding: 2px 4px;
font-size: 0.8rem;
-- transition: color 0.15s;
++ transition: color var(--ov-dur-2, 0.15s);
}
.link-preview-copy:hover { color: var(--text-primary); }
@@@ -380,7 +380,7 @@
color: var(--text-primary, #eee);
font-size: 0.78rem;
cursor: pointer;
-- transition: all 0.12s;
++ transition: all var(--ov-dur-1, 0.12s);
display: flex; align-items: center; gap: 5px;
}
.link-preview-btn:hover {
@@@ -456,19 +456,19 @@
/* New rows arrive with a short rise; history (rendered off-screen and swapped in) does not animate. */
@keyframes chatRowIn { from { opacity: 0; transform: translateY(6px); } }
--.chat-msg.chat-msg-new { animation: chatRowIn 180ms cubic-bezier(.2, .8, .3, 1); }
++.chat-msg.chat-msg-new { animation: chatRowIn var(--ov-dur-2, 180ms) var(--ov-ease-out, cubic-bezier(.2, .8, .3, 1)); }
@media (prefers-reduced-motion: reduce) { .chat-msg.chat-msg-new { animation: none; } }
/* Friendly global chat: folded rows. */
.chat-friendly-group { margin: 2px 0; }
.chat-friendly-group > .chat-msg { display: none; }
--.chat-friendly-group.is-open > .chat-msg { display: block; animation: chatRowIn 180ms ease-out; }
++.chat-friendly-group.is-open > .chat-msg { display: block; animation: chatRowIn var(--ov-dur-2, 180ms) var(--ov-ease-out, ease-out); }
.chat-friendly-toggle {
display: inline-flex; align-items: center; gap: 7px; max-width: 100%;
padding: 3px 10px; border-radius: 999px; cursor: pointer; font: inherit; font-size: 0.74rem;
color: var(--text-muted); background: color-mix(in srgb, var(--text-primary, #fff) 5%, transparent);
border: 1px dashed color-mix(in srgb, var(--text-primary, #fff) 16%, transparent);
-- transition: color 0.15s, border-color 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s);
}
.chat-friendly-toggle:hover { color: var(--text-secondary); border-color: color-mix(in srgb, var(--accent, #8b5cf6) 50%, transparent); }
.chat-friendly-show { color: var(--accent-light, #a78bfa); font-weight: 600; }
@@@ -488,7 +488,7 @@
padding: 0 2px;
cursor: pointer;
user-select: none;
-- transition: background 0.15s;
++ transition: background var(--ov-dur-2, 0.15s);
}
.chat-spoiler:hover { background: var(--text-secondary, #9ca3af); }
.chat-spoiler:not(.revealed) * { visibility: hidden; }
ddiff --git a/public/css/context-cards.css b/public/css/context-cards.css
iindex 1c450509..d854232d 100644
---- a/public/css/context-cards.css
++++ b/public/css/context-cards.css
@@@ -6,14 +6,14 @@
.cc-live { font-size: 0.66rem; color: #fff; background: #e0245e; border-radius: 999px; padding: 2px 8px; letter-spacing: 0.06em; }
.cc-main { display: grid; grid-template-columns: minmax(200px, 300px) 1fr; gap: 16px; align-items: start; }
.cc-thumb { position: relative; display: block; aspect-ratio: 16 / 9; border-radius: 12px; overflow: hidden; background: var(--bg-tertiary); border: 1px solid var(--border); }
--.cc-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.35s ease; }
++.cc-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease); }
.cc-thumb:hover img { transform: scale(1.04); }
.cc-thumb-dur { position: absolute; right: 8px; top: 8px; font-size: 0.68rem; font-weight: 800; padding: 2px 7px; border-radius: 6px; background: rgba(0,0,0,0.72); color: #fff; }
--.cc-thumb-play { position: absolute; inset: 0; display: grid; place-items: center; color: #fff; font-size: 1.4rem; opacity: 0; transition: opacity 0.2s; background: rgba(0,0,0,0.25); }
++.cc-thumb-play { position: absolute; inset: 0; display: grid; place-items: center; color: #fff; font-size: 1.4rem; opacity: 0; transition: opacity var(--ov-dur-2, 0.2s); background: rgba(0,0,0,0.25); }
.cc-thumb:hover .cc-thumb-play { opacity: 1; }
.cc-thumb-play i { width: 46px; height: 46px; border-radius: 50%; display: grid; place-items: center; background: var(--accent); box-shadow: 0 8px 24px rgba(0,0,0,0.5); padding-left: 3px; }
.cc-timeline { position: absolute; left: 0; right: 0; bottom: 0; height: 6px; background: rgba(255,255,255,0.18); }
--.cc-timeline i { position: absolute; top: -2px; bottom: -2px; min-width: 6px; border-radius: 3px; background: var(--accent); box-shadow: 0 0 10px var(--accent), 0 0 0 2px rgba(0,0,0,0.5); animation: ccMark 1.8s ease-in-out infinite; }
++.cc-timeline i { position: absolute; top: -2px; bottom: -2px; min-width: 6px; border-radius: 3px; background: var(--accent); box-shadow: 0 0 10px var(--accent), 0 0 0 2px rgba(0,0,0,0.5); animation: ccMark 1.8s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes ccMark { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.5); } }
.cc-body { min-width: 0; display: grid; gap: 8px; align-content: start; }
.cc-title-row { display: flex; align-items: center; gap: 10px; }
@@@ -44,7 +44,7 @@
.cc-more-head { font-size: 0.82rem; font-weight: 800; margin-bottom: 8px; }
.cc-more-head i { color: var(--accent); margin-right: 4px; }
.cc-clips { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
--.cc-clip { position: relative; display: block; aspect-ratio: 16 / 10; border-radius: 10px; overflow: hidden; background: var(--bg-tertiary); border: 1px solid var(--border); text-decoration: none; color: #fff; transition: transform 0.15s, border-color 0.15s; }
++.cc-clip { position: relative; display: block; aspect-ratio: 16 / 10; border-radius: 10px; overflow: hidden; background: var(--bg-tertiary); border: 1px solid var(--border); text-decoration: none; color: #fff; transition: transform var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s); }
.cc-clip:hover { transform: translateY(-2px); border-color: var(--accent); }
.cc-clip img, .cc-clip-ph { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: grid; place-items: center; color: var(--text-muted); font-size: 1.4rem; }
.cc-clip-dur { position: absolute; right: 6px; top: 6px; font-size: 0.64rem; font-weight: 800; padding: 2px 6px; border-radius: 5px; background: rgba(0,0,0,0.72); }
@@@ -77,7 +77,7 @@
.dsc-sec h4 i { color: var(--accent); }
.dsc-sec h4 .muted { text-transform: none; letter-spacing: 0; font-weight: 600; }
.dsc-live { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 10px; }
--.dsc-live-card { display: block; border-radius: 12px; overflow: hidden; background: var(--bg-secondary); border: 1px solid color-mix(in srgb, #e0245e 40%, var(--border)); text-decoration: none; color: var(--text-primary); transition: transform 0.15s, border-color 0.15s; }
++.dsc-live-card { display: block; border-radius: 12px; overflow: hidden; background: var(--bg-secondary); border: 1px solid color-mix(in srgb, #e0245e 40%, var(--border)); text-decoration: none; color: var(--text-primary); transition: transform var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s); }
.dsc-live-card:hover { transform: translateY(-2px); border-color: #e0245e; }
.dsc-live-thumb { position: relative; display: block; aspect-ratio: 16 / 9; background: var(--bg-tertiary); }
.dsc-live-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
@@@ -110,7 +110,7 @@
.dsc-person .cc-avatar { grid-row: 1 / 3; }
.dsc-person b { font-size: 0.84rem; } .dsc-person small { font-size: 0.68rem; color: var(--text-muted); }
.dsc-person em { grid-column: 2; font-style: normal; font-size: 0.62rem; color: var(--accent); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
--.dsc-live-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #e0245e; margin-right: 3px; animation: ccMark 1.4s ease-in-out infinite; }
++.dsc-live-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #e0245e; margin-right: 3px; animation: ccMark 1.4s var(--ov-ease-in-out, ease-in-out) infinite; }
/* No live streams and no clips to show: let the side column use the full width, two-up. */
@media (max-width: 980px) { .dsc { grid-template-columns: 1fr; } .dsc--sideonly .dsc-side { grid-template-columns: 1fr; } }
.dsc-people { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
@@@ -131,7 +131,7 @@
display: grid; grid-template-columns: auto minmax(0, max-content) 1fr auto auto; align-items: center; gap: 12px;
width: 100%; padding: 6px 2px 10px; cursor: pointer; text-align: left; font: inherit; color: var(--text-primary);
background: none; border: none; border-bottom: 1px solid color-mix(in srgb, var(--accent) 22%, var(--border));
-- transition: border-color .22s;
++ transition: border-color var(--ov-dur-2, .22s);
}
/* A section heading of the offline page (not a card): the rule under it runs the full width. */
/* Explicit columns: auto-placement put the count into the 1fr rule column (a full-width blue bar) and
@@@ -160,13 +160,13 @@
}
/* A single slow pulse ring on the count, only while the board is closed and only on screens that
want motion — once it is open the count has done its job. */
--.dsc-wrap:not(.is-open) .dsc-toggle-count { animation: dscPulse 2.8s ease-out infinite; }
++.dsc-wrap:not(.is-open) .dsc-toggle-count { animation: dscPulse 2.8s var(--ov-ease-out, ease-out) infinite; }
@keyframes dscPulse {
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
70% { box-shadow: 0 0 0 9px transparent; }
100% { box-shadow: 0 0 0 0 transparent; }
}
--.dsc-toggle-chev { flex: none; color: var(--text-muted); transition: transform .3s cubic-bezier(.22,1,.36,1), color .2s; }
++.dsc-toggle-chev { flex: none; color: var(--text-muted); transition: transform var(--ov-dur-3, .3s) var(--ov-ease-out, cubic-bezier(.22,1,.36,1)), color var(--ov-dur-2, .2s); }
.dsc-wrap.is-open .dsc-toggle-chev { transform: rotate(180deg); color: var(--accent); }
.dsc-toggle:hover .dsc-toggle-chev { color: var(--accent); }
/* The board no longer carries its own top margin — the wrapper owns the spacing now. */
ddiff --git a/public/css/cosmetics.css b/public/css/cosmetics.css
iindex cbbd9559..f05036e8 100644
---- a/public/css/cosmetics.css
++++ b/public/css/cosmetics.css
@@@ -25,7 +25,7 @@
.name-fx-fire {
color: #ff6600 !important;
text-shadow: 0 0 6px #ff4400, 0 0 12px #ff220088, 0 0 20px #cc110044;
-- animation: nameFxFire 1.5s ease-in-out infinite alternate;
++ animation: nameFxFire 1.5s var(--ov-ease-in-out, ease-in-out) infinite alternate;
}
@keyframes nameFxFire {
0% { text-shadow: 0 0 6px #ff4400, 0 0 12px #ff220088; color: #ff6600; }
@@@ -36,7 +36,7 @@
.name-fx-ice {
color: #88ddff !important;
text-shadow: 0 0 6px #44bbff88, 0 0 12px #2288cc44;
-- animation: nameFxIce 2s ease-in-out infinite alternate;
++ animation: nameFxIce 2s var(--ov-ease-in-out, ease-in-out) infinite alternate;
}
@keyframes nameFxIce {
0% { text-shadow: 0 0 4px #44bbff88; color: #88ddff; }
@@@ -50,7 +50,7 @@
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
-- animation: nameFxGolden 2s ease-in-out infinite;
++ animation: nameFxGolden 2s var(--ov-ease-in-out, ease-in-out) infinite;
filter: drop-shadow(0 0 4px #ffd70066);
}
@keyframes nameFxGolden {
@@@ -62,7 +62,7 @@
.name-fx-neon {
color: #00ff88 !important;
text-shadow: 0 0 8px #00ff88, 0 0 16px #00ff8888, 0 0 24px #00cc6644;
-- animation: nameFxNeon 1.2s ease-in-out infinite alternate;
++ animation: nameFxNeon 1.2s var(--ov-ease-in-out, ease-in-out) infinite alternate;
}
@keyframes nameFxNeon {
0% { text-shadow: 0 0 4px #00ff88, 0 0 8px #00ff8866; }
@@@ -88,7 +88,7 @@
.name-fx-void {
color: #9900ff !important;
text-shadow: 0 0 8px #6600cc, 0 0 16px #33006688, 0 0 24px #11003344;
-- animation: nameFxVoid 2s ease-in-out infinite;
++ animation: nameFxVoid 2s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes nameFxVoid {
0%, 100% { text-shadow: 0 0 8px #6600cc, 0 0 16px #33006688; transform: scale(1); }
@@@ -102,7 +102,7 @@
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
-- animation: nameFxToxic 1.8s ease-in-out infinite alternate;
++ animation: nameFxToxic 1.8s var(--ov-ease-in-out, ease-in-out) infinite alternate;
}
@keyframes nameFxToxic {
0% { background-position: 0% 50%; filter: brightness(1); }
@@@ -116,7 +116,7 @@
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
-- animation: nameFxBlood 2s ease-in-out infinite;
++ animation: nameFxBlood 2s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes nameFxBlood {
0%, 100% { background-position: 50% 0%; }
@@@ -126,7 +126,7 @@
/* Shadow — dark mist */
.name-fx-shadow {
color: #aa88cc !important;
-- animation: nameFxShadow 2s ease-in-out infinite;
++ animation: nameFxShadow 2s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes nameFxShadow {
0%, 100% { text-shadow: 0 0 6px rgba(0,0,0,0.8), 0 0 12px rgba(80,0,120,0.6), 2px 2px 4px rgba(0,0,0,0.9); }
@@@ -136,7 +136,7 @@
/* Glitch — jittery digital */
.name-fx-glitch {
color: #00ff88 !important;
-- animation: nameFxGlitch 0.15s infinite;
++ animation: nameFxGlitch var(--ov-dur-2, 0.15s) infinite;
text-shadow: 2px 0 #ff0044, -2px 0 #00ccff;
}
@keyframes nameFxGlitch {
@@@ -155,7 +155,7 @@
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
-- animation: nameFxHoloFlicker 0.12s infinite, nameFxHoloShift 3s linear infinite;
++ animation: nameFxHoloFlicker var(--ov-dur-1, 0.12s) infinite, nameFxHoloShift 3s linear infinite;
}
@keyframes nameFxHoloFlicker { 0%,45%,55%,100% { opacity: 1; } 50% { opacity: 0.6; } }
@keyframes nameFxHoloShift { 0% { background-position: 0% 50%; } 100% { background-position: 300% 50%; } }
@@@ -167,7 +167,7 @@
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
-- animation: nameFxDivine 2s ease-in-out infinite;
++ animation: nameFxDivine 2s var(--ov-ease-in-out, ease-in-out) infinite;
filter: drop-shadow(0 0 8px #ffe06688);
}
@keyframes nameFxDivine {
@@@ -190,7 +190,7 @@
letter-spacing: .02em;
white-space: nowrap;
transform-origin: 50% 60%;
-- animation: nameFxVegasShine 2.6s linear infinite, nameFxVegasLights 1.4s steps(1, end) infinite, nameFxVegasJackpot 3.2s ease-out infinite;
++ animation: nameFxVegasShine 2.6s linear infinite, nameFxVegasLights 1.4s steps(1, end) infinite, nameFxVegasJackpot 3.2s var(--ov-ease-out, ease-out) infinite;
}
.name-fx-vegas::before,
.name-fx-vegas::after {
@@@ -261,7 +261,7 @@
letter-spacing: .03em;
white-space: nowrap;
transform-origin: 50% 70%;
-- animation: nameFxTwofatLights 3s linear infinite, nameFxTwofatThump 1s ease-out infinite;
++ animation: nameFxTwofatLights 3s linear infinite, nameFxTwofatThump 1s var(--ov-ease-out, ease-out) infinite;
}
.name-fx-twofat::before {
content: '▂▅▇▃▆';
@@@ -273,7 +273,7 @@
font-size: .6em;
letter-spacing: .12em;
vertical-align: .1em;
-- animation: nameFxTwofatEq .5s steps(1, end) infinite;
++ animation: nameFxTwofatEq var(--ov-dur-4, .5s) steps(1, end) infinite;
}
.name-fx-twofat::after {
content: '🔊';
@@@ -281,7 +281,7 @@
margin-left: .3em;
-webkit-text-fill-color: initial;
font-size: .85em;
-- animation: nameFxTwofatSpeaker 1s ease-out infinite;
++ animation: nameFxTwofatSpeaker 1s var(--ov-ease-out, ease-out) infinite;
}
@keyframes nameFxTwofatLights {
0% { background-position: 0% 50%; }
@@@ -327,7 +327,7 @@
pointer-events: none;
font-size: 10px;
opacity: 0;
-- animation: chatParticleFloat 1.8s ease-out forwards;
++ animation: chatParticleFloat 1.8s var(--ov-ease-out, ease-out) forwards;
}
@keyframes chatParticleFloat {
@@@ -369,13 +369,13 @@
vertical-align: middle;
}
.chat-hat.hat-float {
-- animation: chatHatFloat 2s ease-in-out infinite;
++ animation: chatHatFloat 2s var(--ov-ease-in-out, ease-in-out) infinite;
}
.chat-hat.hat-pulse {
-- animation: chatHatPulse 1.5s ease-in-out infinite;
++ animation: chatHatPulse 1.5s var(--ov-ease-in-out, ease-in-out) infinite;
}
.chat-hat.hat-warp {
-- animation: chatHatWarp 2s ease-in-out infinite;
++ animation: chatHatWarp 2s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes chatHatFloat {
0%, 100% { transform: translateY(0); }
@@@ -435,7 +435,7 @@
color: #c8962e; font-size: 1.3rem;
cursor: pointer; padding: 4px 8px;
border-radius: 6px;
-- transition: color 0.15s, background 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
}
.cosmetics-close:hover {
color: #ffd700; background: #c8962e22;
@@@ -454,7 +454,7 @@
background: none; border: none; border-bottom: 2px solid transparent;
color: #c8962e88; font-size: 0.85rem; font-weight: 600;
cursor: pointer; text-align: center;
-- transition: color 0.15s, border-color 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s);
white-space: nowrap;
}
.cosmetics-tab:hover { color: #ffd700; }
@@@ -489,7 +489,7 @@
padding: 12px;
text-align: center;
cursor: pointer;
-- transition: border-color 0.2s, transform 0.15s, box-shadow 0.2s;
++ transition: border-color var(--ov-dur-2, 0.2s), transform var(--ov-dur-2, 0.15s), box-shadow var(--ov-dur-2, 0.2s);
}
.cosmetic-card:hover {
border-color: #ffd700;
@@@ -550,7 +550,7 @@
font-weight: 600;
border: 1px solid;
cursor: pointer;
-- transition: background 0.15s, color 0.15s;
++ transition: background var(--ov-dur-2, 0.15s), color var(--ov-dur-2, 0.15s);
}
.cosmetic-btn-equip {
background: #ffd70022;
ddiff --git a/public/css/features/broadcast.css b/public/css/features/broadcast.css
iindex fdd873aa..9e42bc41 100644
---- a/public/css/features/broadcast.css
++++ b/public/css/features/broadcast.css
@@@ -70,7 +70,7 @@
padding: 6px 12px;
cursor: pointer;
font-size: 0.82rem;
-- transition: background 0.15s;
++ transition: background var(--ov-dur-2, 0.15s);
}
.bc-pip-btn:hover { background: var(--accent-strong, var(--accent, #8b5cf6)); color: var(--on-accent-strong, var(--on-accent, #fff)); }
.bc-media-pip-queue {
@@@ -203,7 +203,7 @@
border-radius: 4px;
font-size: 0.75rem;
flex-shrink: 0;
-- transition: color 0.15s, background 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
}
.bc-ws-item:hover .bc-ws-item-del { display: flex; align-items: center; }
.bc-ws-item-del:hover { color: var(--danger-text, var(--danger)); background: color-mix(in srgb, var(--danger) 10%, transparent); }
@@@ -493,7 +493,7 @@
font-family: inherit; font-size: 0.8rem; color: #fff; line-height: 1.2;
background: rgba(12, 16, 26, 0.82); border: 1px solid rgba(255, 255, 255, 0.12);
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
-- animation: bcStatsIn 0.16s ease-out;
++ animation: bcStatsIn var(--ov-dur-2, 0.16s) var(--ov-ease-out, ease-out);
}
#bc-stats-overlay[style*="none"] { animation: none; }
@keyframes bcStatsIn { from { opacity: 0; transform: translateY(-4px); } }
@@@ -575,7 +575,7 @@
border-radius: var(--radius, 8px);
color: var(--text-secondary);
cursor: pointer;
-- transition: all 0.2s ease;
++ transition: all var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease);
font-size: 0.78rem;
text-align: center;
min-height: 72px;
@@@ -618,7 +618,7 @@
color: var(--text-primary);
font-size: 0.85rem;
cursor: pointer;
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
display: flex;
align-items: center;
gap: 6px;
@@@ -689,7 +689,7 @@
align-items: center;
justify-content: center;
cursor: pointer;
-- transition: all 0.18s ease;
++ transition: all var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease);
}
.bc-camera-sheet-close:hover {
@@@ -930,7 +930,7 @@
color: var(--text-primary);
font-size: 0.78rem;
cursor: pointer;
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
display: inline-flex;
align-items: center;
gap: 4px;
@@@ -1001,7 +1001,7 @@
bottom: 2px;
background: var(--text-muted);
border-radius: 50%;
-- transition: 0.2s;
++ transition: var(--ov-dur-2, 0.2s);
}
.bc-toggle input:checked + .bc-toggle-slider::before {
@@@ -1152,7 +1152,7 @@
font-size: 0.82rem;
white-space: nowrap;
cursor: pointer;
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
font-family: var(--font);
}
@@@ -1209,7 +1209,7 @@
color: var(--text-muted);
font-size: 0.9rem;
cursor: pointer;
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
margin-left: 4px;
}
@@@ -1225,7 +1225,7 @@
font-size: 0.7rem;
padding: 2px 4px;
border-radius: 4px;
-- transition: opacity 0.15s, background 0.15s;
++ transition: opacity var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
color: inherit;
}
@@@ -1286,7 +1286,7 @@
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
-- transition: border-color 0.15s;
++ transition: border-color var(--ov-dur-2, 0.15s);
}
.bc-stream-item:hover {
@@@ -1462,7 +1462,7 @@
color: var(--text-secondary);
font-size: 0.72rem;
cursor: pointer;
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
vertical-align: middle;
}
@@@ -1615,7 +1615,7 @@
display: flex; align-items: center; gap: 12px;
padding: 14px 18px; border-radius: var(--radius);
margin: 16px 0;
-- transition: all 0.4s ease;
++ transition: all var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease);
}
.bc-rtmp-status.waiting {
background: rgba(241, 196, 15, 0.08);
@@@ -1653,7 +1653,7 @@
font-size: 0.85rem;
font-weight: 600;
margin: 8px 16px;
-- animation: bc-alert-pulse 2s ease-in-out infinite;
++ animation: bc-alert-pulse 2s var(--ov-ease-in-out, ease-in-out) infinite;
}
.bc-rtmp-preview img {
width: 100%;
@@@ -1960,7 +1960,7 @@
border-radius: 6px;
padding: 10px 12px;
background: var(--bg-tertiary);
-- transition: opacity 0.15s;
++ transition: opacity var(--ov-dur-2, 0.15s);
}
.bc-ws-restream-card.disabled {
opacity: 0.5;
@@@ -2038,7 +2038,7 @@
border: none;
border-bottom: 2px solid transparent;
cursor: pointer;
-- transition: color 0.15s, border-color 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s);
display: flex;
align-items: center;
gap: 6px;
@@@ -2062,7 +2062,7 @@
border: none;
border-bottom: 2px solid transparent;
cursor: pointer;
-- transition: color 0.15s, border-color 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s);
display: flex;
align-items: center;
justify-content: center;
@@@ -2131,7 +2131,7 @@
border-radius: 50%;
background: var(--accent);
flex-shrink: 0;
-- animation: ws-autodetect-pulse 2s ease infinite;
++ animation: ws-autodetect-pulse 2s var(--ov-ease-out, ease) infinite;
}
.bc-ws-popout-chat-btn {
display: inline-flex;
@@@ -2278,7 +2278,7 @@
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
-- transition: color .15s, border-color .15s;
++ transition: color var(--ov-dur-2, .15s), border-color var(--ov-dur-2, .15s);
}
.bc-ws-cli-panel {
display: none;
@@@ -2361,7 +2361,7 @@
.bc-perm-mock-btns .primary {
background: #3b82f6; color: #fff; font-weight: 600;
box-shadow: 0 0 0 3px rgba(59,130,246,.28);
-- animation: bcPermPulse 1.7s ease-in-out infinite;
++ animation: bcPermPulse 1.7s var(--ov-ease-in-out, ease-in-out) infinite;
}
.bc-perm-note {
@@@ -2449,13 +2449,13 @@
/* NSFW: a themed switch with what it does, instead of a bare checkbox floating beside a select. */
.bc-switch { display: flex; align-items: flex-start; gap: 12px; margin: 2px 0 16px; padding: 10px 12px; cursor: pointer;
border: 1px solid var(--border); border-radius: 12px; background: color-mix(in srgb, var(--bg-input, var(--bg)) 70%, transparent);
-- transition: border-color .18s, background-color .18s; }
++ transition: border-color var(--ov-dur-2, .18s), background-color var(--ov-dur-2, .18s); }
.bc-switch:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
.bc-switch input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.bc-switch-track { flex: none; position: relative; width: 38px; height: 22px; margin-top: 1px; border-radius: 999px;
-- background: color-mix(in srgb, var(--text-muted) 35%, transparent); border: 1px solid var(--border); transition: background-color .2s, border-color .2s; }
++ background: color-mix(in srgb, var(--text-muted) 35%, transparent); border: 1px solid var(--border); transition: background-color var(--ov-dur-2, .2s), border-color var(--ov-dur-2, .2s); }
.bc-switch-thumb { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--text-primary);
-- box-shadow: 0 1px 3px rgba(0,0,0,.35); transition: transform .22s cubic-bezier(.22,1,.36,1), background-color .2s; }
++ box-shadow: 0 1px 3px rgba(0,0,0,.35); transition: transform var(--ov-dur-2, .22s) var(--ov-ease-out, cubic-bezier(.22,1,.36,1)), background-color var(--ov-dur-2, .2s); }
.bc-switch input:checked + .bc-switch-track { background: var(--danger, #ef4444); border-color: var(--danger, #ef4444); }
.bc-switch input:checked + .bc-switch-track .bc-switch-thumb { transform: translateX(16px); background: #fff; }
.bc-switch input:focus-visible + .bc-switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }
@@@ -2468,12 +2468,12 @@
/* Video quality: reads as a row that opens (chevron, current values), not a static label. */
.bc-ws-quality { padding: 0; border-radius: 12px; overflow: hidden; }
.bc-ws-quality > summary { display: flex; align-items: center; gap: 8px; padding: 11px 12px; cursor: pointer; list-style: none; user-select: none;
-- font-size: 0.86rem; font-weight: 650; color: var(--text-primary); transition: background-color .15s; }
++ font-size: 0.86rem; font-weight: 650; color: var(--text-primary); transition: background-color var(--ov-dur-2, .15s); }
.bc-ws-quality > summary::-webkit-details-marker { display: none; }
.bc-ws-quality > summary:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.bc-ws-quality > summary > i:first-child { color: var(--accent); margin: 0; }
.bc-sum-val { margin-left: auto; font-size: 0.76rem; font-weight: 500; color: var(--text-muted); white-space: nowrap; }
--.bc-sum-chev { color: var(--text-muted); font-size: 0.75rem; transition: transform .22s ease; }
++.bc-sum-chev { color: var(--text-muted); font-size: 0.75rem; transition: transform var(--ov-dur-2, .22s) var(--ov-ease-out, ease); }
.bc-ws-quality[open] .bc-sum-chev { transform: rotate(180deg); color: var(--accent); }
.bc-ws-quality[open] > summary { border-bottom: 1px solid var(--border); }
.bc-ws-quality .bc-ws-quality-inner { margin: 0; padding: 12px; }
@@@ -2594,12 +2594,12 @@ body.bc-studio-on .toast-container { z-index: 1500; }
.bcs-live {
display: inline-flex; align-items: center; gap: 7px; height: 30px; padding: 0 12px 0 11px; border-radius: 999px;
background: #e11d48; font-size: 0.86rem; font-weight: 800; font-variant-numeric: tabular-nums; letter-spacing: 0.01em;
-- box-shadow: 0 2px 10px rgba(225, 29, 72, 0.35); transition: background 0.2s;
++ box-shadow: 0 2px 10px rgba(225, 29, 72, 0.35); transition: background var(--ov-dur-2, 0.2s);
}
/* Starting and reconnecting: amber, the dot still pulsing. */
.bcs-live[data-phase="starting"], .bcs-live[data-phase="recovering"] { background: #b45309; box-shadow: none; }
.bcs-live-t { min-width: 2.6em; }
--.bcs-live-dot { width: 7px; height: 7px; border-radius: 50%; background: #fff; animation: bcs-pulse 1.6s ease-in-out infinite; }
++.bcs-live-dot { width: 7px; height: 7px; border-radius: 50%; background: #fff; animation: bcs-pulse 1.6s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes bcs-pulse { 50% { opacity: 0.35; } }
.bcs-pill {
display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; border-radius: 999px;
@@@ -2624,7 +2624,7 @@ body.bc-studio-on .toast-container { z-index: 1500; }
}
.bcs-viewers[data-rs="bad"]::after { background: #ef4444; }
/* The studio fades in over the page instead of appearing a frame after it. */
--.bcs { animation: bcs-in 0.22s ease-out; }
++.bcs { animation: bcs-in var(--ov-dur-2, 0.22s) var(--ov-ease-out, ease-out); }
@keyframes bcs-in { from { opacity: 0; } }
/* Starting or reconnecting the camera: said on the picture, not a frozen last frame. */
.bcs-state {
@@@ -2635,7 +2635,7 @@ body.bc-studio-on .toast-container { z-index: 1500; }
.bcs-state[hidden] { display: none; }
.bcs-state b { font-size: 0.98rem; }
.bcs-state small { font-size: 0.78rem; color: rgba(255, 255, 255, 0.75); }
--.bcs-state-spin { width: 26px; height: 26px; border-radius: 50%; border: 3px solid #fff; animation: bcs-pulse 1.4s ease-out infinite; } /* a ring that pulses out: nothing spins */
++.bcs-state-spin { width: 26px; height: 26px; border-radius: 50%; border: 3px solid #fff; animation: bcs-pulse 1.4s var(--ov-ease-out, ease-out) infinite; } /* a ring that pulses out: nothing spins */
@keyframes bcs-pulse { from { box-shadow: 0 0 0 0 rgba(255,255,255,.5); } to { box-shadow: 0 0 0 10px rgba(255,255,255,0); } }
/* Flip with more than two cameras: a small "+" (hold it to choose). */
.bcs-btn[data-act="flip"] { position: relative; }
@@@ -2684,7 +2684,7 @@ body.bc-studio-on #bc-chat-messages .chat-conn-status { display: block !importan
.bcs-btn[hidden] { display: none; }
.bcs-ico {
display: grid; place-items: center; width: 46px; height: 46px; border-radius: 50%; font-size: 1.12rem;
-- background: rgba(0,0,0,0.5); border: 1px solid rgba(255,255,255,0.18); transition: background 0.15s, transform 0.1s;
++ background: rgba(0,0,0,0.5); border: 1px solid rgba(255,255,255,0.18); transition: background var(--ov-dur-2, 0.15s), transform var(--ov-dur-1, 0.1s);
}
.bcs-btn:active .bcs-ico { transform: scale(0.94); }
.bcs-btn.is-off .bcs-ico { background: #e11d48; border-color: transparent; }
@@@ -2709,7 +2709,7 @@ body.bc-studio-on #bc-chat-messages .chat-conn-status { display: block !importan
.bcs-sheet-wrap {
position: fixed; inset: 0; z-index: 1300; display: flex; align-items: flex-end; justify-content: center;
-- background: rgba(0,0,0,0.5); animation: bcs-fade 0.15s ease-out;
++ background: rgba(0,0,0,0.5); animation: bcs-fade var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease-out);
}
.bcs-sheet-wrap.bcs-center { align-items: center; padding: 16px; }
@keyframes bcs-fade { from { opacity: 0; } }
@@@ -2717,7 +2717,7 @@ body.bc-studio-on #bc-chat-messages .chat-conn-status { display: block !importan
width: 100%; max-width: 560px; max-height: 84vh; max-height: 84dvh; overflow-y: auto; overscroll-behavior: contain;
background: var(--bg-secondary, #15151c); color: var(--text-primary, #f4f4f5); border-radius: 22px 22px 0 0;
padding: 6px 16px calc(18px + env(safe-area-inset-bottom, 0px)); box-shadow: 0 -10px 40px rgba(0,0,0,0.35);
-- animation: bcs-up 0.2s ease-out;
++ animation: bcs-up var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease-out);
}
@keyframes bcs-up { from { transform: translateY(24px); opacity: 0.6; } }
.bcs-grab { width: 40px; height: 4px; border-radius: 4px; background: var(--border, rgba(255,255,255,0.2)); margin: 4px auto 10px; }
@@@ -2770,7 +2770,7 @@ body.bc-studio-on #bc-chat-messages .chat-conn-status { display: block !importan
position: relative; flex: none; width: 52px; height: 32px; padding: 0; border-radius: 999px; cursor: pointer;
border: 1px solid var(--border, rgba(255,255,255,0.18)); background: var(--surface-raised, rgba(255,255,255,0.1));
}
--.bcs-switch-k { position: absolute; top: 3px; left: 3px; width: 24px; height: 24px; border-radius: 50%; background: #fff; transition: transform 0.15s; }
++.bcs-switch-k { position: absolute; top: 3px; left: 3px; width: 24px; height: 24px; border-radius: 50%; background: #fff; transition: transform var(--ov-dur-2, 0.15s); }
.bcs-switch[aria-checked="true"] { background: #16a34a; border-color: transparent; }
.bcs-switch[aria-checked="true"] .bcs-switch-k { transform: translateX(20px); }
@media (prefers-reduced-motion: reduce) { .bcs-switch-k { transition: none; } }
@@@ -2797,7 +2797,7 @@ body.bc-studio-on #bc-chat-messages .chat-conn-status { display: block !importan
.bc-live-pill[hidden] { display: none; }
.bc-live-pill b { font-weight: 800; }
.bc-live-pill span:last-child { opacity: 0.9; }
--.bc-live-pill-dot { width: 8px; height: 8px; border-radius: 50%; background: #fff; animation: bcLivePillPulse 1.6s ease-in-out infinite; }
++.bc-live-pill-dot { width: 8px; height: 8px; border-radius: 50%; background: #fff; animation: bcLivePillPulse 1.6s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes bcLivePillPulse { 50% { opacity: 0.35; } }
.bc-live-pill:focus-visible { outline: 3px solid #93c5fd; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .bc-live-pill-dot { animation: none; } }
@@@ -2849,7 +2849,7 @@ body.bc-golive-dock-on #page-broadcast { padding-bottom: calc(104px + var(--ovna
display: grid; gap: 8px; padding: 14px 14px 16px; border-radius: 16px;
background: var(--bg-secondary, #15151c); color: var(--text-primary, #f4f4f5);
border: 1px solid var(--border, rgba(255,255,255,0.12)); box-shadow: 0 16px 48px rgba(0,0,0,0.45);
-- animation: bc-clip-name-in 0.18s ease-out;
++ animation: bc-clip-name-in var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease-out);
}
@keyframes bc-clip-name-in { from { transform: translate(-50%, -12px); opacity: 0; } }
.bc-clip-name-head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
@@@ -2901,7 +2901,7 @@ body.bc-golive-dock-on #page-broadcast { padding-bottom: calc(104px + var(--ovna
.bc-setup-chev { flex: 0 0 auto; font-size: 0.72rem; }
/* The select lies over the whole row, unseen: a tap anywhere on it opens the list. 16px keeps iOS from zooming. */
.bc-setup-native { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; font-size: 16px; border: 0; -webkit-appearance: none; appearance: none; }
--.bc-setup-row:has(> .bc-setup-pick > .bc-setup-native) { transition: background-color 0.15s; }
++.bc-setup-row:has(> .bc-setup-pick > .bc-setup-native) { transition: background-color var(--ov-dur-2, 0.15s); }
.bc-setup-row:has(> .bc-setup-pick > .bc-setup-native):hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.bc-setup-row:has(.bc-setup-native:focus-visible) { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The lens row belongs to the camera's: no line between them, its label under the camera's name. */
@@@ -2918,7 +2918,7 @@ body.bc-golive-dock-on #page-broadcast { padding-bottom: calc(104px + var(--ovna
display: inline-flex; align-items: center; justify-content: center; gap: 6px; flex: 0 0 auto;
min-height: 36px; padding: 0 14px; border: 0; border-radius: 999px; background: transparent;
color: var(--text-secondary); font-size: 0.82rem; font-weight: 700; white-space: nowrap;
-- transition: background-color 0.15s, color 0.15s;
++ transition: background-color var(--ov-dur-2, 0.15s), color var(--ov-dur-2, 0.15s);
}
.bc-cam-side i { font-size: 0.82rem; }
.bc-cam-lens { min-height: 32px; padding: 0 12px; font-size: 0.78rem; }
@@@ -3006,7 +3006,7 @@ body.bc-golive-dock-on #page-broadcast { padding-bottom: calc(104px + var(--ovna
.form-group > .bc-file-pick {
display: flex; align-items: center; gap: 12px; margin: 0; padding: 10px 12px; cursor: pointer;
border: 1px dashed color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 12px;
-- background: var(--bg-card); transition: background-color 0.15s;
++ background: var(--bg-card); transition: background-color var(--ov-dur-2, 0.15s);
}
.form-group > .bc-file-pick:hover { background: color-mix(in srgb, var(--accent) 7%, var(--bg-card)); }
.form-group > .bc-file-pick:has(+ input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
@@@ -3036,7 +3036,7 @@ body.bc-golive-dock-on #page-broadcast { padding-bottom: calc(104px + var(--ovna
display: inline-flex; align-items: center; gap: 7px; height: 28px; padding: 0 12px; border-radius: 999px;
background: #e11d48; color: #fff; font-size: 0.8rem; font-weight: 800; font-variant-numeric: tabular-nums;
}
--.bc-live-card-dot { width: 7px; height: 7px; border-radius: 50%; background: #fff; animation: bcs-pulse 1.6s ease-in-out infinite; }
++.bc-live-card-dot { width: 7px; height: 7px; border-radius: 50%; background: #fff; animation: bcs-pulse 1.6s var(--ov-ease-in-out, ease-in-out) infinite; }
.bc-live-card-watch { font-size: 0.86rem; font-weight: 700; color: var(--text-secondary); }
.bc-live-card-title { margin: 12px 0 2px; font-size: 1.2rem; line-height: 1.25; overflow-wrap: anywhere; }
.bc-live-card-sub { margin: 0; font-size: 0.84rem; color: var(--text-muted); }
@@@ -3065,7 +3065,7 @@ body.bc-golive-dock-on #page-broadcast { padding-bottom: calc(104px + var(--ovna
}
.bc-last-replay:hover, .bc-last-replay:focus-visible { background: color-mix(in srgb, var(--accent) 24%, transparent); outline: none; }
/* The phone bar fades up instead of appearing (and it no longer shows before the page knows the streams). */
--.bcs-golive:not([hidden]) { animation: bcs-dock-in 0.2s ease-out; }
++.bcs-golive:not([hidden]) { animation: bcs-dock-in var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease-out); }
.bcs-golive-t { margin-left: 8px; }
@keyframes bcs-dock-in { from { opacity: 0; transform: translateY(8px); } }
@media (prefers-reduced-motion: reduce) { .bcs-golive:not([hidden]), .bc-live-card-dot { animation: none; } }
@@@ -3109,14 +3109,14 @@ body.bc-golive-dock-on #page-broadcast { padding-bottom: calc(104px + var(--ovna
.bcs-livecard { position: fixed; z-index: 1600; top: calc(64px + env(safe-area-inset-top, 0px)); left: 50%; width: min(420px, calc(100vw - 24px)); box-sizing: border-box; padding: 14px 14px 14px 16px; border-radius: 18px;
background: color-mix(in srgb, var(--bg-secondary, #0f1626) 92%, transparent); -webkit-backdrop-filter: blur(16px) saturate(1.4); backdrop-filter: blur(16px) saturate(1.4);
border: 1px solid color-mix(in srgb, var(--live-red, #ef4444) 42%, var(--border, #1f2d47)); box-shadow: 0 24px 60px rgba(0,0,0,.55); color: var(--text-primary); overflow: hidden;
-- transform: translateX(-50%); animation: bcs-gl-in .45s cubic-bezier(.2,1.3,.3,1) both; }
--.bcs-livecard.is-out { animation: bcs-gl-out .25s ease-in forwards; }
++ transform: translateX(-50%); animation: bcs-gl-in var(--ov-dur-4, .45s) var(--ov-ease-spring, cubic-bezier(.2,1.3,.3,1)) both; }
++.bcs-livecard.is-out { animation: bcs-gl-out var(--ov-dur-2, .25s) ease-in forwards; }
@keyframes bcs-gl-in { from { opacity: 0; transform: translate(-50%, -14px) scale(.94); } to { opacity: 1; transform: translate(-50%, 0) scale(1); } }
@keyframes bcs-gl-out { to { opacity: 0; transform: translate(-50%, -10px) scale(.97); } }
.bcs-gl-head { display: flex; align-items: center; gap: 10px; }
.bcs-gl-head b { flex: 1; font-size: 1.15rem; font-weight: 800; letter-spacing: -.2px; }
.bcs-gl-dot { position: relative; flex: none; width: 12px; height: 12px; border-radius: 50%; background: var(--live-red, #ef4444); }
--.bcs-gl-dot::after { content: ""; position: absolute; inset: -6px; border-radius: 50%; border: 2px solid var(--live-red, #ef4444); animation: bcs-gl-ping 1.4s ease-out infinite; }
++.bcs-gl-dot::after { content: ""; position: absolute; inset: -6px; border-radius: 50%; border: 2px solid var(--live-red, #ef4444); animation: bcs-gl-ping 1.4s var(--ov-ease-out, ease-out) infinite; }
@keyframes bcs-gl-ping { from { transform: scale(.5); opacity: .9; } to { transform: scale(1.6); opacity: 0; } }
.bcs-gl-x { all: unset; box-sizing: border-box; cursor: pointer; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px; color: var(--text-secondary); }
.bcs-gl-x:hover, .bcs-gl-x:focus-visible { background: rgba(255,255,255,.08); color: var(--text-primary); }
@@@ -3124,16 +3124,16 @@ body.bc-golive-dock-on #page-broadcast { padding-bottom: calc(104px + var(--ovna
.bcs-gl-link { display: flex; align-items: center; gap: 8px; padding: 6px 6px 6px 12px; border-radius: 14px; background: var(--bg-primary, #0a0f1c); border: 1px solid var(--border, #1f2d47); }
.bcs-gl-link code { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 700 1rem/1.3 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: var(--text-primary); background: none; user-select: all; -webkit-user-select: all; }
.bcs-gl-copy, .bcs-gl-share { all: unset; box-sizing: border-box; cursor: pointer; flex: none; display: inline-flex; align-items: center; gap: 4px; height: 42px; padding: 0 16px; border-radius: 11px; font-weight: 700; font-size: .95rem;
-- background: var(--accent-strong, var(--accent, #3472d8)); color: var(--on-accent-strong, var(--on-accent, #fff)); transition: transform .15s, background .2s; }
++ background: var(--accent-strong, var(--accent, #3472d8)); color: var(--on-accent-strong, var(--on-accent, #fff)); transition: transform var(--ov-dur-2, .15s), background var(--ov-dur-2, .2s); }
.bcs-gl-copy:active, .bcs-gl-share:active { transform: scale(.96); }
.bcs-gl-copy.is-copied, .bc-ctrl-btn.is-copied { background: var(--success, #16a34a); color: #fff; }
.bcs-gl-copy:focus-visible, .bcs-gl-share:focus-visible { outline: 2px solid var(--accent-light, #60a5fa); outline-offset: 2px; }
.bcs-gl-more { display: flex; justify-content: flex-end; margin-top: 8px; }
.bcs-gl-share { height: 34px; background: rgba(255,255,255,.08); color: var(--text-primary); }
.bcs-gl-bar { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: rgba(255,255,255,.06); }
--.bcs-gl-bar i { display: block; height: 100%; background: linear-gradient(90deg, var(--live-red, #ef4444), var(--accent, #3b82f6)); transform-origin: left; transition: transform .2s linear; }
++.bcs-gl-bar i { display: block; height: 100%; background: linear-gradient(90deg, var(--live-red, #ef4444), var(--accent, #3b82f6)); transform-origin: left; transition: transform var(--ov-dur-2, .2s) linear; }
.bcs-gl-burst { position: absolute; left: 22px; top: 21px; width: 0; height: 0; pointer-events: none; }
--.bcs-gl-burst i { position: absolute; width: 5px; height: 5px; margin: -2.5px; border-radius: 50%; background: hsl(var(--a), 90%, 66%); opacity: 0; animation: bcs-gl-spark .9s cubic-bezier(.2,.8,.3,1) var(--d) forwards; }
++.bcs-gl-burst i { position: absolute; width: 5px; height: 5px; margin: -2.5px; border-radius: 50%; background: hsl(var(--a), 90%, 66%); opacity: 0; animation: bcs-gl-spark .9s var(--ov-ease-out, cubic-bezier(.2,.8,.3,1)) var(--d) forwards; }
@keyframes bcs-gl-spark { 0% { opacity: 1; transform: rotate(var(--a)) translateX(0); } 100% { opacity: 0; transform: rotate(var(--a)) translateX(48px) scale(.4); } }
@media (prefers-reduced-motion: reduce) { .bcs-livecard, .bcs-gl-dot::after, .bcs-gl-burst i { animation: none !important; } .bcs-gl-burst { display: none; } }
/* A phone: the link on its own line, whole, and Copy the width of the card (an easy tap while holding the phone). */
ddiff --git a/public/css/features/channel.css b/public/css/features/channel.css
iindex afa3dd85..52341354 100644
---- a/public/css/features/channel.css
++++ b/public/css/features/channel.css
@@@ -11,7 +11,7 @@
.ch-paste-card {
display: block; background: var(--bg-card); border: 1px solid var(--border);
border-radius: 12px; overflow: hidden; text-decoration: none; color: var(--text-primary);
-- transition: border-color 0.15s ease, transform 0.15s ease;
++ transition: border-color var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease), transform var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
.ch-paste-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.ch-paste-thumb { height: 120px; background: var(--bg-tertiary); display: flex; align-items: center; justify-content: center; overflow: hidden; }
@@@ -42,7 +42,7 @@
border-radius: 3px;
background: var(--accent, #7c5cfc);
opacity: .6;
-- animation: mic-bar-pulse 1.2s ease-in-out infinite;
++ animation: mic-bar-pulse 1.2s var(--ov-ease-in-out, ease-in-out) infinite;
}
.mic-only-label {
font-size: 0.82rem;
@@@ -58,7 +58,7 @@
background: var(--bg-secondary); color: var(--text-primary);
border: 1px solid var(--border); border-radius: var(--radius);
padding: 6px 10px; font-size: 0.88rem; cursor: pointer;
-- transition: border-color 0.15s;
++ transition: border-color var(--ov-dur-2, 0.15s);
}
.ch-vods-filter-select:hover, .ch-vods-filter-select:focus { border-color: var(--accent); outline: none; }
#page-channel .stream-layout {
@@@ -109,7 +109,7 @@
}
.stream-layout:has(.video-container.is-vertical) .chat-sidebar {
width: clamp(340px, 35vw, 500px);
-- transition: width 0.3s ease;
++ transition: width var(--ov-dur-3, 0.3s) var(--ov-ease-out, ease);
}
}
@@@ -219,7 +219,7 @@
background: linear-gradient(145deg, rgba(10, 14, 24, 0.84), rgba(8, 12, 20, 0.58));
box-shadow: 0 26px 70px rgba(0, 0, 0, 0.36), inset 0 1px 0 rgba(255,255,255,0.04);
backdrop-filter: blur(22px);
-- transition: padding 0.35s ease, gap 0.35s ease, border-color 0.35s ease, background 0.35s ease, box-shadow 0.35s ease;
++ transition: padding var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease), gap var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease), border-color var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease), background var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease), box-shadow var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease);
}
.player-loader-shell.is-expanded {
@@@ -231,7 +231,7 @@
.player-loader-shell.is-phase-changing .player-loader-kicker,
.player-loader-shell.is-phase-changing .player-loader-summary,
.player-loader-shell.is-phase-changing .player-loader-progress {
-- animation: playerLoaderStatusSwap 420ms cubic-bezier(0.22, 1, 0.36, 1);
++ animation: playerLoaderStatusSwap var(--ov-dur-3, 420ms) var(--ov-ease-out, cubic-bezier(0.22, 1, 0.36, 1));
}
.player-loader-halo {
@@@ -245,7 +245,7 @@
inset: 14%;
border-style: dashed;
border-color: color-mix(in srgb, var(--loader-c) 46%, transparent);
-- animation-duration: 22s;
++ animation-duration: 2;
animation-direction: reverse;
}
@@@ -257,7 +257,7 @@
filter: blur(0.5px);
opacity: 0.92;
border-radius: 42% 58% 53% 47% / 45% 44% 56% 55%;
-- animation: playerLoaderMorph 8.5s ease-in-out infinite alternate, playerLoaderFloat 6.2s ease-in-out infinite;
++ animation: playerLoaderMorph 8.5s var(--ov-ease-in-out, ease-in-out) infinite alternate, playerLoaderFloat 6.2s var(--ov-ease-in-out, ease-in-out) infinite;
}
.player-loader-orb.orb-b {
@@@ -265,7 +265,7 @@
background: linear-gradient(210deg, color-mix(in srgb, var(--loader-c) 78%, #ffffff 12%), color-mix(in srgb, var(--loader-a) 88%, #0f172a 8%));
mix-blend-mode: screen;
opacity: 0.88;
-- animation-duration: 10.5s, 5.2s;
++ animation-duration: 1, 5;
animation-direction: alternate-reverse, alternate;
}
@@@ -276,7 +276,7 @@
0 0 46px color-mix(in srgb, var(--loader-a) 34%, transparent),
inset 0 0 18px rgba(255,255,255,0.06);
backdrop-filter: blur(6px);
-- animation: playerLoaderPulse 3s ease-in-out infinite;
++ animation: playerLoaderPulse 3s var(--ov-ease-in-out, ease-in-out) infinite;
}
.player-loader-grid {
inset: 8%;
@@@ -292,7 +292,7 @@
inset: -12%;
background: linear-gradient(120deg, transparent 30%, rgba(255,255,255,0.10) 48%, transparent 66%);
transform: translateX(-42%) rotate(12deg);
-- animation: playerLoaderSheen 4.6s ease-in-out infinite;
++ animation: playerLoaderSheen 4.6s var(--ov-ease-in-out, ease-in-out) infinite;
}
.player-loader-copy {
@@@ -377,7 +377,7 @@
color: rgba(203, 213, 225, 0.5);
opacity: 0.85;
text-shadow: 0 1px 6px rgba(0,0,0,0.55);
-- transition: color 0.24s ease, opacity 0.24s ease, text-shadow 0.24s ease;
++ transition: color var(--ov-dur-2, 0.24s) var(--ov-ease-out, ease), opacity var(--ov-dur-2, 0.24s) var(--ov-ease-out, ease), text-shadow var(--ov-dur-2, 0.24s) var(--ov-ease-out, ease);
}
.player-loader-step.is-done {
@@@ -427,7 +427,7 @@
align-items: center;
gap: 8px;
cursor: pointer;
-- transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
++ transition: transform var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease), box-shadow var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease), border-color var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease);
}
.player-loader-action:hover {
@@@ -460,7 +460,7 @@
opacity: 0;
filter: blur(26px) saturate(1.08);
transform: scale(1.03);
-- transition: opacity 0.55s cubic-bezier(0.22, 1, 0.36, 1), filter 0.82s cubic-bezier(0.22, 1, 0.36, 1), transform 0.82s cubic-bezier(0.22, 1, 0.36, 1);
++ transition: opacity var(--ov-dur-4, 0.55s) var(--ov-ease-out, cubic-bezier(0.22, 1, 0.36, 1)), filter 0.82s var(--ov-ease-out, cubic-bezier(0.22, 1, 0.36, 1)), transform 0.82s var(--ov-ease-out, cubic-bezier(0.22, 1, 0.36, 1));
will-change: opacity, filter, transform;
pointer-events: none;
}
@@@ -594,7 +594,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
width: 36px; height: 36px;
background: var(--bg-hover); border: 1px solid var(--border);
border-radius: var(--radius); color: var(--text-primary);
-- cursor: pointer; transition: all 0.1s;
++ cursor: pointer; transition: all var(--ov-dur-1, 0.1s);
font-size: 0.9rem;
}
@@@ -615,7 +615,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
padding: 6px 12px; background: var(--bg-hover);
border: 1px solid var(--border); border-radius: var(--radius);
color: var(--text-primary); font-size: 0.75rem;
-- cursor: pointer; transition: all 0.1s;
++ cursor: pointer; transition: all var(--ov-dur-1, 0.1s);
}
.preset-btn:hover { border-color: var(--accent); color: var(--accent); }
@@@ -634,14 +634,14 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
font-size: 0.7rem; font-weight: 700; letter-spacing: 0.01em; text-transform: none;
white-space: nowrap; pointer-events: none; opacity: 0; z-index: 60;
box-shadow: 0 8px 22px rgba(0,0,0,0.4);
-- transition: opacity 0.18s ease, transform 0.2s cubic-bezier(.2,.9,.3,1.25);
++ transition: opacity var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease), transform var(--ov-dur-2, 0.2s) var(--ov-ease-out, cubic-bezier(.2,.9,.3,1.25));
}
.staff-badge[data-tip]::before {
content: ''; position: absolute; bottom: calc(100% + 2px); left: 50%;
transform: translateX(-50%) translateY(5px);
border: 5px solid transparent; border-top-color: var(--border-light, var(--border));
opacity: 0; pointer-events: none; z-index: 60;
-- transition: opacity 0.18s ease, transform 0.2s cubic-bezier(.2,.9,.3,1.25);
++ transition: opacity var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease), transform var(--ov-dur-2, 0.2s) var(--ov-ease-out, cubic-bezier(.2,.9,.3,1.25));
}
.staff-badge[data-tip]:hover::after { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
.staff-badge[data-tip]:hover::before { opacity: 1; transform: translateX(-50%) translateY(0); }
@@@ -786,14 +786,14 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
.stream-switch-spinner::before {
inset: 18%;
border: 2px solid rgba(255,255,255,0.9);
-- animation: spin 1.2s ease-in-out infinite; /* "spin" breathes now (style.css): nothing spins */
++ animation: spin 1.2s var(--ov-ease-in-out, ease-in-out) infinite; /* "spin" breathes now (style.css): nothing spins */
}
.stream-switch-spinner::after {
inset: 34%;
background: radial-gradient(circle at 32% 30%, rgba(255,255,255,0.9), color-mix(in srgb, var(--accent) 86%, white 10%));
box-shadow: 0 0 18px color-mix(in srgb, var(--accent) 44%, transparent);
-- animation: playerLoaderPulse 1.8s ease-in-out infinite;
++ animation: playerLoaderPulse 1.8s var(--ov-ease-in-out, ease-in-out) infinite;
}
.ch-cumulative-viewers {
display: flex; align-items: center; gap: 10px;
@@@ -808,7 +808,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
color: #4fc3f7; background: rgba(79, 195, 247, 0.08);
padding: 2px 8px; border-radius: 10px;
display: inline-flex; align-items: center; gap: 5px;
-- text-decoration: none; transition: all 0.15s;
++ text-decoration: none; transition: all var(--ov-dur-2, 0.15s);
}
.ch-restream-badge {
font-size: 0.72rem; font-weight: 600;
@@@ -816,14 +816,14 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
padding: 2px 8px; border-radius: 10px;
display: inline-flex; align-items: center; gap: 5px;
background: rgba(255, 255, 255, 0.06);
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
}
.ch-share-stream {
font-size: 0.72rem; color: var(--text-muted);
background: none; border: 1px solid var(--border);
padding: 2px 8px; border-radius: 10px; cursor: pointer;
display: inline-flex; align-items: center; gap: 4px;
-- font-family: var(--font); transition: all 0.15s;
++ font-family: var(--font); transition: all var(--ov-dur-2, 0.15s);
}
.ch-share-stream:hover { color: var(--accent); border-color: var(--accent); }
.ch-info-bar {
@@@ -847,7 +847,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
display: inline-flex; align-items: center; justify-content: center;
width: 20px; height: 20px; margin-left: 2px; border-radius: 5px;
color: var(--text-muted); background: color-mix(in srgb, var(--accent) 14%, transparent);
-- cursor: pointer; transition: color 0.15s ease, background 0.15s ease;
++ cursor: pointer; transition: color var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease), background var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
.ch-tab-edit:hover { color: var(--accent); background: color-mix(in srgb, var(--accent) 26%, transparent); }
.ch-about-content { display: flex; flex-direction: column; gap: 24px; }
@@@ -947,17 +947,17 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
.off-cyc-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.off-cyc-title { display: inline-flex; align-items: center; gap: 7px; font-size: 0.82rem; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-primary); }
.off-cyc-ranges { display: inline-flex; gap: 4px; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 999px; padding: 3px; }
--.off-cyc-chip { border: 0; background: transparent; color: var(--text-secondary); font-size: 0.74rem; font-weight: 600; padding: 4px 12px; border-radius: 999px; white-space: nowrap; cursor: pointer; transition: background .15s, color .15s; }
++.off-cyc-chip { border: 0; background: transparent; color: var(--text-secondary); font-size: 0.74rem; font-weight: 600; padding: 4px 12px; border-radius: 999px; white-space: nowrap; cursor: pointer; transition: background var(--ov-dur-2, .15s), color var(--ov-dur-2, .15s); }
.off-cyc-single-label { font-size: 0.74rem; color: var(--text-muted); }
.off-cyc-nav { display: inline-flex; gap: 4px; margin-left: auto; }
--.off-cyc-arrow { width: 28px; height: 28px; border-radius: 50%; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-secondary); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: border-color .15s, color .15s; }
++.off-cyc-arrow { width: 28px; height: 28px; border-radius: 50%; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-secondary); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: border-color var(--ov-dur-2, .15s), color var(--ov-dur-2, .15s); }
.off-cyc-arrow:hover { border-color: var(--accent); color: var(--text-primary); }
.off-cyc-body { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.off-cyc-body:has(.off-top-card:only-child) { grid-template-columns: minmax(0, 420px); justify-content: center; }
/* One card: the whole block narrows to it, so the "Most watched" heading sits over the card instead of far to its left. */
.off-cyc:has(.off-top-card:only-child) { max-width: calc(420px + 2 * clamp(10px, 3vw, 24px)); }
--.off-cyc-fade { animation: offCycFade .32s ease; }
--.off-top-card { display: flex; flex-direction: column; text-align: left; text-decoration: none; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; transition: border-color .15s, transform .15s, box-shadow .15s; }
++.off-cyc-fade { animation: offCycFade var(--ov-dur-3, .32s) var(--ov-ease-out, ease); }
++.off-top-card { display: flex; flex-direction: column; text-align: left; text-decoration: none; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; transition: border-color var(--ov-dur-2, .15s), transform var(--ov-dur-2, .15s), box-shadow var(--ov-dur-2, .15s); }
.off-top-card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow, 0 6px 20px rgba(0,0,0,.3)); }
.off-top-info { padding: 10px 12px; }
.off-top-title { font-size: 0.9rem; font-weight: 700; color: var(--text-primary); line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
@@@ -1076,7 +1076,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
.weather-tab {
flex: 1; padding: 3px 8px; border: none; border-radius: 4px;
background: transparent; color: var(--text-muted); font-size: 0.72rem;
-- font-weight: 600; cursor: pointer; transition: all 0.15s;
++ font-weight: 600; cursor: pointer; transition: all var(--ov-dur-2, 0.15s);
}
.weather-daily { display: flex; flex-direction: column; gap: 2px; margin-top: 2px; }
.weather-day {
@@@ -1196,7 +1196,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
background: var(--bg-tertiary); color: var(--text-secondary);
border: 1px solid var(--border); border-radius: 999px;
padding: 4px 11px; font-size: 0.8rem; font-weight: 600; cursor: pointer;
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
}
.ct-badge.ct-self { font-style: italic; }
.ct-count { font-size: 0.72rem; opacity: 0.7; background: rgba(255,255,255,.08); border-radius: 999px; padding: 0 6px; }
@@@ -1241,9 +1241,9 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
@media (max-width: 560px) { .ai-tl-moment-thumb { width: 72px; height: 40px; } }
.ai-tl-months { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 0 0 18px; padding-bottom: 12px; border-bottom: 1px solid var(--border, rgba(255,255,255,0.06)); }
.ai-tl-months-label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); font-weight: 700; margin-right: 4px; display: inline-flex; align-items: center; gap: 5px; }
--.ai-tl-month { font-size: 0.75rem; font-weight: 600; color: var(--text-secondary); background: var(--bg-elevated, rgba(255,255,255,0.04)); border: 1px solid var(--border, rgba(255,255,255,0.08)); border-radius: 999px; padding: 3px 11px; cursor: pointer; transition: all .15s; }
++.ai-tl-month { font-size: 0.75rem; font-weight: 600; color: var(--text-secondary); background: var(--bg-elevated, rgba(255,255,255,0.04)); border: 1px solid var(--border, rgba(255,255,255,0.08)); border-radius: 999px; padding: 3px 11px; cursor: pointer; transition: all var(--ov-dur-2, .15s); }
.ai-tl-month:hover { color: var(--accent, #8b5cf6); border-color: color-mix(in srgb, var(--accent, #8b5cf6) 45%, transparent); background: color-mix(in srgb, var(--accent, #8b5cf6) 10%, transparent); }
--.ai-tl-moments-toggle { margin-top: 8px; display: inline-flex; align-items: center; gap: 7px; font-size: 0.8rem; font-weight: 600; color: var(--accent, #8b5cf6); background: color-mix(in srgb, var(--accent, #8b5cf6) 9%, transparent); border: 1px solid color-mix(in srgb, var(--accent, #8b5cf6) 22%, transparent); border-radius: 8px; padding: 5px 11px; cursor: pointer; transition: all .15s; }
++.ai-tl-moments-toggle { margin-top: 8px; display: inline-flex; align-items: center; gap: 7px; font-size: 0.8rem; font-weight: 600; color: var(--accent, #8b5cf6); background: color-mix(in srgb, var(--accent, #8b5cf6) 9%, transparent); border: 1px solid color-mix(in srgb, var(--accent, #8b5cf6) 22%, transparent); border-radius: 8px; padding: 5px 11px; cursor: pointer; transition: all var(--ov-dur-2, .15s); }
.ai-tl-moments-toggle:hover { background: color-mix(in srgb, var(--accent, #8b5cf6) 16%, transparent); }
.ai-tl-moments-toggle.open i { transform: rotate(90deg); }
.ai-tl-moments[hidden] { display: none; }
@@@ -1265,7 +1265,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
.ch-about-ai-hint { font-size: 0.78rem; }
.ai-tl-side-label { font-size: 0.95rem; font-weight: 800; color: var(--text-primary); margin: 26px 0 14px; display: flex; align-items: center; gap: 9px; padding-bottom: 7px; border-bottom: 2px solid color-mix(in srgb, var(--accent, #8b5cf6) 30%, transparent); }
.ai-tl-subtabs { display: flex; gap: 6px; margin: 14px 0 16px; border-bottom: 1px solid var(--border, rgba(255,255,255,0.08)); }
--.ai-tl-subtab { font-size: 0.9rem; font-weight: 700; color: var(--text-muted); background: none; border: none; border-bottom: 2px solid transparent; padding: 8px 14px; cursor: pointer; display: inline-flex; align-items: center; gap: 7px; margin-bottom: -1px; transition: color .15s; }
++.ai-tl-subtab { font-size: 0.9rem; font-weight: 700; color: var(--text-muted); background: none; border: none; border-bottom: 2px solid transparent; padding: 8px 14px; cursor: pointer; display: inline-flex; align-items: center; gap: 7px; margin-bottom: -1px; transition: color var(--ov-dur-2, .15s); }
.ai-tl-pane--hidden { display: none; }
.ov-pip:hover { border-color: rgba(255, 255, 255, .38); }
.ov-pip.is-dragging { cursor: grabbing; box-shadow: 0 10px 30px rgba(0, 0, 0, .7); }
@@@ -1323,21 +1323,21 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
background: linear-gradient(160deg, color-mix(in srgb, var(--accent) 10%, var(--bg-card)), var(--bg-card));
border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
box-shadow: 0 10px 30px rgba(0,0,0,0.25);
-- animation: i18nSlideIn 0.4s ease both;
++ animation: i18nSlideIn var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease) both;
}
.ch-captions-head {
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
padding: 8px 12px; border-bottom: 1px solid color-mix(in srgb, var(--accent) 25%, var(--border));
}
.ch-captions-title { display: inline-flex; align-items: center; gap: 7px; font-weight: 800; font-size: 0.88rem; color: var(--text-primary); }
--.ch-captions-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--live-red); animation: i18nBlink 1.4s ease-in-out infinite; }
++.ch-captions-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--live-red); animation: i18nBlink 1.4s var(--ov-ease-in-out, ease-in-out) infinite; }
.ch-captions-sub { color: var(--text-secondary); font-size: 0.76rem; }
.ch-captions-toggle { margin-left: auto; background: none; border: none; color: var(--text-muted); cursor: pointer; padding: 4px 6px; border-radius: 6px; }
.ch-captions-toggle:hover { color: var(--text-primary); background: var(--bg-hover); }
.ch-captions-lines { max-height: 190px; overflow-y: auto; padding: 8px 12px; display: grid; gap: 6px; scroll-behavior: smooth; }
.ch-captions.collapsed .ch-captions-lines { display: none; }
.ch-captions-wait { color: var(--text-muted); font-size: 0.8rem; font-style: italic; }
--.ch-caption { animation: i18nSlideIn 0.35s ease both; padding: 4px 0; border-bottom: 1px dashed color-mix(in srgb, var(--border) 70%, transparent); }
++.ch-caption { animation: i18nSlideIn var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease) both; padding: 4px 0; border-bottom: 1px dashed color-mix(in srgb, var(--border) 70%, transparent); }
.ch-caption:last-child { border-bottom: none; }
.ch-caption-en { font-size: 0.95rem; color: var(--text-primary); line-height: 1.35; }
.ch-caption-src { font-size: 0.78rem; color: var(--text-secondary); margin-top: 2px; }
@@@ -1346,7 +1346,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
margin: 8px 0 12px; padding: 10px 12px; border-radius: 10px;
background: color-mix(in srgb, var(--accent) 8%, transparent);
border-left: 3px solid var(--accent); white-space: pre-wrap; line-height: 1.5;
-- animation: i18nSlideIn 0.4s ease both;
++ animation: i18nSlideIn var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease) both;
}
.ch-about-bio-translated-label { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--accent-light); margin-bottom: 4px; }
@@@ -1370,7 +1370,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
.ch-social-pills { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 8px; }
.ch-social-pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px 4px 8px; border-radius: 999px; font-size: 0.8rem; font-weight: 700;
color: var(--text-primary); text-decoration: none; background: color-mix(in srgb, var(--sc) 12%, transparent);
-- border: 1px solid color-mix(in srgb, var(--sc) 38%, transparent); transition: background .15s, transform .1s; max-width: 220px; }
++ border: 1px solid color-mix(in srgb, var(--sc) 38%, transparent); transition: background var(--ov-dur-2, .15s), transform var(--ov-dur-1, .1s); max-width: 220px; }
.ch-social-pill i { color: var(--sc); font-size: 0.95em; }
.ch-social-pill span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-social-pill:hover { background: color-mix(in srgb, var(--sc) 22%, transparent); transform: translateY(-1px); }
@@@ -1379,7 +1379,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
.ch-socials-head i { color: var(--accent); }
.ch-socials-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.ch-social-card { border-radius: 14px; background: var(--bg-card); border: 1px solid color-mix(in srgb, var(--sc) 28%, var(--border)); overflow: hidden; min-width: 0;
-- box-shadow: inset 0 3px 0 color-mix(in srgb, var(--sc) 70%, transparent); transition: border-color .15s, transform .15s; }
++ box-shadow: inset 0 3px 0 color-mix(in srgb, var(--sc) 70%, transparent); transition: border-color var(--ov-dur-2, .15s), transform var(--ov-dur-2, .15s); }
.ch-social-card:hover { border-color: color-mix(in srgb, var(--sc) 60%, var(--border)); transform: translateY(-2px); }
.ch-social-card.is-live { border-color: #e0245e; }
.ch-social-card-top { display: grid; grid-template-columns: auto 1fr auto auto; gap: 10px; align-items: center; padding: 12px 14px; color: var(--text-primary); text-decoration: none; }
@@@ -1426,7 +1426,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
.chat-header .ch-chat-collapse { margin-right: 2px; }
@media (min-width: 1181px) {
#page-channel.chat-collapsed .stream-layout > .chat-sidebar { display: none !important; }
-- #page-channel.chat-revealing .stream-layout > .chat-sidebar { animation: chChatReveal .32s cubic-bezier(.22,1,.36,1); }
++ #page-channel.chat-revealing .stream-layout > .chat-sidebar { animation: chChatReveal var(--ov-dur-3, .32s) var(--ov-ease-out, cubic-bezier(.22,1,.36,1)); }
}
@keyframes chChatReveal { from { opacity: 0; transform: translateX(36px); } to { opacity: 1; transform: none; } }
.ch-chat-reopen {
@@@ -1435,7 +1435,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
border: 1px solid var(--border); border-right: 0; border-radius: 12px 0 0 12px;
background: color-mix(in srgb, var(--bg-card, var(--bg-secondary)) 90%, transparent); backdrop-filter: blur(10px);
color: var(--text-secondary); font: 700 0.8rem/1 inherit; cursor: pointer; box-shadow: var(--shadow);
-- transition: color .15s, border-color .15s, padding-right .18s ease;
++ transition: color var(--ov-dur-2, .15s), border-color var(--ov-dur-2, .15s), padding-right var(--ov-dur-2, .18s) var(--ov-ease-out, ease);
}
.ch-chat-reopen:hover { color: var(--text-primary); border-color: var(--accent); padding-right: 16px; }
.ch-chat-reopen .fa-comments { color: var(--accent); }
@@@ -1457,7 +1457,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
#page-channel.chat-resizing .chat-sidebar { transition: none !important; }
#page-channel.chat-resizing iframe, #page-channel.chat-resizing video { pointer-events: none; }
.chat-grip { position: absolute; z-index: 30; touch-action: none; outline: none; }
--.chat-grip::before, .chat-grip::after { content: ''; position: absolute; pointer-events: none; transition: opacity .15s, background-color .15s; }
++.chat-grip::before, .chat-grip::after { content: ''; position: absolute; pointer-events: none; transition: opacity var(--ov-dur-2, .15s), background-color var(--ov-dur-2, .15s); }
@media (min-width: 1181px), (max-width: 1180px) and (orientation: landscape) {
.chat-grip { top: 0; bottom: 0; left: -6px; width: 12px; cursor: col-resize; }
.chat-grip::before { left: 5px; top: 0; bottom: 0; width: 2px; background: var(--accent); opacity: 0; }
@@@ -1487,7 +1487,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
.ch-about-more { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; padding: 4px 2px; border: 0; background: none; cursor: pointer;
color: var(--accent-light, var(--accent)); font: 600 0.78rem/1 inherit; letter-spacing: 0; text-transform: none; }
.ch-about-more:hover { color: var(--text-primary); }
--.ch-about-more i { font-size: 0.72rem; transition: transform .18s ease; }
++.ch-about-more i { font-size: 0.72rem; transition: transform var(--ov-dur-2, .18s) var(--ov-ease-out, ease); }
.ch-about-more:hover i { transform: translateX(2px); }
.about-ai-overview { color: var(--text-secondary); font-size: 0.95rem; line-height: 1.6; max-width: 72ch; }
/* Latest videos / Popular clips: a snap-scrolling row of cards. */
@@@ -1496,7 +1496,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
scrollbar-color: color-mix(in srgb, var(--accent) 30%, transparent) transparent; overscroll-behavior-x: contain; }
.ch-about-card { scroll-snap-align: start; display: flex; flex-direction: column; gap: 6px; min-width: 0; color: inherit; text-decoration: none; }
.ch-about-card-thumb { position: relative; display: block; aspect-ratio: 16 / 9; border-radius: 10px; overflow: hidden;
-- background: var(--bg-tertiary); border: 1px solid var(--border); transition: transform .2s cubic-bezier(.22,1,.36,1), box-shadow .2s, border-color .2s; }
++ background: var(--bg-tertiary); border: 1px solid var(--border); transition: transform var(--ov-dur-2, .2s) var(--ov-ease-out, cubic-bezier(.22,1,.36,1)), box-shadow var(--ov-dur-2, .2s), border-color var(--ov-dur-2, .2s); }
.ch-about-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ch-about-card:hover .ch-about-card-thumb, .ch-about-card:focus-visible .ch-about-card-thumb { transform: translateY(-2px); border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
box-shadow: 0 8px 22px color-mix(in srgb, var(--accent) 16%, rgba(0,0,0,.35)); }
@@@ -1563,7 +1563,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
display: block !important; position: absolute; left: -4px; bottom: calc(100% + 10px); z-index: 6;
width: 150px !important; height: 40px; padding: 0 14px; opacity: 1 !important; box-sizing: border-box;
border-radius: 999px; background: rgba(12, 15, 24, 0.92); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
-- animation: vc-volume-pop 0.18s ease-out;
++ animation: vc-volume-pop var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease-out);
}
}
@keyframes vc-volume-pop { from { opacity: 0; transform: translateY(4px); } }
@@@ -1583,7 +1583,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
aspect-ratio: var(--video-ar, 16 / 9);
max-height: var(--player-max-h);
isolation: isolate; /* the glow sits behind the picture and every overlay, above the box's black */
-- transition: aspect-ratio 0.45s cubic-bezier(0.22, 1, 0.36, 1), max-height 0.3s ease, background 0.35s ease, box-shadow 0.35s ease;
++ transition: aspect-ratio var(--ov-dur-4, 0.45s) var(--ov-ease-out, cubic-bezier(0.22, 1, 0.36, 1)), max-height var(--ov-dur-3, 0.3s) var(--ov-ease-out, ease), background var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease), box-shadow var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease);
}
#page-channel.has-live-tabs .video-container { --live-tabs-h: 44px; }
@media (max-width: 768px) and (orientation: portrait) {
@@@ -1595,7 +1595,7 @@ button.viewer-badge:focus-visible { outline: 2px solid #93c5fd; outline-offset:
.player-shape-glow {
position: absolute; inset: 0; width: 100%; height: 100%; z-index: -1; pointer-events: none;
opacity: 0; transform: scale(1.25); filter: blur(32px) saturate(1.25) brightness(0.5);
-- transition: opacity 0.9s ease;
++ transition: opacity 0.9s var(--ov-ease-out, ease);
}
.player-shape-glow.is-on { opacity: 0.8; }
#page-channel .video-container:fullscreen { aspect-ratio: auto; max-height: none; width: 100%; height: 100%; }
ddiff --git a/public/css/features/chatpage.css b/public/css/features/chatpage.css
iindex 5ea80d39..ae429a19 100644
---- a/public/css/features/chatpage.css
++++ b/public/css/features/chatpage.css
@@@ -12,10 +12,10 @@
display: flex; align-items: center; gap: 8px; cursor: pointer;
padding: 8px 16px; border-bottom: 1px solid var(--border);
background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 12%, transparent), transparent);
-- font-size: 0.82rem; color: var(--text-secondary); transition: background 0.15s;
++ font-size: 0.82rem; color: var(--text-secondary); transition: background var(--ov-dur-2, 0.15s);
}
.global-ai-strip:hover { background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 20%, transparent), transparent); }
--.gai-strip-caret { flex-shrink: 0; opacity: 0.6; transition: transform 0.2s; }
++.gai-strip-caret { flex-shrink: 0; opacity: 0.6; transition: transform var(--ov-dur-2, 0.2s); }
.global-ai-strip.open .gai-strip-caret { transform: rotate(180deg); }
.global-ai-panel {
@@@ -32,7 +32,7 @@
.gai-tl-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.gai-tl-search { flex: 1; max-width: 200px; padding: 5px 10px; font-size: 0.78rem; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-input, var(--bg-tertiary)); color: var(--text-primary); }
.gai-tl-periods { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 12px; }
--.gai-tl-chip { border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-secondary); font-size: 0.72rem; font-weight: 600; padding: 3px 11px; border-radius: 999px; cursor: pointer; transition: background .15s, color .15s, border-color .15s; }
++.gai-tl-chip { border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-secondary); font-size: 0.72rem; font-weight: 600; padding: 3px 11px; border-radius: 999px; cursor: pointer; transition: background var(--ov-dur-2, .15s), color var(--ov-dur-2, .15s), border-color var(--ov-dur-2, .15s); }
.gai-memory { font-size: 0.85rem; line-height: 1.5; color: var(--text-secondary); white-space: pre-wrap; }
.vc-sidebar {
width: 300px;
@@@ -66,7 +66,7 @@
display: flex;
align-items: center;
justify-content: center;
-- transition: background 0.15s, transform 0.1s;
++ transition: background var(--ov-dur-2, 0.15s), transform var(--ov-dur-1, 0.1s);
}
.vc-create-btn:hover {
background: var(--accent-hover);
@@@ -85,7 +85,7 @@
padding: 9px 12px;
border-radius: var(--radius);
cursor: pointer;
-- transition: background 0.12s;
++ transition: background var(--ov-dur-1, 0.12s);
margin-bottom: 2px;
}
@@@ -272,7 +272,7 @@
display: flex;
align-items: center;
justify-content: center;
-- transition: background 0.12s, color 0.12s, transform 0.1s;
++ transition: background var(--ov-dur-1, 0.12s), color var(--ov-dur-1, 0.12s), transform var(--ov-dur-1, 0.1s);
}
.vc-ctrl-settings {
background: none;
@@@ -352,13 +352,13 @@
}
/* Channel rows arrive and leave with a short move; the joined row and speaking names stay clear. */
--.vc-channel-item.is-entering { animation: vcRowIn 200ms cubic-bezier(.2, .9, .3, 1); }
--.vc-channel-item.is-leaving { animation: vcRowOut 200ms ease-in forwards; }
++.vc-channel-item.is-entering { animation: vcRowIn var(--ov-dur-2, 200ms) var(--ov-ease-out, cubic-bezier(.2, .9, .3, 1)); }
++.vc-channel-item.is-leaving { animation: vcRowOut var(--ov-dur-2, 200ms) ease-in forwards; }
@keyframes vcRowIn { from { opacity: 0; transform: translateX(-6px); } }
@keyframes vcRowOut { to { opacity: 0; transform: translateX(-6px); } }
.vc-channel-item.is-private .vc-channel-icon { color: #f472b6; }
.vc-participant-row.speaking .vc-avatar-mini { box-shadow: 0 0 0 2px rgba(74, 222, 128, 0.7); }
--.vc-connected-panel.is-open { animation: vcPanelIn 220ms cubic-bezier(.2, .9, .3, 1); }
++.vc-connected-panel.is-open { animation: vcPanelIn var(--ov-dur-2, 220ms) var(--ov-ease-out, cubic-bezier(.2, .9, .3, 1)); }
@keyframes vcPanelIn { from { opacity: 0; transform: translateY(6px); } }
@media (prefers-reduced-motion: reduce) { .vc-channel-item.is-entering, .vc-channel-item.is-leaving, .vc-connected-panel.is-open { animation: none; } }
ddiff --git a/public/css/features/content.css b/public/css/features/content.css
iindex bfb5c79a..d91f41b8 100644
---- a/public/css/features/content.css
++++ b/public/css/features/content.css
@@@ -138,7 +138,7 @@
overflow: hidden;
box-shadow: var(--elev-1);
transition: border-color var(--motion-normal, 200ms), box-shadow var(--motion-normal, 200ms), transform var(--motion-normal, 200ms) var(--ease-standard, ease);
-- animation: cfIn .32s var(--ease-standard, ease) both;
++ animation: cfIn var(--ov-dur-3, .32s) var(--ease-standard, ease) both;
}
.cf-card:hover { border-color: var(--border-light); box-shadow: var(--elev-2); }
.cf-card--ai { border-color: color-mix(in srgb, var(--cf-ai) 30%, var(--border)); }
@@@ -170,7 +170,7 @@
radial-gradient(90% 80% at 90% 100%, color-mix(in srgb, var(--cf-ai-2) 20%, transparent), transparent 60%),
var(--bg-secondary);
}
--.cf-media img.cf-thumb { transition: transform .5s var(--ease-standard, ease); }
++.cf-media img.cf-thumb { transition: transform var(--ov-dur-4, .5s) var(--ease-standard, ease); }
a.cf-media:hover img.cf-thumb { transform: scale(1.03); }
.cf-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; background: #000; display: block; cursor: pointer; }
@@@ -449,7 +449,7 @@ a.cf-recap-band:hover .cf-recap-sub { text-decoration: underline; text-underline
inset: 0;
background: linear-gradient(100deg, transparent 20%, color-mix(in srgb, var(--text-primary) 7%, transparent) 50%, transparent 80%);
transform: translateX(-100%);
-- animation: cfSweep 1.5s ease-in-out infinite;
++ animation: cfSweep 1.5s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes cfSweep { to { transform: translateX(100%); } }
ddiff --git a/public/css/features/dashboard.css b/public/css/features/dashboard.css
iindex c81081e5..d692dfee 100644
---- a/public/css/features/dashboard.css
++++ b/public/css/features/dashboard.css
@@@ -3,35 +3,35 @@
Split by class/id ownership with cascade-order checks; edit freely. */
/* ── from style.css ── */
.vod-grid .sel-card-wrap { display: inline-block; vertical-align: top; }
--#dash-powerchat-card { transition: box-shadow .3s ease, border-color .3s ease; }
++#dash-powerchat-card { transition: box-shadow var(--ov-dur-3, .3s) var(--ov-ease-out, ease), border-color var(--ov-dur-3, .3s) var(--ov-ease-out, ease); }
.pc-status { font-size: 0.92rem; display: flex; align-items: center; gap: 8px; min-height: 22px; }
.pc-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; display: inline-block; }
--.pc-dot-on { background: #53fc18; box-shadow: 0 0 6px rgba(83,252,24,0.6); animation: pcDotPulse 2.2s ease-in-out infinite; }
++.pc-dot-on { background: #53fc18; box-shadow: 0 0 6px rgba(83,252,24,0.6); animation: pcDotPulse 2.2s var(--ov-ease-in-out, ease-in-out) infinite; }
@media (prefers-reduced-motion: reduce) { .pc-dot-on { animation: none; } }
.pc-kind { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; padding: 1px 7px; border-radius: 6px; margin-left: 4px; vertical-align: middle; }
.pc-kind-app { background: rgba(83,252,24,0.16); color: #6ee34a; }
.pc-kind-test { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent-light, #e1b77d); }
.pc-dot-off { background: var(--text-muted, #888); }
.pc-warn { color: #f0a742; }
--.pc-fade-in { animation: pcFadeIn .22s ease-out; }
--.pc-card-in { animation: pcCardIn .4s cubic-bezier(.34,1.4,.64,1); }
++.pc-fade-in { animation: pcFadeIn var(--ov-dur-2, .22s) var(--ov-ease-out, ease-out); }
++.pc-card-in { animation: pcCardIn var(--ov-dur-3, .4s) var(--ov-ease-spring, cubic-bezier(.34,1.4,.64,1)); }
#dash-powerchat-card.pc-busy { border-color: color-mix(in srgb, var(--accent) 50%, transparent); box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent), 0 6px 24px color-mix(in srgb, var(--accent) 12%, transparent); }
--.pc-spinner { width: 15px; height: 15px; border-radius: 50%; flex-shrink: 0; border: 2px solid var(--accent); display: inline-block; animation: pcSpin 1.2s ease-in-out infinite; }
++.pc-spinner { width: 15px; height: 15px; border-radius: 50%; flex-shrink: 0; border: 2px solid var(--accent); display: inline-block; animation: pcSpin 1.2s var(--ov-ease-in-out, ease-in-out) infinite; }
.pc-ellipsis span { animation: pcBlink 1.2s infinite both; }
.pc-ellipsis span:nth-child(2) { animation-delay: .2s; }
.pc-ellipsis span:nth-child(3) { animation-delay: .4s; }
.pc-progress { display: inline-block; vertical-align: middle; width: 90px; height: 4px; border-radius: 3px; background: color-mix(in srgb, var(--accent) 15%, transparent); overflow: hidden; margin-right: 8px; }
--.pc-progress-bar { display: block; height: 100%; width: 40%; border-radius: 3px; background: var(--accent, var(--accent)); animation: pcSlide 1.1s ease-in-out infinite; }
--.pc-check-pop { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%; background: #53fc18; color: #05270a; font-weight: 800; flex-shrink: 0; animation: pcPop .45s cubic-bezier(.34,1.7,.5,1) both; }
++.pc-progress-bar { display: block; height: 100%; width: 40%; border-radius: 3px; background: var(--accent, var(--accent)); animation: pcSlide 1.1s var(--ov-ease-in-out, ease-in-out) infinite; }
++.pc-check-pop { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%; background: #53fc18; color: #05270a; font-weight: 800; flex-shrink: 0; animation: pcPop var(--ov-dur-4, .45s) var(--ov-ease-spring, cubic-bezier(.34,1.7,.5,1)) both; }
.pc-success-text { color: #53fc18; font-weight: 600; }
--#dash-powerchat-card.pc-flash { animation: pcFlash 1s ease-out; }
++#dash-powerchat-card.pc-flash { animation: pcFlash 1s var(--ov-ease-out, ease-out); }
.pc-disconnect-btn { opacity: .85; }
#pc-actions { display: flex; flex-direction: column; gap: 12px; margin-top: 12px; }
.pc-action-group { display: flex; flex-direction: column; gap: 6px; }
.pc-action-label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted, #888); display: flex; align-items: center; gap: 6px; }
.pc-btn-row { display: flex; gap: 8px; flex-wrap: wrap; }
.pc-btn-row .btn { flex: 0 1 auto; }
--.pc-test-caret { font-size: 0.7em; margin-left: 2px; transition: transform .18s ease; }
++.pc-test-caret { font-size: 0.7em; margin-left: 2px; transition: transform var(--ov-dur-2, .18s) var(--ov-ease-out, ease); }
.pc-test-menu-wrap.open .pc-test-caret { transform: rotate(180deg); }
.pc-test-menu[hidden] { display: none; }
.pc-test-menu-head {
@@@ -50,7 +50,7 @@
.pc-copy-btn {
background: rgba(255,255,255,0.06); border: 1px solid var(--border); color: var(--text-secondary);
border-radius: 6px; padding: 3px 9px; font-size: 0.78rem; cursor: pointer; display: inline-flex; align-items: center; gap: 5px;
-- transition: background .15s, border-color .15s;
++ transition: background var(--ov-dur-2, .15s), border-color var(--ov-dur-2, .15s);
}
.pc-copy-btn:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--text); }
.pc-cancel-btn { opacity: .85; }
@@@ -71,7 +71,7 @@
color: var(--accent-light, #e1b77d);
background: color-mix(in srgb, var(--accent) 15%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
-- transition: background .15s, transform .1s;
++ transition: background var(--ov-dur-2, .15s), transform var(--ov-dur-1, .1s);
}
.dash-bucks-badge:hover { background: color-mix(in srgb, var(--accent) 26%, transparent); }
.dash-bucks-badge:active { transform: scale(0.96); }
@@@ -231,7 +231,7 @@
border-radius: var(--radius);
overflow: hidden;
background: var(--bg-card);
-- transition: border-color 0.2s, transform 0.15s;
++ transition: border-color var(--ov-dur-2, 0.2s), transform var(--ov-dur-2, 0.15s);
}
.theme-dir-card:hover {
border-color: var(--accent);
@@@ -419,7 +419,7 @@
color: var(--text-secondary);
font-size: 0.9rem;
cursor: pointer;
-- transition: all 0.2s;
++ transition: all var(--ov-dur-2, 0.2s);
white-space: nowrap;
}
ddiff --git a/public/css/features/docs.css b/public/css/features/docs.css
iindex e085e177..8d13d6a1 100644
---- a/public/css/features/docs.css
++++ b/public/css/features/docs.css
@@@ -11,13 +11,13 @@
.doc-table { display: block; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }
}
.docs-tab-bar::-webkit-scrollbar { display: none; }
--.docs-tab-arrow { flex: 0 0 auto; display: none; align-items: center; justify-content: center; width: 30px; background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 0.8rem; transition: color 0.15s; }
++.docs-tab-arrow { flex: 0 0 auto; display: none; align-items: center; justify-content: center; width: 30px; background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 0.8rem; transition: color var(--ov-dur-2, 0.15s); }
.docs-tab-arrow:hover { color: var(--accent); }
.docs-tab-wrap.can-scroll-left #docs-tab-arrow-left { display: flex; }
.docs-tab-wrap.can-scroll-right #docs-tab-arrow-right { display: flex; }
--.docs-tab-wrap::before, .docs-tab-wrap::after { content: ''; position: absolute; top: 0; bottom: 0; width: 28px; pointer-events: none; opacity: 0; transition: opacity 0.15s; z-index: 1; }
++.docs-tab-wrap::before, .docs-tab-wrap::after { content: ''; position: absolute; top: 0; bottom: 0; width: 28px; pointer-events: none; opacity: 0; transition: opacity var(--ov-dur-2, 0.15s); z-index: 1; }
.docs-tab-wrap::before { left: 30px; background: linear-gradient(to right, var(--bg-primary, #0f1420), transparent); }
.docs-tab-wrap::after { right: 30px; background: linear-gradient(to left, var(--bg-primary, #0f1420), transparent); }
.docs-tab-wrap.can-scroll-left::before { opacity: 1; }
.docs-tab-wrap.can-scroll-right::after { opacity: 1; }
--.docs-tab { background: none; border: none; color: var(--text-muted); padding: 0.6rem 1rem; font-size: 0.85rem; font-weight: 500; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -2px; white-space: nowrap; transition: color 0.15s, border-color 0.15s; }
++.docs-tab { background: none; border: none; color: var(--text-muted); padding: 0.6rem 1rem; font-size: 0.85rem; font-weight: 500; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -2px; white-space: nowrap; transition: color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s); }
ddiff --git a/public/css/features/featured.css b/public/css/features/featured.css
iindex 15b5266e..40bf8bb1 100644
---- a/public/css/features/featured.css
++++ b/public/css/features/featured.css
@@@ -7,28 +7,28 @@
background: color-mix(in srgb, var(--bg-card) 92%, transparent);
border: 1px solid color-mix(in srgb, #ef4444 26%, var(--border));
box-shadow: 0 24px 60px -30px rgba(0, 0, 0, 0.8);
-- animation: hfIn 320ms cubic-bezier(.2, .9, .3, 1);
++ animation: hfIn var(--ov-dur-3, 320ms) var(--ov-ease-out, cubic-bezier(.2, .9, .3, 1));
}
@keyframes hfIn { from { opacity: 0; transform: translateY(8px); } }
.hf-media { position: relative; aspect-ratio: 16 / 9; background: #000; overflow: hidden; }
.hf-media video, .hf-media canvas, .hf-media img.hf-frame { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: #000; }
--.hf-media img.hf-frame { object-fit: cover; transition: opacity 0.5s; }
++.hf-media img.hf-frame { object-fit: cover; transition: opacity var(--ov-dur-4, 0.5s); }
.hf-media.is-switching video, .hf-media.is-switching canvas, .hf-media.is-switching img.hf-frame { opacity: 0; }
--.hf-media video, .hf-media canvas { transition: opacity 0.4s; }
++.hf-media video, .hf-media canvas { transition: opacity var(--ov-dur-3, 0.4s); }
.hf-poster { position: absolute; inset: 0; background-size: cover; background-position: center; filter: blur(14px) saturate(1.2); transform: scale(1.1); opacity: 0.55; }
.hf-live {
position: absolute; top: 12px; left: 12px; z-index: 2; display: inline-flex; align-items: center; gap: 6px;
padding: 4px 10px; border-radius: 999px; font-size: 0.7rem; font-weight: 800; letter-spacing: 0.08em; color: #fff;
background: #ef4444; box-shadow: 0 4px 14px rgba(239, 68, 68, 0.45);
}
--.hf-live i { font-size: 0.5rem; animation: hfBlink 1.6s ease-in-out infinite; }
++.hf-live i { font-size: 0.5rem; animation: hfBlink 1.6s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes hfBlink { 50% { opacity: 0.35; } }
.hf-viewers { position: absolute; top: 12px; right: 12px; z-index: 2; display: inline-flex; align-items: center; gap: 5px; padding: 4px 9px; border-radius: 999px; font-size: 0.72rem; color: #fff; background: rgba(0, 0, 0, 0.55); backdrop-filter: blur(6px); }
.hf-sound {
position: absolute; bottom: 12px; left: 12px; z-index: 2; display: inline-flex; align-items: center; gap: 7px;
padding: 7px 12px; border-radius: 999px; border: 1px solid rgba(255, 255, 255, 0.25); cursor: pointer;
background: rgba(0, 0, 0, 0.6); color: #fff; font: inherit; font-size: 0.78rem; font-weight: 600; backdrop-filter: blur(6px);
-- transition: background 0.15s, transform 0.15s;
++ transition: background var(--ov-dur-2, 0.15s), transform var(--ov-dur-2, 0.15s);
}
.hf-sound:hover { background: rgba(0, 0, 0, 0.8); transform: translateY(-1px); }
.hf-preview-note { position: absolute; bottom: 12px; right: 12px; z-index: 2; padding: 4px 9px; border-radius: 999px; font-size: 0.68rem; color: #ddd; background: rgba(0, 0, 0, 0.5); }
@@@ -49,7 +49,7 @@
.hf-now {
position: relative; padding: 10px 12px 10px 14px; border-radius: 12px; font-size: 0.84rem; line-height: 1.5; color: var(--text-secondary);
background: color-mix(in srgb, #22d3ee 7%, transparent); border: 1px solid color-mix(in srgb, #22d3ee 22%, transparent);
-- transition: opacity 0.3s;
++ transition: opacity var(--ov-dur-3, 0.3s);
}
.hf-now::before { content: ''; position: absolute; left: 0; top: 10px; bottom: 10px; width: 3px; border-radius: 3px; background: #22d3ee; }
.hf-now-head { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; font-size: 0.66rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: #22d3ee; }
@@@ -63,12 +63,12 @@
background: linear-gradient(160deg, color-mix(in srgb, var(--accent) 85%, #000), color-mix(in srgb, var(--accent) 60%, #000));
border: 1px solid color-mix(in srgb, var(--accent) 60%, transparent);
box-shadow: 0 8px 22px -10px color-mix(in srgb, var(--accent) 80%, transparent);
-- transition: transform 0.18s cubic-bezier(.2, 1.3, .3, 1), box-shadow 0.2s;
++ transition: transform var(--ov-dur-2, 0.18s) var(--ov-ease-spring, cubic-bezier(.2, 1.3, .3, 1)), box-shadow var(--ov-dur-2, 0.2s);
}
.hf-watch:hover { transform: translateY(-2px); box-shadow: 0 14px 30px -12px color-mix(in srgb, var(--accent) 90%, transparent); }
.hf-next {
display: inline-flex; align-items: center; gap: 6px; padding: 9px 12px; border-radius: 12px; cursor: pointer; font: inherit; font-size: 0.82rem; font-weight: 600;
-- color: var(--text-secondary); background: transparent; border: 1px solid var(--border); transition: color 0.15s, border-color 0.15s;
++ color: var(--text-secondary); background: transparent; border: 1px solid var(--border); transition: color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s);
}
.hf-next:hover { color: var(--text-primary); border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }
.hf-next small { color: var(--text-muted); font-weight: 500; }
ddiff --git a/public/css/features/home.css b/public/css/features/home.css
iindex 53aa34de..68cd7551 100644
---- a/public/css/features/home.css
++++ b/public/css/features/home.css
@@@ -28,22 +28,22 @@
.hero-cta { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
.hero-egg { max-width: 520px; margin: 0 auto 22px; border-radius: 16px; border: 1px solid color-mix(in srgb, var(--accent) 38%, var(--border)); background: var(--bg-card); box-shadow: 0 14px 40px rgba(0,0,0,0.3); text-align: left; overflow: hidden; }
.hero-egg-head { width: 100%; display: flex; align-items: center; gap: 10px; padding: 10px 14px; background: none; border: 0; cursor: pointer; color: var(--text-primary); font: inherit; }
--.hero-egg-icon { font-size: 1.3rem; filter: drop-shadow(0 2px 6px rgba(0,0,0,0.4)); animation: eggWobble 3.5s ease-in-out infinite; }
++.hero-egg-icon { font-size: 1.3rem; filter: drop-shadow(0 2px 6px rgba(0,0,0,0.4)); animation: eggWobble 3.5s var(--ov-ease-in-out, ease-in-out) infinite; }
.hero-egg-kicker { font-size: 0.62rem; font-weight: 800; letter-spacing: 0.09em; text-transform: uppercase; color: var(--accent-light, var(--accent)); }
.hero-egg-title { font-size: 0.95rem; font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; color: var(--text-primary); }
.hero-egg-status { font-size: 0.72rem; font-weight: 700; color: var(--text-secondary); background: var(--bg-tertiary); padding: 3px 9px; border-radius: 999px; white-space: nowrap; }
.hero-egg-status.solved { color: #06d6a0; background: color-mix(in srgb, #06d6a0 18%, transparent); }
--.hero-egg-chev { color: var(--text-muted); transition: transform .2s ease; }
++.hero-egg-chev { color: var(--text-muted); transition: transform var(--ov-dur-2, .2s) var(--ov-ease-out, ease); }
.hero-egg.open .hero-egg-chev { transform: rotate(180deg); }
--.hero-egg-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .35s ease; }
++.hero-egg-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--ov-dur-3, .35s) var(--ov-ease-out, ease); }
.hero-egg.open .hero-egg-body { grid-template-rows: 1fr; }
.hero-egg-inner { min-height: 0; overflow: hidden; padding: 0 14px; }
.hero-egg.open .hero-egg-inner { padding: 0 14px 14px; }
.hero-egg-intro { font-size: 0.78rem; color: var(--text-secondary); margin: 0 0 10px; line-height: 1.45; }
.hero-egg-slots { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 12px; }
--.egg-slot { width: 38px; height: 42px; border-radius: 10px; display: grid; place-items: center; font-size: 1.15rem; font-weight: 900; color: var(--text-primary); background: var(--bg-tertiary); border: 1px solid var(--border); transition: transform .15s, border-color .15s, background .15s; }
++.egg-slot { width: 38px; height: 42px; border-radius: 10px; display: grid; place-items: center; font-size: 1.15rem; font-weight: 900; color: var(--text-primary); background: var(--bg-tertiary); border: 1px solid var(--border); transition: transform var(--ov-dur-2, .15s), border-color var(--ov-dur-2, .15s), background var(--ov-dur-2, .15s); }
.egg-slot.is-next { border-style: dashed; border-color: color-mix(in srgb, var(--accent) 60%, transparent); }
--.hero-egg-slots.is-wrong .egg-slot { border-color: #ff5a5f; background: color-mix(in srgb, #ff5a5f 18%, var(--bg-tertiary)); animation: eggShakeSlot .45s; }
++.hero-egg-slots.is-wrong .egg-slot { border-color: #ff5a5f; background: color-mix(in srgb, #ff5a5f 18%, var(--bg-tertiary)); animation: eggShakeSlot var(--ov-dur-4, .45s); }
.hero-egg-slots.is-right .egg-slot, .hero-egg-slots.is-solved .egg-slot { border-color: #06d6a0; background: color-mix(in srgb, #06d6a0 20%, var(--bg-tertiary)); }
.hero-egg-clues { margin: 0 0 12px; padding: 0; list-style: none; display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; counter-reset: clue; }
.hero-egg-clues li { display: grid; grid-template-columns: 22px 1fr auto; align-items: center; gap: 8px; font-size: 0.84rem; color: var(--text-primary); padding: 6px 8px; border-radius: 10px; background: var(--bg-secondary); border: 1px solid var(--border); }
@@@ -64,7 +64,7 @@
.hero-egg-reveal:not(:disabled):hover { border-color: #fbbf24; }
.hero-egg-meta { font-size: 0.72rem; color: var(--text-muted); }
@media (max-width: 480px) { .hero-egg-pad-letters { grid-template-columns: repeat(9, 1fr); } .egg-slot { width: 32px; height: 38px; } }
--body.egg-shake { animation: eggShake 0.9s cubic-bezier(.36,.07,.19,.97) both; }
++body.egg-shake { animation: eggShake 0.9s var(--ov-ease-out, cubic-bezier(.36,.07,.19,.97)) both; }
body.egg-rainbow { animation: eggRainbow 2.6s linear; }
@media (prefers-reduced-motion: reduce) { .egg-confetti, body.egg-shake, body.egg-rainbow { animation: none !important; } .egg-confetti { display: none; } }
@@@ -92,7 +92,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
background: var(--bg-card); border: 1px solid var(--border);
border-radius: 12px; padding: 14px 16px;
text-decoration: none; color: var(--text-primary);
-- transition: all 0.2s ease;
++ transition: all var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease);
}
.network-card-featured {
border-color: rgba(231,76,60,0.25);
@@@ -108,7 +108,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
}
.network-card-arrow {
color: var(--text-secondary); font-size: 0.75rem;
-- opacity: 0; transition: opacity 0.2s, transform 0.2s;
++ opacity: 0; transition: opacity var(--ov-dur-2, 0.2s), transform var(--ov-dur-2, 0.2s);
flex-shrink: 0;
}
.network-card:hover .network-card-arrow { opacity: 1; transform: translateX(3px); }
@@@ -164,7 +164,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-soft);
-- transition: border-color 0.2s, box-shadow 0.2s;
++ transition: border-color var(--ov-dur-2, 0.2s), box-shadow var(--ov-dur-2, 0.2s);
}
.streamer-group-card:hover { border-color: var(--accent-dark); box-shadow: var(--ring-accent); }
.streamer-group-header { padding: 12px 14px 0; }
@@@ -179,13 +179,13 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
display: flex; align-items: center; gap: 10px;
padding: 7px 14px; text-decoration: none; color: inherit;
border-top: 1px solid var(--border);
-- transition: background 0.15s;
++ transition: background var(--ov-dur-2, 0.15s);
}
.streamer-group-stream:hover { background: var(--bg-hover); }
.streamer-group-stream-thumb-img { width: 100%; height: 100%; object-fit: cover; }
.streamer-group-stream-thumb-placeholder { color: var(--text-muted); font-size: 1rem; }
.sg-live { color: #ef4444; font-weight: 700; }
--.sg-live-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #ef4444; margin-right: 2px; vertical-align: middle; animation: sg-pulse 1.6s ease-in-out infinite; }
++.sg-live-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #ef4444; margin-right: 2px; vertical-align: middle; animation: sg-pulse 1.6s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes sg-pulse { 50% { opacity: .35; } }
.streamer-group-stream.is-live .streamer-group-stream-thumb { box-shadow: 0 0 0 2px #ef4444; border-radius: 8px; }
@media (prefers-reduced-motion: reduce) { .sg-live-dot { animation: none; } }
@@@ -195,7 +195,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
display: inline-block;
opacity: 0;
transform: translateY(6px);
-- transition: opacity 0.35s ease, transform 0.35s ease;
++ transition: opacity var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease), transform var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease);
color: var(--accent);
font-weight: 650;
white-space: nowrap;
@@@ -214,14 +214,14 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
box-shadow: 0 26px 60px -10px rgba(0, 0, 0, 0.85), 0 0 0 1px var(--accent) inset;
}
@media (prefers-reduced-motion: reduce) {
-- .hero-bg-layer { transition: opacity 0.6s ease; }
++ .hero-bg-layer { transition: opacity var(--ov-dur-4, 0.6s) var(--ov-ease-out, ease); }
}
.hero-quip {
height: 2.8em; margin: 2px auto 18px; max-width: 640px; overflow: hidden;
display: grid; place-items: center; text-align: center; text-wrap: balance;
font-size: clamp(0.98rem, 1.9vw, 1.16rem);
font-style: italic; color: var(--accent-light, var(--accent-light)); opacity: 0;
-- transform: translateY(5px); transition: opacity 0.5s ease, transform 0.5s ease;
++ transform: translateY(5px); transition: opacity var(--ov-dur-4, 0.5s) var(--ov-ease-out, ease), transform var(--ov-dur-4, 0.5s) var(--ov-ease-out, ease);
text-shadow: 0 1px 12px rgba(0, 0, 0, 0.55); line-height: 1.35;
}
.hero-philosophy-links { margin-top: 24px; }
@@@ -253,7 +253,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
.pulse-card {
display: block; padding: 13px 15px; border-radius: 14px; text-decoration: none; color: var(--text-primary);
background: var(--bg-secondary); border: 1px solid var(--border);
-- transition: border-color 0.15s ease, transform 0.15s ease;
++ transition: border-color var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease), transform var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
.pulse-kicker { font-size: 0.68rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); margin-bottom: 7px; }
.pulse-title { font-weight: 700; margin-bottom: 7px; }
@@@ -301,7 +301,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
.hero-float { border-radius: 10px; }
}
@media (prefers-reduced-motion: reduce) {
-- .hero-float { animation: heroFloatIn 0.01s forwards; opacity: 0.5; }
++ .hero-float { animation: heroFloatIn var(--ov-dur-1, 0.01s) forwards; opacity: 0.5; }
.hero-float::after { animation: none; display: none; }
.hero-collage { transition: none; transform: none; }
.hero-quip { transition: none; }
@@@ -347,7 +347,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
}
.cta-diff-label { margin-top: 30px; }
.cta-methods { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; text-align: left; }
--.cta-method::after { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 2px; background: linear-gradient(90deg, transparent, var(--accent), transparent); opacity: 0; transition: opacity .2s; }
++.cta-method::after { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 2px; background: linear-gradient(90deg, transparent, var(--accent), transparent); opacity: 0; transition: opacity var(--ov-dur-2, .2s); }
.cta-method:hover { transform: translateY(-4px); border-color: color-mix(in srgb, var(--accent) 40%, transparent); box-shadow: 0 14px 34px rgba(0,0,0,0.4); }
.cta-method:hover::after { opacity: 1; }
.cta-method-icon { width: 42px; height: 42px; border-radius: 11px; display: flex; align-items: center; justify-content: center; font-size: 1.15rem; color: #0e0e12; background: linear-gradient(135deg, var(--accent-light), var(--accent-dark)); margin-bottom: 10px; box-shadow: 0 6px 16px color-mix(in srgb, var(--accent-dark) 35%, transparent); }
@@@ -365,7 +365,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
at the top of the pulse instead of a glow. */
content: ''; position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none;
box-shadow: 0 10px 34px color-mix(in srgb, var(--accent-dark) 50%, transparent), 0 0 30px color-mix(in srgb, var(--accent) 28%, transparent);
-- opacity: 0; animation: ctaBtnGlow 2.6s ease-in-out infinite;
++ opacity: 0; animation: ctaBtnGlow 2.6s var(--ov-ease-in-out, ease-in-out) infinite;
}
@media (max-width: 860px) {
@@@ -392,7 +392,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
padding: 0;
background: var(--bg-card); border: 1px solid var(--border);
border-radius: var(--radius); text-decoration: none; color: var(--text-primary);
-- transition: border-color 0.2s, background 0.2s, transform 0.2s, box-shadow 0.2s;
++ transition: border-color var(--ov-dur-2, 0.2s), background var(--ov-dur-2, 0.2s), transform var(--ov-dur-2, 0.2s), box-shadow var(--ov-dur-2, 0.2s);
overflow: hidden;
}
.home-paste-card:hover {
@@@ -448,8 +448,8 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
.stream-card-meta { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.stream-card-metaright { margin-left: auto; display: inline-flex; align-items: center; gap: 12px; color: var(--text-muted); font-size: 0.75rem; }
.stream-card-uptime, .stream-card-vcount { display: inline-flex; align-items: center; white-space: nowrap; }
--.stream-card-appear { animation: streamCardAppear 0.6s ease; }
--.stream-card-leave { opacity: 0; transform: scale(0.94); transition: opacity 0.4s ease, transform 0.4s ease; pointer-events: none; }
++.stream-card-appear { animation: streamCardAppear var(--ov-dur-4, 0.6s) var(--ov-ease-out, ease); }
++.stream-card-leave { opacity: 0; transform: scale(0.94); transition: opacity var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease), transform var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease); pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
.stream-card-appear { animation: none; }
.stream-card-leave { transition: none; }
@@@ -471,7 +471,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
.stream-card-desc {
font-size: 0.8rem; color: var(--text-secondary); margin-top: 5px;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
-- line-height: 1.35; max-height: 2.85em; transition: max-height 0.35s ease; cursor: pointer;
++ line-height: 1.35; max-height: 2.85em; transition: max-height var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease); cursor: pointer;
}
.stream-card-desc.expanded { -webkit-line-clamp: unset; max-height: 4000px; overflow-y: auto; }
.hero-stat--clickable { cursor: pointer; }
@@@ -521,7 +521,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
background: color-mix(in srgb, var(--bg-secondary) 78%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent); backdrop-filter: blur(8px);
box-shadow: 0 6px 20px -12px rgba(0, 0, 0, 0.7);
-- transition: color 0.2s, background 0.2s, border-color 0.2s, transform 0.2s;
++ transition: color var(--ov-dur-2, 0.2s), background var(--ov-dur-2, 0.2s), border-color var(--ov-dur-2, 0.2s), transform var(--ov-dur-2, 0.2s);
}
.hero-stat-more:hover { border-color: color-mix(in srgb, var(--accent) 55%, transparent); transform: translateY(-1px); }
/* The stats are something to be proud of — the toggle reads as an invitation, not a footnote. */
@@@ -529,7 +529,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
display: inline-grid; place-items: center; width: 22px; height: 22px; border-radius: 7px;
color: #fff; background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 55%, #22d3ee));
box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent);
-- transition: box-shadow 0.3s, transform 0.3s cubic-bezier(.2, 1, .3, 1);
++ transition: box-shadow var(--ov-dur-3, 0.3s), transform var(--ov-dur-3, 0.3s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1));
}
.hero-stat-more-icon i { font-size: 0.7rem; }
.hero-stat-more-text { background: linear-gradient(90deg, var(--text-primary), color-mix(in srgb, var(--accent) 60%, var(--text-primary))); -webkit-background-clip: text; background-clip: text; color: transparent; }
@@@ -610,7 +610,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
background: color-mix(in srgb, var(--accent) 9%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
border-radius: 14px;
-- transition: transform 0.18s cubic-bezier(.2, 1, .3, 1), background 0.2s, border-color 0.2s, box-shadow 0.22s;
++ transition: transform var(--ov-dur-2, 0.18s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1)), background var(--ov-dur-2, 0.2s), border-color var(--ov-dur-2, 0.2s), box-shadow var(--ov-dur-2, 0.22s);
}
.home-load-more:hover {
transform: translateY(-1px);
@@@ -654,7 +654,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
.hero-stats.is-swapping {
overflow: hidden;
min-height: 0 !important;
-- transition: height 0.42s cubic-bezier(.2, 1, .3, 1);
++ transition: height var(--ov-dur-3, 0.42s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1));
}
.hero-stat > i { grid-row: 1 / 2; align-self: center; justify-self: center; }
.hero-stat-num { white-space: nowrap; line-height: 1.12; }
@@@ -712,12 +712,12 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
.ov-user {
font-weight: 800; color: var(--text-primary); cursor: pointer;
border-radius: 4px; text-decoration: none;
-- text-underline-offset: 2px; transition: color .15s;
++ text-underline-offset: 2px; transition: color var(--ov-dur-2, .15s);
}
.ov-user:hover { color: var(--accent); text-decoration: underline; text-decoration-thickness: 1.5px; }
.ov-user:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ov-enter {
-- animation: ovEnter 0.46s cubic-bezier(.22, 1, .36, 1) both;
++ animation: ovEnter var(--ov-dur-4, 0.46s) var(--ov-ease-out, cubic-bezier(.22, 1, .36, 1)) both;
animation-delay: calc(var(--ov-enter-i, 0) * 45ms);
will-change: transform, opacity;
}
@@@ -728,17 +728,17 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
.cta-method > span { display: block; }
.cta-method-tag { display: block; width: fit-content; margin-top: auto; padding-top: 3px; }
.cta-method > span + .cta-method-tag { margin-top: 12px; }
--.cta-method-icon { transition: transform .25s cubic-bezier(.2, 1.3, .3, 1), box-shadow .25s ease; }
++.cta-method-icon { transition: transform var(--ov-dur-2, .25s) var(--ov-ease-spring, cubic-bezier(.2, 1.3, .3, 1)), box-shadow var(--ov-dur-2, .25s) var(--ov-ease-out, ease); }
.cta-method:hover .cta-method-icon { transform: rotate(-6deg) scale(1.08); box-shadow: 0 10px 24px color-mix(in srgb, var(--accent-dark) 55%, transparent); }
.cta-method:hover .cta-method-num { color: color-mix(in srgb, var(--accent) 32%, transparent); }
--.cta-method-num { transition: color .2s ease; }
++.cta-method-num { transition: color var(--ov-dur-2, .2s) var(--ov-ease-out, ease); }
.home-cta-feature > i:first-child {
width: 34px; height: 34px; flex: none; border-radius: 10px;
display: grid; place-items: center; font-size: 0.92rem; margin-bottom: 6px;
color: var(--accent);
background: color-mix(in srgb, var(--accent) 13%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
-- transition: transform .25s cubic-bezier(.2, 1.3, .3, 1), background .2s ease;
++ transition: transform var(--ov-dur-2, .25s) var(--ov-ease-spring, cubic-bezier(.2, 1.3, .3, 1)), background var(--ov-dur-2, .2s) var(--ov-ease-out, ease);
}
.home-cta-feature:hover > i:first-child { transform: scale(1.1) rotate(-5deg); background: color-mix(in srgb, var(--accent) 22%, transparent); }
.cta-method::before, .home-cta-feature::after {
@@@ -747,7 +747,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
transform: translateX(-100%); opacity: 0;
}
.home-cta-feature { overflow: hidden; }
--.cta-method:hover::before, .home-cta-feature:hover::after { opacity: 1; animation: ctaCardSheen .8s ease-out; }
++.cta-method:hover::before, .home-cta-feature:hover::after { opacity: 1; animation: ctaCardSheen var(--ov-dur-4, .8s) var(--ov-ease-out, ease-out); }
.cta-money-lede {
max-width: 700px; margin: 0 0 14px !important; text-align: left;
padding: 2px 0 2px 14px; border-left: 3px solid color-mix(in srgb, #4ade80 55%, transparent);
@@@ -765,7 +765,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
.home-cta-banner.about-opening .cta-section-label,
.home-cta-banner.about-opening .home-cta-buttons,
.home-cta-banner.about-opening .cta-money-lede {
-- animation: ctaRise .5s cubic-bezier(.22, 1, .36, 1) both;
++ animation: ctaRise var(--ov-dur-4, .5s) var(--ov-ease-out, cubic-bezier(.22, 1, .36, 1)) both;
animation-delay: calc(var(--about-i, 0) * 38ms);
}
@@@ -910,16 +910,16 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
.star-more[hidden] { display: none !important; }
.star-more { display: block; grid-column: 1 / -1; }
.star-card > .star-actions, .star-card > .star-more { grid-column: 1 / -1; }
--.star-more.is-entering { animation: starMoreIn 0.42s cubic-bezier(.2, 1, .3, 1); }
--.star-card.is-swapping { overflow: hidden; transition: height 0.42s cubic-bezier(.2, 1, .3, 1); }
++.star-more.is-entering { animation: starMoreIn var(--ov-dur-3, 0.42s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1)); }
++.star-card.is-swapping { overflow: hidden; transition: height var(--ov-dur-3, 0.42s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1)); }
.star-card.is-compact .star-avatar-ring {
inset: -3px; border-radius: 50%;
background: conic-gradient(from 0deg, var(--star-gold), #f472b6, var(--accent), var(--star-gold));
-- animation: starRingSpin 6s ease-in-out infinite;
++ animation: starRingSpin 6s var(--ov-ease-in-out, ease-in-out) infinite;
opacity: 0.9;
}
.star-card.is-compact:hover .star-avatar-ring { opacity: 1; }
--.star-card.is-compact .star-avatar { transition: transform 0.28s cubic-bezier(.2, 1.2, .3, 1); }
++.star-card.is-compact .star-avatar { transition: transform var(--ov-dur-3, 0.28s) var(--ov-ease-spring, cubic-bezier(.2, 1.2, .3, 1)); }
.star-card.is-compact:hover .star-avatar { transform: scale(1.04); }
/* ── Action row: one family ──────────────────────────────────────────────────
@@@ -937,9 +937,9 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
color: var(--text-primary);
background: color-mix(in srgb, var(--text-primary) 4%, transparent);
border: 1px solid color-mix(in srgb, var(--text-primary) 16%, transparent);
-- transition: background 0.18s, border-color 0.18s, box-shadow 0.2s, translate 0.2s cubic-bezier(.2, 1, .3, 1), scale 0.12s;
++ transition: background var(--ov-dur-2, 0.18s), border-color var(--ov-dur-2, 0.18s), box-shadow var(--ov-dur-2, 0.2s), translate var(--ov-dur-2, 0.2s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1)), scale var(--ov-dur-1, 0.12s);
}
--.star-act > i { flex: none; font-size: 0.92em; color: var(--star-ink); transition: rotate 0.3s cubic-bezier(.2, 1, .3, 1); }
++.star-act > i { flex: none; font-size: 0.92em; color: var(--star-ink); transition: rotate var(--ov-dur-3, 0.3s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1)); }
.star-act > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.star-act:hover {
color: var(--text-primary); translate: 0 -1px;
@@@ -1139,7 +1139,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
color: color-mix(in srgb, var(--sc-on) 50%, var(--text-primary));
background: color-mix(in srgb, var(--sc-on) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--sc-on) 28%, transparent);
-- transition: background 0.15s, border-color 0.15s, color 0.15s, scale 0.12s;
++ transition: background var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s), color var(--ov-dur-2, 0.15s), scale var(--ov-dur-1, 0.12s);
}
.section-header > .section-action:hover {
color: var(--text-primary);
@@@ -1157,7 +1157,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
/* The changelog's "show all" lives under the list it expands. */
.home-changelog-toggle { margin: 12px auto 0; }
--.home-changelog-toggle > i { transition: rotate 0.3s cubic-bezier(.2, 1, .3, 1); }
++.home-changelog-toggle > i { transition: rotate var(--ov-dur-3, 0.3s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1)); }
.home-changelog-toggle[aria-expanded="true"] > i { rotate: 180deg; }
/* Network hub two-up on a phone: the icon beside the text left ~90px, and "OpenVibe.Community"
@@@ -1177,7 +1177,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
what you do not need, on one line each. No card inside the banner (it read as a box in a box on phones). */
.cta-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin: 20px 0 12px; }
.cta-btn { display: inline-flex; align-items: center; justify-content: center; gap: 9px; min-height: 48px; padding: 0 22px;
-- border-radius: 999px; font-weight: 800; font-size: 1rem; text-decoration: none; transition: filter .15s, background .15s, transform .1s; }
++ border-radius: 999px; font-weight: 800; font-size: 1rem; text-decoration: none; transition: filter var(--ov-dur-2, .15s), background var(--ov-dur-2, .15s), transform var(--ov-dur-1, .1s); }
.cta-btn:active { transform: translateY(1px); }
.cta-btn-primary { background: var(--accent-strong, var(--accent, #3472d8)); color: var(--on-accent-strong, var(--on-accent, #fff));
box-shadow: 0 10px 28px color-mix(in srgb, var(--accent, #3b82f6) 35%, transparent); }
@@@ -1230,10 +1230,10 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
border-radius: 999px; text-decoration: none; font-size: 0.8rem; font-weight: 700; text-align: center;
color: var(--text-primary); background: color-mix(in srgb, var(--accent) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
-- transition: border-color 0.2s, background 0.2s, transform 0.18s;
++ transition: border-color var(--ov-dur-2, 0.2s), background var(--ov-dur-2, 0.2s), transform var(--ov-dur-2, 0.18s);
}
.hero-stat-all-sites > i:first-child { color: var(--accent); }
--.hero-stat-all-sites > i:last-child { font-size: 0.7rem; color: var(--text-muted); transition: transform 0.18s; }
++.hero-stat-all-sites > i:last-child { font-size: 0.7rem; color: var(--text-muted); transition: transform var(--ov-dur-2, 0.18s); }
.hero-stat-all-sites:hover, .hero-stat-all-sites:focus-visible { border-color: color-mix(in srgb, var(--accent) 60%, transparent); background: color-mix(in srgb, var(--accent) 16%, transparent); outline: none; }
.hero-stat-all-sites:hover > i:last-child { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) { .hero-stat-all-sites, .hero-stat-all-sites > i { transition: none; } }
@@@ -1248,7 +1248,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
.sg-play i {
display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%; padding-left: 2px; box-sizing: border-box;
font-size: 0.72rem; color: #fff; background: rgba(0, 0, 0, 0.55); border: 1px solid rgba(255, 255, 255, 0.35);
-- transition: transform 0.15s, background 0.15s;
++ transition: transform var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
}
.streamer-group-stream.has-replay:hover .sg-play i, .streamer-group-stream.has-replay:focus-visible .sg-play i { transform: scale(1.08); background: var(--accent); border-color: transparent; }
.sg-replay { color: var(--accent-light, var(--accent)); font-weight: 700; }
@@@ -1286,7 +1286,7 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
font: inherit; font-size: 0.86rem; font-weight: 750; white-space: nowrap; cursor: pointer;
color: var(--text-secondary); background: transparent;
border: 1px solid var(--border); border-radius: 999px;
-- transition: color 0.18s, background 0.18s, border-color 0.18s;
++ transition: color var(--ov-dur-2, 0.18s), background var(--ov-dur-2, 0.18s), border-color var(--ov-dur-2, 0.18s);
}
.home-tabs [role="tab"][hidden] { display: none; }
.home-tabs [role="tab"] i { font-size: 0.78rem; color: var(--sc-on); }
ddiff --git a/public/css/features/rundown.css b/public/css/features/rundown.css
iindex ae33f863..3c27e07e 100644
---- a/public/css/features/rundown.css
++++ b/public/css/features/rundown.css
@@@ -235,7 +235,7 @@ a.rd-use:hover b { color: var(--accent); }
.rd-more { margin-top: 14px; border-radius: 14px; border: 1px solid var(--border, rgba(255,255,255,.1)); }
.rd-more > summary { padding: 14px 16px; cursor: pointer; font-weight: 700; list-style: none; display: flex; align-items: center; gap: 10px; }
.rd-more > summary::-webkit-details-marker { display: none; }
--.rd-more > summary::before { content: ''; width: 8px; height: 8px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: translateY(-2px) rotate(-45deg); transition: transform .18s ease; }
++.rd-more > summary::before { content: ''; width: 8px; height: 8px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: translateY(-2px) rotate(-45deg); transition: transform var(--ov-dur-2, .18s) var(--ov-ease-out, ease); }
.rd-more[open] > summary::before { transform: translateY(-4px) rotate(45deg); }
.rd-more > .rd-topic-grid { padding: 4px 12px 14px; }
@media (prefers-reduced-motion: reduce) { .rd-more > summary::before { transition: none; } }
ddiff --git a/public/css/features/stats.css b/public/css/features/stats.css
iindex d4382db4..c5f7480d 100644
---- a/public/css/features/stats.css
++++ b/public/css/features/stats.css
@@@ -3,14 +3,14 @@
.sn-overlay {
position: fixed; inset: 0; z-index: 10050; /* above the chat/DM bubbles (9999) */ display: flex; align-items: center; justify-content: center;
padding: 16px; background: rgba(6, 6, 10, 0.72); backdrop-filter: blur(6px);
-- animation: snFade var(--dur-fast, 160ms) ease-out;
++ animation: snFade var(--dur-fast, 160ms) var(--ov-ease-out, ease-out);
}
--.sn-overlay.is-closing { animation: snFadeOut 160ms ease-in forwards; }
++.sn-overlay.is-closing { animation: snFadeOut var(--ov-dur-2, 160ms) ease-in forwards; }
.sn-dialog {
position: relative; width: min(860px, 100%); max-height: calc(100dvh - 32px); overflow: auto;
background: var(--bg-card); border: 1px solid var(--border); border-radius: 20px;
padding: 18px 20px 14px; box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5);
-- animation: snPop 240ms cubic-bezier(0.2, 0.9, 0.3, 1.15);
++ animation: snPop var(--ov-dur-2, 240ms) var(--ov-ease-out, cubic-bezier(0.2, 0.9, 0.3, 1.15));
}
.sn-dialog:focus { outline: none; }
.sn-head { display: grid; grid-template-columns: auto 1fr auto auto; align-items: start; gap: 10px; }
@@@ -65,12 +65,12 @@
.sn-svg { display: block; width: 100%; height: auto; overflow: visible; touch-action: pan-y; }
.sn-grid { stroke: color-mix(in srgb, var(--text-primary) 9%, transparent); stroke-width: 1; shape-rendering: crispEdges; }
.sn-axis { fill: var(--text-muted); font-size: 10.5px; font-variant-numeric: tabular-nums; }
--.sn-bar { fill: var(--accent); transform-box: fill-box; transform-origin: bottom; animation: snGrow 520ms cubic-bezier(0.2, 0.8, 0.3, 1) both; transition: fill var(--dur-fast, 160ms); }
++.sn-bar { fill: var(--accent); transform-box: fill-box; transform-origin: bottom; animation: snGrow var(--ov-dur-4, 520ms) var(--ov-ease-out, cubic-bezier(0.2, 0.8, 0.3, 1)) both; transition: fill var(--dur-fast, 160ms); }
.sn-bar.is-hot { fill: color-mix(in srgb, var(--accent) 70%, #fff); }
.sn-line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.sn-line--context { stroke: color-mix(in srgb, var(--text-primary) 32%, transparent); stroke-width: 1.5; }
.sn-area { fill: color-mix(in srgb, var(--accent) 10%, transparent); }
--.sn-marks .sn-line:not(.sn-line--context) { stroke-dasharray: 4000; stroke-dashoffset: 4000; animation: snDraw 900ms cubic-bezier(0.3, 0.7, 0.2, 1) forwards; }
++.sn-marks .sn-line:not(.sn-line--context) { stroke-dasharray: 4000; stroke-dashoffset: 4000; animation: snDraw 900ms var(--ov-ease-out, cubic-bezier(0.3, 0.7, 0.2, 1)) forwards; }
.sn-dot { fill: var(--accent); stroke: var(--bg-card); stroke-width: 2; }
.sn-cross { stroke: color-mix(in srgb, var(--text-primary) 35%, transparent); stroke-width: 1; shape-rendering: crispEdges; pointer-events: none; }
.sn-legend { display: flex; gap: 14px; justify-content: flex-end; font-size: 0.72rem; color: var(--text-secondary); padding: 2px 4px 0; }
@@@ -83,7 +83,7 @@
position: absolute; top: 0; left: 0; z-index: 2; pointer-events: none; min-width: 90px;
padding: 7px 10px; border-radius: 10px; background: color-mix(in srgb, var(--bg-primary) 92%, transparent);
border: 1px solid var(--border); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
-- transition: transform 70ms linear;
++ transition: transform var(--ov-dur-1, 70ms) linear;
}
.sn-tip-value { font-size: 1rem; font-weight: 700; color: var(--text-primary); }
.sn-tip-label { font-size: 0.72rem; color: var(--text-secondary); }
ddiff --git a/public/css/features/viewer-places.css b/public/css/features/viewer-places.css
iindex 0775c9eb..fddc49c0 100644
---- a/public/css/features/viewer-places.css
++++ b/public/css/features/viewer-places.css
@@@ -5,7 +5,7 @@
padding: 8px 0 6px; border-radius: 14px; color: #fff; font-size: 0.86rem; line-height: 1.3;
background: rgba(12, 15, 24, 0.94); border: 1px solid rgba(255, 255, 255, 0.14);
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.5); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
-- animation: ovp-in 0.16s ease-out;
++ animation: ovp-in var(--ov-dur-2, 0.16s) var(--ov-ease-out, ease-out);
}
@keyframes ovp-in { from { opacity: 0; transform: translateY(4px); } }
.ovp-head {
ddiff --git a/public/css/home-fx.css b/public/css/home-fx.css
iindex 7f77868a..35dbad15 100644
---- a/public/css/home-fx.css
++++ b/public/css/home-fx.css
@@@ -7,7 +7,7 @@
.hfx-sparks { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; opacity: 0.9; }
.hfx-spot {
position: absolute; left: 0; top: 0; width: 720px; height: 720px; margin: -360px 0 0 -360px;
-- pointer-events: none; z-index: 0; opacity: 0; transition: opacity 0.4s;
++ pointer-events: none; z-index: 0; opacity: 0; transition: opacity var(--ov-dur-3, 0.4s);
border-radius: 50%;
background: radial-gradient(circle, color-mix(in srgb, var(--accent) 22%, transparent) 0%, transparent 70%);
transform: translate3d(50vw, 30vh, 0);
@@@ -34,7 +34,7 @@
#page-home .hero-primary .hb-label small { margin-left: 4px; font-size: 0.74rem; font-weight: 600; opacity: 0.82; }
#page-home .hero-golive-note { margin: 0; gap: 6px 18px; font-size: 0.86rem; }
#page-home .hero-golive-note a { display: inline-flex; align-items: center; gap: 6px; }
--#page-home .hero-golive-note a i { color: inherit; font-size: 0.7rem; transition: transform 0.2s; }
++#page-home .hero-golive-note a i { color: inherit; font-size: 0.7rem; transition: transform var(--ov-dur-2, 0.2s); }
#page-home .hero-golive-note a:hover i, #page-home .hero-golive-note a:focus-visible i { transform: translateX(3px); }
/* The rest: sized to their labels on one centred row (it fits from tablets up). */
#page-home .hero-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; width: 100%; }
@@@ -54,7 +54,7 @@
color-mix(in srgb, var(--c) 10%, var(--bg-card));
border: 1px solid color-mix(in srgb, var(--c) 42%, var(--border));
box-shadow: 0 2px 0 0 color-mix(in srgb, var(--c) 22%, transparent), 0 8px 20px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.09);
-- transition: transform 0.18s cubic-bezier(.2,1.3,.3,1), border-color 0.2s, box-shadow 0.2s, background 0.2s;
++ transition: transform var(--ov-dur-2, 0.18s) var(--ov-ease-spring, cubic-bezier(.2,1.3,.3,1)), border-color var(--ov-dur-2, 0.2s), box-shadow var(--ov-dur-2, 0.2s), background var(--ov-dur-2, 0.2s);
}
/* One wave, not six blinks: every button shares --hb-wave, so they can never drift apart, and a
small per-button offset makes a single highlight travel across the row. */
@@@ -81,7 +81,7 @@
content: ''; position: absolute; inset: -1px; border-radius: inherit; z-index: 0; padding: 1px;
background: conic-gradient(from var(--a, 0deg), transparent 0 70%, var(--c) 85%, transparent 100%);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-- -webkit-mask-composite: xor; mask-composite: exclude; opacity: 0; transition: opacity 0.25s;
++ -webkit-mask-composite: xor; mask-composite: exclude; opacity: 0; transition: opacity var(--ov-dur-2, 0.25s);
display: none;
}
/* Only built when a pointer is actually on the button — there are no hover states on a phone,
@@@ -93,14 +93,14 @@
background: color-mix(in srgb, var(--c) 16%, var(--bg-tertiary));
border: 1px solid color-mix(in srgb, var(--c) 40%, transparent);
box-shadow: 0 2px 6px rgba(0,0,0,0.35) inset;
-- transition: transform 0.35s cubic-bezier(.2,1.6,.3,1), background 0.25s, color 0.25s, box-shadow 0.25s;
-- animation: hbPulse var(--hb-wave) ease-in-out infinite; animation-delay: calc(var(--i, 0) * var(--hb-step));
++ transition: transform var(--ov-dur-3, 0.35s) var(--ov-ease-spring, cubic-bezier(.2,1.6,.3,1)), background var(--ov-dur-2, 0.25s), color var(--ov-dur-2, 0.25s), box-shadow var(--ov-dur-2, 0.25s);
++ animation: hbPulse var(--hb-wave) var(--ov-ease-in-out, ease-in-out) infinite; animation-delay: calc(var(--i, 0) * var(--hb-step));
animation-play-state: paused;
}
.hb-ico, .hb-label, .hb-go { position: relative; z-index: 1; }
.hb-label { flex: 1; text-align: left; }
/* Chevron appears on hover/focus: an explicit "this goes somewhere" cue. */
--.hb-go { width: 0; opacity: 0; font-size: 0.72rem; color: var(--c); transition: width 0.22s, opacity 0.22s, transform 0.22s; transform: translateX(-4px); }
++.hb-go { width: 0; opacity: 0; font-size: 0.72rem; color: var(--c); transition: width var(--ov-dur-2, 0.22s), opacity var(--ov-dur-2, 0.22s), transform var(--ov-dur-2, 0.22s); transform: translateX(-4px); }
.hb:hover, .hb:focus-visible {
transform: translateY(-3px);
border-color: color-mix(in srgb, var(--c) 78%, transparent);
@@@ -145,21 +145,21 @@
@keyframes hfxPulse { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); } 70% { box-shadow: 0 0 0 14px color-mix(in srgb, var(--accent) 0%, transparent); } 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); } }
/* Headline: subtle breathing glow (the sweeping sheen rendered as a block over gradient text, so it is gone) */
--.hfx-shimmer { animation: hfxBreathe 4s ease-in-out infinite; }
++.hfx-shimmer { animation: hfxBreathe 4s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes hfxBreathe { 0%,100% { filter: drop-shadow(0 0 0 rgba(139,92,246,0)); } 50% { filter: drop-shadow(0 0 18px rgba(139,92,246,0.45)); } }
--.hfx-glow { text-shadow: 0 0 22px color-mix(in srgb, var(--accent) 55%, transparent), 0 0 44px color-mix(in srgb, var(--accent) 25%, transparent); animation: hfxGlow 3s ease-in-out infinite; }
++.hfx-glow { text-shadow: 0 0 22px color-mix(in srgb, var(--accent) 55%, transparent), 0 0 44px color-mix(in srgb, var(--accent) 25%, transparent); animation: hfxGlow 3s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes hfxGlow { 0%,100% { filter: brightness(1); } 50% { filter: brightness(1.18); } }
/* Scroll reveal */
--.hfx-reveal { opacity: 0; transform: translateY(18px) scale(0.985); transition: opacity 0.6s ease var(--d, 0ms), transform 0.7s cubic-bezier(.2,1.2,.3,1) var(--d, 0ms); }
++.hfx-reveal { opacity: 0; transform: translateY(18px) scale(0.985); transition: opacity var(--ov-dur-4, 0.6s) var(--ov-ease-out, ease) var(--d, 0ms), transform var(--ov-dur-4, 0.7s) var(--ov-ease-spring, cubic-bezier(.2,1.2,.3,1)) var(--d, 0ms); }
.hfx-reveal.is-in { opacity: 1; transform: none; }
/* Stream card tilt */
--#page-home .stream-card { transition: transform 0.15s ease-out, box-shadow 0.25s; }
++#page-home .stream-card { transition: transform var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease-out), box-shadow var(--ov-dur-2, 0.25s); }
#page-home .stream-card.hfx-tilting { box-shadow: 0 24px 50px rgba(0,0,0,0.45), 0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent); z-index: 2; }
/* Live section header dot + Star section shimmer accent */
--#page-home .section-header h2 .live-dot { animation: hfxLive 1.4s ease-in-out infinite; }
++#page-home .section-header h2 .live-dot { animation: hfxLive 1.4s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes hfxLive { 0%,100% { transform: scale(1); filter: drop-shadow(0 0 0 rgba(231,76,60,0)); } 50% { transform: scale(1.25); filter: drop-shadow(0 0 8px rgba(231,76,60,0.9)); } }
@media (prefers-reduced-motion: reduce) {
@@@ -186,7 +186,7 @@
.cta-hero-grad { animation: none !important; }
.hero-aurora, .hero-aurora::before, .hero-aurora::after { filter: none !important; mix-blend-mode: normal !important; animation: none !important; transform: none !important; opacity: 0.22; }
.hero-collage { display: none !important; }
-- .hero-bg-layer, .hero-bg-layer.active { animation: none !important; transform: none !important; transition: opacity 0.4s ease; }
++ .hero-bg-layer, .hero-bg-layer.active { animation: none !important; transform: none !important; transition: opacity var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease); }
.hero-scrim { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; }
.navbar { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; background: var(--glass-strong); }
.stream-card, .stream-card:hover { transform: none !important; }
@@@ -216,7 +216,7 @@
content: ''; position: absolute; top: 0; bottom: 0; left: -60%; width: 220%;
z-index: 0; pointer-events: none;
background-image: linear-gradient(115deg, color-mix(in srgb, var(--c) 16%, transparent), transparent 42%, color-mix(in srgb, #a78bfa 14%, transparent));
-- animation: hbVibeWash 11s ease-in-out infinite;
++ animation: hbVibeWash 11s var(--ov-ease-in-out, ease-in-out) infinite;
}
.hb-vibe > * { position: relative; z-index: 1; }
@keyframes hbVibeWash { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(27%); } }
@@@ -225,7 +225,7 @@
.hb-vibe .hb-ico::after {
content: ''; position: absolute; top: 2px; right: 1px; width: 5px; height: 5px; border-radius: 50%;
background: radial-gradient(circle, #fff, var(--c) 60%, transparent 70%);
-- opacity: 0; animation: hbSpark 4.4s ease-out infinite;
++ opacity: 0; animation: hbSpark 4.4s var(--ov-ease-out, ease-out) infinite;
}
@keyframes hbSpark {
0% { opacity: 0; transform: translate(0, 0) scale(0.4); }
@@@ -259,7 +259,7 @@
width: 6px; height: 6px; border-radius: 50%;
background: color-mix(in srgb, var(--c) 55%, transparent);
z-index: 1; pointer-events: none; transform-origin: center;
-- animation: hbHintPulse 2.4s ease-out infinite;
++ animation: hbHintPulse 2.4s var(--ov-ease-out, ease-out) infinite;
}
@keyframes hbHintPulse {
0% { transform: scale(1); opacity: 0.85; }
@@@ -303,7 +303,7 @@
#page-home .hero-latest { background: color-mix(in srgb, var(--bg-secondary) 88%, transparent); }
/* Collage: flat drift, no 3D context, no per-card blur. */
--.hero-collage { perspective: none !important; transform-style: flat !important; transition: transform 0.6s cubic-bezier(.2, .7, .2, 1); }
++.hero-collage { perspective: none !important; transform-style: flat !important; transition: transform var(--ov-dur-4, 0.6s) var(--ov-ease-out, cubic-bezier(.2, .7, .2, 1)); }
.hero-float { transform-style: flat !important; will-change: auto !important; transform: translate3d(0, 12px, 0) scale(var(--depth-scale, 1)); }
.hero-float-inner { filter: none !important; }
.hero-float:hover .hero-float-inner { filter: none !important; }
@@@ -336,7 +336,7 @@
/* Phones: nothing in the hero moves on its own. The page is scrolled past here far more than it is
looked at, and every running animation in a region being scrolled costs a frame budget the
scroll needs. */
--.rs-lite .hero-float { animation: heroFloatIn 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) var(--in-delay, 0s) forwards !important; }
++.rs-lite .hero-float { animation: heroFloatIn var(--ov-dur-4, 0.6s) var(--ov-ease-out, cubic-bezier(0.2, 0.8, 0.2, 1)) var(--in-delay, 0s) forwards !important; }
.rs-lite .hero-bg-layer.active { animation: none !important; transform: scale(1.06) !important; will-change: auto !important; }
.rs-lite .hero-aurora, .rs-lite .hero-aurora::before, .rs-lite .hero-aurora::after { animation: none !important; }
.rs-lite .hfx-spot, .rs-lite .hfx-sparks { display: none !important; }
ddiff --git a/public/css/home-tour.css b/public/css/home-tour.css
iindex 4b858582..70125f09 100644
---- a/public/css/home-tour.css
++++ b/public/css/home-tour.css
@@@ -29,7 +29,7 @@
/* Stage: 3 columns on wide screens, a vertical stack on phones */
.tour-stage { --tour-vertical: 0; position: relative; display: grid; grid-template-columns: minmax(150px, 1fr) minmax(220px, 1.25fr) minmax(150px, 1fr); gap: 18px clamp(28px, 6vw, 70px); align-items: center; padding: 8px 4px; }
.tour-wires { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
--.tour-wire { fill: none; stroke-width: 1.6; stroke-linecap: round; stroke: color-mix(in srgb, var(--text-primary) 22%, transparent); stroke-dasharray: 5 7; animation: tourFlow 1.1s linear infinite; transition: stroke 0.2s, stroke-width 0.2s; }
++.tour-wire { fill: none; stroke-width: 1.6; stroke-linecap: round; stroke: color-mix(in srgb, var(--text-primary) 22%, transparent); stroke-dasharray: 5 7; animation: tourFlow 1.1s linear infinite; transition: stroke var(--ov-dur-2, 0.2s), stroke-width var(--ov-dur-2, 0.2s); }
.tour-wire--out { stroke: color-mix(in srgb, var(--c, #fff) 45%, transparent); }
@keyframes tourFlow { to { stroke-dashoffset: -12; } }
.tour-pk { fill: var(--c, #fde68a); filter: drop-shadow(0 0 4px var(--c, #fde68a)); }
@@@ -42,7 +42,7 @@
.tour-col--src .tour-col-label { text-align: right; }
.tour-col--src .tour-node { justify-self: end; flex-direction: row-reverse; text-align: right; }
.tour-node { --c: var(--text-primary); display: inline-flex; align-items: center; gap: 9px; padding: 6px 12px 6px 6px; border-radius: 999px; max-width: 100%;
-- background: color-mix(in srgb, var(--bg-tertiary) 80%, transparent); border: 1px solid color-mix(in srgb, var(--c) 30%, var(--border)); transition: transform 0.2s, border-color 0.2s, box-shadow 0.2s; }
++ background: color-mix(in srgb, var(--bg-tertiary) 80%, transparent); border: 1px solid color-mix(in srgb, var(--c) 30%, var(--border)); transition: transform var(--ov-dur-2, 0.2s), border-color var(--ov-dur-2, 0.2s), box-shadow var(--ov-dur-2, 0.2s); }
.tour-node--src { --c: #fde68a; }
.tour-node--dst { justify-self: start; padding-right: 14px; cursor: default; }
.tour-node:hover { transform: translateY(-1px); border-color: color-mix(in srgb, var(--c) 75%, transparent); box-shadow: 0 8px 22px rgba(0,0,0,0.3), 0 0 18px color-mix(in srgb, var(--c) 25%, transparent); }
@@@ -50,7 +50,7 @@
.tour-node-text { display: grid; line-height: 1.15; min-width: 0; }
.tour-node-text b { font-size: 0.84rem; font-weight: 800; white-space: nowrap; }
.tour-node-text small { font-size: 0.66rem; color: var(--text-muted); font-weight: 600; }
--.tour-node-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--c); box-shadow: 0 0 8px var(--c); margin-left: 2px; animation: tourDot 1.8s ease-in-out infinite; }
++.tour-node-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--c); box-shadow: 0 0 8px var(--c); margin-left: 2px; animation: tourDot 1.8s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes tourDot { 0%, 100% { opacity: 0.5; transform: scale(0.85); } 50% { opacity: 1; transform: scale(1.15); } }
.tour-core-wrap { display: grid; place-items: center; position: relative; z-index: 1; }
@@@ -70,7 +70,7 @@
content: ''; position: absolute; left: 50%; top: 6px; width: 190px; height: 190px;
transform: translate(-50%, -22%); border-radius: 50%; pointer-events: none; z-index: -1;
background: radial-gradient(circle, color-mix(in srgb, var(--accent) 30%, transparent) 0%, color-mix(in srgb, var(--accent) 10%, transparent) 38%, transparent 68%);
-- animation: tourCoreBreathe 5.6s ease-in-out infinite;
++ animation: tourCoreBreathe 5.6s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes tourCoreBreathe { 0%, 100% { opacity: 0.75; transform: translate(-50%, -22%) scale(1); } 50% { opacity: 1; transform: translate(-50%, -22%) scale(1.07); } }
.tour-core b {
@@@ -79,7 +79,7 @@
background: linear-gradient(100deg, var(--text-primary) 0%, var(--text-primary) 38%, #fff 48%, color-mix(in srgb, var(--accent) 45%, #fff) 56%, var(--text-primary) 66%, var(--text-primary) 100%);
background-size: 280% 100%;
-webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
-- animation: tourCoreSheen 6.5s ease-in-out infinite;
++ animation: tourCoreSheen 6.5s var(--ov-ease-in-out, ease-in-out) infinite;
text-shadow: 0 0 24px color-mix(in srgb, var(--accent) 30%, transparent);
}
@keyframes tourCoreSheen { 0%, 62% { background-position: 130% 50%; } 100% { background-position: -30% 50%; } }
@@@ -96,7 +96,7 @@
0 0 26px color-mix(in srgb, var(--accent) 60%, transparent),
0 0 60px color-mix(in srgb, var(--accent) 30%, transparent),
inset 0 -4px 10px rgba(0,0,0,0.25);
-- animation: tourCoreGlow 3.8s ease-in-out infinite;
++ animation: tourCoreGlow 3.8s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes tourCoreGlow {
0%, 100% { box-shadow: 0 0 0 1px color-mix(in srgb, #fff 22%, transparent), 0 0 26px color-mix(in srgb, var(--accent) 55%, transparent), 0 0 58px color-mix(in srgb, var(--accent) 26%, transparent), inset 0 -4px 10px rgba(0,0,0,0.25); }
@@@ -106,7 +106,7 @@
.tour-core-ring {
position: absolute; left: 50%; top: 32px; width: 52px; height: 52px; border-radius: 50%;
border: 1px solid color-mix(in srgb, var(--accent) 60%, transparent);
-- transform: translate(-50%, -50%); animation: tourRing 3.4s ease-out infinite; pointer-events: none;
++ transform: translate(-50%, -50%); animation: tourRing 3.4s var(--ov-ease-out, ease-out) infinite; pointer-events: none;
}
.tour-core-ring--2 { animation-delay: 1.7s; }
@keyframes tourRing {
@@@ -116,8 +116,8 @@
.tour-features { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin: 20px 0 0; }
.tour-feat { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border-radius: 999px; font-size: 0.76rem; font-weight: 700; color: var(--text-secondary);
-- background: color-mix(in srgb, var(--bg-tertiary) 70%, transparent); border: 1px solid rgba(255,255,255,0.08); transition: color 0.2s, border-color 0.2s, transform 0.2s;
-- animation: tourPop 0.5s cubic-bezier(.2,1.5,.3,1) both; animation-delay: calc(0.15s + var(--i, 0) * 55ms); }
++ background: color-mix(in srgb, var(--bg-tertiary) 70%, transparent); border: 1px solid rgba(255,255,255,0.08); transition: color var(--ov-dur-2, 0.2s), border-color var(--ov-dur-2, 0.2s), transform var(--ov-dur-2, 0.2s);
++ animation: tourPop var(--ov-dur-4, 0.5s) var(--ov-ease-spring, cubic-bezier(.2,1.5,.3,1)) both; animation-delay: calc(0.15s + var(--i, 0) * 55ms); }
.tour-feat i { color: var(--accent-light, var(--accent)); }
.tour-feat:hover { color: var(--text-primary); border-color: color-mix(in srgb, var(--accent) 50%, transparent); transform: translateY(-1px); }
@keyframes tourPop { from { opacity: 0; transform: translateY(8px) scale(0.9); } to { opacity: 1; transform: none; } }
@@@ -174,7 +174,7 @@
border-radius: 16px; border: 1px solid color-mix(in srgb, var(--q) 40%, var(--border));
background: linear-gradient(180deg, color-mix(in srgb, var(--q) 8%, var(--bg-card)), var(--bg-secondary));
box-shadow: 0 10px 26px rgba(0,0,0,0.30), inset 0 1px 0 rgba(255,255,255,0.05);
-- transition: transform 0.2s, box-shadow 0.25s, border-color 0.25s;
++ transition: transform var(--ov-dur-2, 0.2s), box-shadow var(--ov-dur-2, 0.25s), border-color var(--ov-dur-2, 0.25s);
}
.ovg-quest:hover { transform: translateY(-2px); border-color: color-mix(in srgb, var(--q) 70%, transparent);
box-shadow: 0 16px 38px rgba(0,0,0,0.42), 0 0 28px color-mix(in srgb, var(--q) 24%, transparent); }
@@@ -186,7 +186,7 @@
.ovg-quest-liquid {
position: absolute; left: 0; right: 0; bottom: 0; z-index: 0; pointer-events: none;
height: calc(var(--pct, 0) * 1%);
-- transition: height 1.8s cubic-bezier(.32, 1, .38, 1);
++ transition: height 1.8s var(--ov-ease-out, cubic-bezier(.32, 1, .38, 1));
background: linear-gradient(180deg, color-mix(in srgb, var(--q) 20%, transparent), color-mix(in srgb, var(--q) 9%, transparent));
}
/* The surface: two strips of a repeating wave, masked out of the water colour, drifting sideways
@@@ -198,7 +198,7 @@
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 16' preserveAspectRatio='none'%3E%3Cpath d='M0 9 C14 3 28 3 42 9 S70 15 84 9 V16 H0 Z'/%3E%3C/svg%3E") repeat-x left top / var(--wl) 16px;
animation: questWave 4.5s linear infinite;
}
--.ovg-quest-wave--b { --wl: 118px; top: -11px; opacity: 0.45; animation-duration: 7s; animation-direction: reverse; }
++.ovg-quest-wave--b { --wl: 118px; top: -11px; opacity: 0.45; animation-duration: 7; animation-direction: reverse; }
@keyframes questWave { to { transform: translateX(calc(var(--wl) * -1)); } }
.ovg-quest-bub {
position: absolute; left: var(--x); bottom: -6px; width: 5px; height: 5px; border-radius: 50%;
@@@ -210,7 +210,7 @@
.ovg-quest-sheen {
position: absolute; top: 0; bottom: 0; left: -40%; width: 32%; z-index: 1; pointer-events: none;
background: linear-gradient(100deg, transparent, rgba(255,255,255,0.10), transparent);
-- animation: questSheen 7s ease-in-out infinite;
++ animation: questSheen 7s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes questSheen { 0%, 58% { transform: translateX(0); } 100% { transform: translateX(440%); } }
@@@ -220,7 +220,7 @@
color: var(--q); background: color-mix(in srgb, var(--q) 20%, transparent);
border: 1px solid color-mix(in srgb, var(--q) 45%, transparent);
box-shadow: 0 0 14px color-mix(in srgb, var(--q) 25%, transparent);
-- animation: questBob 3.6s ease-in-out infinite;
++ animation: questBob 3.6s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes questBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2.5px); } }
.ovg-quest-text { min-width: 0; }
@@@ -230,12 +230,12 @@
.ovg-quest-count { text-align: right; line-height: 1.05; font-variant-numeric: tabular-nums; }
.ovg-quest-count b { font-size: 1.06rem; font-weight: 900; color: var(--q); }
.ovg-quest-count i { font-style: normal; font-size: 0.72rem; color: var(--text-secondary); text-shadow: 0 1px 3px rgba(0,0,0,0.45); }
--.ovg-quest-go { color: var(--q); opacity: 0.55; font-size: 0.8rem; transition: transform 0.2s, opacity 0.2s; }
++.ovg-quest-go { color: var(--q); opacity: 0.55; font-size: 0.8rem; transition: transform var(--ov-dur-2, 0.2s), opacity var(--ov-dur-2, 0.2s); }
.ovg-quest:hover .ovg-quest-go { opacity: 1; transform: translateX(3px); }
/* Came back with a task finished: the water climbs, the frame lights up, the gain floats off. */
.ovg-quest-pop { position: absolute; right: 54px; top: 50%; font-size: 0.95rem; font-weight: 900; color: var(--q);
-- pointer-events: none; animation: questPop 2.6s ease-out forwards; }
++ pointer-events: none; animation: questPop 2.6s var(--ov-ease-out, ease-out) forwards; }
@keyframes questPop {
0% { opacity: 0; transform: translateY(8px) scale(0.8); }
20% { opacity: 1; transform: translateY(-6px) scale(1.18); }
@@@ -264,7 +264,7 @@
position: absolute; inset: -40% -10% auto -10%; height: 200%; pointer-events: none;
background: radial-gradient(45% 40% at 20% 0%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%),
radial-gradient(40% 40% at 85% 8%, rgba(45,212,191,0.16), transparent 70%);
-- animation: joinDrift 16s ease-in-out infinite alternate; animation-play-state: paused;
++ animation: joinDrift 16s var(--ov-ease-in-out, ease-in-out) infinite alternate; animation-play-state: paused;
}
.tour-quest-slot.fx-live .ovg-join-glow { animation-play-state: running; }
@keyframes joinDrift { to { transform: translate3d(-5%, 3%, 0) scale(1.1); } }
@@@ -272,7 +272,7 @@
.ovg-join-mark { width: 40px; height: 40px; flex: none; border-radius: 13px; display: grid; place-items: center; color: #fff;
background: linear-gradient(135deg, var(--accent), #2dd4bf);
box-shadow: 0 8px 20px color-mix(in srgb, var(--accent) 45%, transparent); }
--.tour-quest-slot.fx-live .ovg-join-mark { animation: joinMark 5.5s ease-in-out infinite; }
++.tour-quest-slot.fx-live .ovg-join-mark { animation: joinMark 5.5s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes joinMark { 0%, 100% { transform: translateY(0) rotate(0); } 50% { transform: translateY(-3px) rotate(-7deg); } }
.ovg-join-head h3 { margin: 0; font-size: 1.02rem; font-weight: 900; letter-spacing: -0.015em; }
.ovg-join-head p { margin: 3px 0 0; font-size: 0.82rem; color: var(--text-muted); }
@@@ -285,7 +285,7 @@
padding: 6px 11px 6px 7px; border-radius: 999px;
background: color-mix(in srgb, var(--accent) 9%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 20%, transparent);
-- transition: background 0.2s, border-color 0.2s; }
++ transition: background var(--ov-dur-2, 0.2s), border-color var(--ov-dur-2, 0.2s); }
.ovg-join-perks li:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); border-color: color-mix(in srgb, var(--accent) 38%, transparent); }
.ovg-join-perks small { display: none; }
.ovg-join .ovg-join-ico { width: 22px; height: 22px; border-radius: 7px; font-size: 0.66rem; }
@@@ -314,7 +314,7 @@
background: color-mix(in srgb, var(--accent) 11%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
border-radius: 999px;
-- transition: transform 0.22s cubic-bezier(.2, 1, .3, 1), background 0.22s, border-color 0.22s, box-shadow 0.25s;
++ transition: transform var(--ov-dur-2, 0.22s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1)), background var(--ov-dur-2, 0.22s), border-color var(--ov-dur-2, 0.22s), box-shadow var(--ov-dur-2, 0.25s);
}
.ovg-join-expand:hover {
transform: translateY(-1px);
@@@ -329,7 +329,7 @@
font-size: 0.6rem; color: #fff;
background: linear-gradient(140deg, var(--accent), color-mix(in srgb, var(--accent) 55%, #2dd4bf));
box-shadow: 0 3px 9px color-mix(in srgb, var(--accent) 45%, transparent);
-- transition: transform 0.3s cubic-bezier(.2, 1, .3, 1);
++ transition: transform var(--ov-dur-3, 0.3s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1));
}
.ovg-join.is-open .ovg-join-expand i { transform: rotate(180deg); }
/* The nudge and the sweep: attention without a colour change that drags the eye off the copy. */
@@@ -337,15 +337,15 @@
content: ''; position: absolute; top: 0; bottom: 0; left: -45%; width: 38%; pointer-events: none;
background: linear-gradient(100deg, transparent, color-mix(in srgb, var(--accent) 34%, transparent), transparent);
}
--.tour-quest-slot.fx-live .ovg-join:not(.is-open) .ovg-join-expand::after { animation: joinNudgeSweep 6s ease-in-out infinite; }
--.tour-quest-slot.fx-live .ovg-join:not(.is-open) .ovg-join-expand i { animation: joinNudge 2.6s ease-in-out infinite; }
++.tour-quest-slot.fx-live .ovg-join:not(.is-open) .ovg-join-expand::after { animation: joinNudgeSweep 6s var(--ov-ease-in-out, ease-in-out) infinite; }
++.tour-quest-slot.fx-live .ovg-join:not(.is-open) .ovg-join-expand i { animation: joinNudge 2.6s var(--ov-ease-in-out, ease-in-out) infinite; }
.ovg-join-expand:hover i, .ovg-join.is-open .ovg-join-expand i { animation: none !important; }
@keyframes joinNudgeSweep { 0%, 62% { transform: translateX(0); } 100% { transform: translateX(420%); } }
@keyframes joinNudge { 0%, 74%, 100% { transform: translateY(0); } 82% { transform: translateY(2.5px); } 90% { transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) {
.ovg-join-expand::after, .ovg-join-expand i { animation: none !important; }
}
--.tour-quest-slot.fx-live .ovg-join-perks li { animation: joinPerk 0.6s cubic-bezier(.2, 1, .3, 1) both;
++.tour-quest-slot.fx-live .ovg-join-perks li { animation: joinPerk var(--ov-dur-4, 0.6s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1)) both;
animation-delay: calc(var(--i) * 85ms + 100ms); }
@keyframes joinPerk { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.ovg-join-ico { width: 28px; height: 28px; flex: none; border-radius: 9px; display: grid; place-items: center; font-size: 0.76rem;
@@@ -358,10 +358,10 @@
.ovg-join-go { position: relative; overflow: hidden; }
.ovg-join-go::after { content: ''; position: absolute; top: 0; bottom: 0; left: -45%; width: 35%;
background: linear-gradient(100deg, transparent, rgba(255,255,255,0.28), transparent); }
--.tour-quest-slot.fx-live .ovg-join-go::after { animation: questSheen 5.5s ease-in-out infinite; }
++.tour-quest-slot.fx-live .ovg-join-go::after { animation: questSheen 5.5s var(--ov-ease-in-out, ease-in-out) infinite; }
.ovg-join-tour { background: none; border: 0; padding: 4px 2px; cursor: pointer; font: inherit;
font-size: 0.8rem; color: var(--text-muted); text-decoration: underline; text-underline-offset: 3px;
-- text-decoration-style: dotted; transition: color 0.2s; }
++ text-decoration-style: dotted; transition: color var(--ov-dur-2, 0.2s); }
.ovg-join-tour:hover { color: var(--accent); }
@media (max-width: 620px) {
@@@ -397,7 +397,7 @@
border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 12%, var(--bg-card)), var(--bg-secondary));
box-shadow: 0 10px 26px rgba(0,0,0,0.30), inset 0 1px 0 rgba(255,255,255,0.06);
-- transition: transform 0.2s, box-shadow 0.25s, border-color 0.25s;
++ transition: transform var(--ov-dur-2, 0.2s), box-shadow var(--ov-dur-2, 0.25s), border-color var(--ov-dur-2, 0.25s);
}
.ovg-launch {
display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 12px;
@@@ -418,11 +418,11 @@
}
.ovg-launch-aura { left: -10px; top: 50%; width: 120px; height: 120px; transform: translateY(-50%); }
.ovg-firstlive-ring { inset: -40% -10%; border-radius: 999px; }
--.fx-live .ovg-launch-aura, .fx-live .ovg-firstlive-ring { animation: ovgAura 3.6s ease-in-out infinite; }
++.fx-live .ovg-launch-aura, .fx-live .ovg-firstlive-ring { animation: ovgAura 3.6s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes ovgAura { 0%, 100% { opacity: 0.45; transform: translateY(-50%) scale(1); } 50% { opacity: 0.9; transform: translateY(-50%) scale(1.12); } }
.ovg-firstlive-ring { transform: none; }
@keyframes ovgAuraFlat { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.75; } }
--.fx-live .ovg-firstlive-ring { animation: ovgAuraFlat 3.6s ease-in-out infinite; }
++.fx-live .ovg-firstlive-ring { animation: ovgAuraFlat 3.6s var(--ov-ease-in-out, ease-in-out) infinite; }
/* The sheen crosses once on hover rather than looping — a loop on a primary button is noise. */
.ovg-launch-sheen, .ovg-firstlive-sheen {
@@@ -431,7 +431,7 @@
transform: translateX(-60%);
}
.ovg-launch:hover .ovg-launch-sheen, .ovg-firstlive:hover .ovg-firstlive-sheen {
-- opacity: 1; animation: ovgSheen 0.85s ease-out;
++ opacity: 1; animation: ovgSheen 0.85s var(--ov-ease-out, ease-out);
}
@keyframes ovgSheen { from { transform: translateX(-60%); } to { transform: translateX(60%); } }
@@@ -444,7 +444,7 @@
.ovg-launch-text { position: relative; z-index: 2; min-width: 0; display: grid; gap: 2px; }
.ovg-launch-text b { font-size: 0.95rem; font-weight: 800; letter-spacing: -0.01em; }
.ovg-launch-text small { font-size: 0.74rem; color: var(--text-muted); }
--.ovg-launch-go { position: relative; z-index: 2; color: var(--accent); transition: transform 0.2s; }
++.ovg-launch-go { position: relative; z-index: 2; color: var(--accent); transition: transform var(--ov-dur-2, 0.2s); }
.ovg-launch:hover .ovg-launch-go { transform: translateX(3px); }
/* The home page's empty state is a single centred pill — there is no progress to explain there.
@@@ -470,7 +470,7 @@
border: 1px solid color-mix(in srgb, var(--accent) 36%, transparent);
}
.ovg-firstlive > span { flex: 1 1 auto; text-align: left; white-space: nowrap; }
--.ovg-firstlive-go { flex: none; font-size: 0.82rem; color: var(--accent); transition: transform 0.2s; }
++.ovg-firstlive-go { flex: none; font-size: 0.82rem; color: var(--accent); transition: transform var(--ov-dur-2, 0.2s); }
.ovg-firstlive:hover .ovg-firstlive-go { transform: translateX(3px); }
@media (max-width: 420px) {
.ovg-firstlive { font-size: 0.86rem; padding: 8px 13px 8px 8px; gap: 9px; }
ddiff --git a/public/css/i18n-star.css b/public/css/i18n-star.css
iindex 0ff31c59..0bdf0af8 100644
---- a/public/css/i18n-star.css
++++ b/public/css/i18n-star.css
@@@ -12,7 +12,7 @@
border-radius: 0 8px 8px 0;
background: color-mix(in srgb, var(--accent) 8%, transparent);
color: var(--text-secondary); font-size: 0.82rem; line-height: 1.35;
-- animation: i18nSlideIn 0.35s ease both;
++ animation: i18nSlideIn var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease) both;
}
.chat-translation i { color: var(--accent-light); margin-top: 2px; font-size: 0.8rem; }
.chat-translation-text { color: var(--text-primary); opacity: 0.92; }
@@@ -26,7 +26,7 @@
color: var(--accent-light);
background: color-mix(in srgb, var(--accent) 14%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
-- cursor: help; animation: i18nPulse 3s ease-in-out infinite;
++ cursor: help; animation: i18nPulse 3s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes i18nPulse { 0%,100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); } 50% { box-shadow: 0 0 12px color-mix(in srgb, var(--accent) 45%, transparent); } }
@@@ -45,7 +45,7 @@
═══════════════════════════════════════════════════════════════ */
.star-card-border {
position: relative; border-radius: 22px; padding: 2px; overflow: hidden; isolation: isolate;
-- animation: starCardIn 0.8s cubic-bezier(.2,1.3,.3,1) both;
++ animation: starCardIn var(--ov-dur-4, 0.8s) var(--ov-ease-spring, cubic-bezier(.2,1.3,.3,1)) both;
}
@keyframes starSpin { 50% { filter: hue-rotate(50deg) brightness(1.15); } } /* the ring shimmers in colour; it never turns */
@keyframes starCardIn { from { opacity: 0; transform: translateY(24px) scale(0.97); } to { opacity: 1; transform: none; } }
@@@ -82,7 +82,7 @@
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
}
--.star-avatar-glow { position: absolute; inset: -14px; border-radius: 50%; background: radial-gradient(circle, rgba(251,191,36,0.35), transparent 65%); animation: starGlow 2.6s ease-in-out infinite; }
++.star-avatar-glow { position: absolute; inset: -14px; border-radius: 50%; background: radial-gradient(circle, rgba(251,191,36,0.35), transparent 65%); animation: starGlow 2.6s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes starGlow { 0%,100% { transform: scale(0.9); opacity: 0.6; } 50% { transform: scale(1.15); opacity: 1; } }
.star-avatar {
position: relative; width: 100%; height: 100%; border-radius: 50%; object-fit: cover;
@@@ -99,9 +99,9 @@
.star-live-pill {
position: absolute; top: -4px; right: -6px; padding: 2px 8px; border-radius: 999px;
background: var(--live-red); color: #fff; font-size: 0.64rem; font-weight: 900; letter-spacing: 0.08em;
-- animation: i18nBlink 1.4s ease-in-out infinite;
++ animation: i18nBlink 1.4s var(--ov-ease-in-out, ease-in-out) infinite;
}
--.star-kicker i { animation: starTwinkle 1.8s ease-in-out infinite; }
++.star-kicker i { animation: starTwinkle 1.8s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes starTwinkle { 0%,100% { transform: scale(1) rotate(0); opacity: 1; } 50% { transform: scale(1.35) rotate(20deg); opacity: 0.7; } }
.star-btn-watch {
background: linear-gradient(135deg, #fde68a, #fbbf24 50%, #f59e0b); color: #1c1002; border: none;
@@@ -121,7 +121,7 @@
display: none; position: absolute; right: 30px; top: 2px;
padding: 2px 6px; font-size: 0.65rem; line-height: 1; z-index: 5; cursor: pointer;
background: var(--bg-card, #1e1e2e); border: 1px solid var(--border, #333); border-radius: var(--radius-sm, 4px);
-- color: var(--text-muted, #888); transition: color 0.15s, border-color 0.15s;
++ color: var(--text-muted, #888); transition: color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s);
}
.chat-msg-hoverable:hover .chat-translate-btn { display: inline-flex; }
.chat-msg-hoverable .chat-translate-btn:hover { color: var(--accent-light); border-color: var(--accent); }
@@@ -211,7 +211,7 @@
.pc-tools-row > button.chat-autotr {
display: inline-flex; align-items: center; gap: 6px; margin-left: auto; padding: 0 12px; height: 34px; border-radius: 999px; cursor: pointer;
font-size: 0.78rem; font-weight: 600; line-height: 1; white-space: nowrap; color: var(--text-secondary);
-- background: transparent; border: 1px solid var(--border); transition: color .15s, border-color .15s, background-color .15s;
++ background: transparent; border: 1px solid var(--border); transition: color var(--ov-dur-2, .15s), border-color var(--ov-dur-2, .15s), background-color var(--ov-dur-2, .15s);
}
.chat-tools-row > button.chat-autotr i { font-size: 0.95rem; color: var(--accent-light, var(--accent)); }
.chat-tools-row > button.chat-autotr:hover { color: var(--text-primary); background: transparent; border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }
ddiff --git a/public/css/media-player.css b/public/css/media-player.css
iindex 592dd622..e2e6907a 100644
---- a/public/css/media-player.css
++++ b/public/css/media-player.css
@@@ -79,7 +79,7 @@ code { background: rgba(255,255,255,.08); padding: 2px 6px; border-radius: 6px;
width: 40px; height: 40px;
border: 3px solid var(--mp-accent);
border-radius: 50%;
-- animation: mp-pulse 1.4s ease-out infinite; /* a ring that pulses out: nothing spins */
++ animation: mp-pulse 1.4s var(--ov-ease-out, ease-out) infinite; /* a ring that pulses out: nothing spins */
}
@keyframes mp-pulse { from { box-shadow: 0 0 0 0 color-mix(in srgb, var(--mp-accent) 55%, transparent); } to { box-shadow: 0 0 0 12px transparent; } }
@media (prefers-reduced-motion: reduce) { .mp-spinner { animation: none; opacity: .7; } } /* a still ring while it loads */
@@@ -94,7 +94,7 @@ code { background: rgba(255,255,255,.08); padding: 2px 6px; border-radius: 6px;
padding: 10px 14px;
background: linear-gradient(transparent, rgba(0,0,0,0.85));
z-index: 20;
-- transition: opacity 0.3s;
++ transition: opacity var(--ov-dur-3, 0.3s);
}
.mp-player-wrap:not(:hover) .mp-controls:not(.mp-controls-locked) {
@@@ -110,7 +110,7 @@ code { background: rgba(255,255,255,.08); padding: 2px 6px; border-radius: 6px;
padding: 6px 8px;
border-radius: 8px;
line-height: 1;
-- transition: background 150ms;
++ transition: background var(--ov-dur-2, 150ms);
}
.mp-ctrl-btn:hover { background: rgba(255,255,255,0.1); }
@@@ -146,7 +146,7 @@ code { background: rgba(255,255,255,.08); padding: 2px 6px; border-radius: 6px;
background: rgba(255,255,255,0.18);
border-radius: 3px;
pointer-events: none;
-- transition: width 0.3s;
++ transition: width var(--ov-dur-3, 0.3s);
}
.mp-seek-progress {
@@@ -155,7 +155,7 @@ code { background: rgba(255,255,255,.08); padding: 2px 6px; border-radius: 6px;
background: var(--mp-accent);
border-radius: 3px;
pointer-events: none;
-- transition: width 0.1s linear;
++ transition: width var(--ov-dur-1, 0.1s) linear;
}
.mp-seek-input {
@@@ -232,7 +232,7 @@ code { background: rgba(255,255,255,.08); padding: 2px 6px; border-radius: 6px;
.mp-item-main { display: flex; gap: 12px; min-width: 0; }
.mp-item-body { min-width: 0; display: grid; gap: 4px; }
.mp-item-actions { display: flex; gap: 8px; flex-wrap: wrap; }
--.mp-icon-btn { background: transparent; color: var(--mp-text); border: 1px solid var(--mp-border); border-radius: 10px; padding: 8px 10px; cursor: pointer; transition: background 0.15s; }
++.mp-icon-btn { background: transparent; color: var(--mp-text); border: 1px solid var(--mp-border); border-radius: 10px; padding: 8px 10px; cursor: pointer; transition: background var(--ov-dur-2, 0.15s); }
.mp-icon-btn:hover { background: rgba(255,255,255,0.06); }
.mp-icon-btn.mp-icon-danger { color: var(--mp-danger); border-color: rgba(224, 90, 90, 0.2); }
.mp-icon-btn.mp-icon-danger:hover { background: rgba(224, 90, 90, 0.1); }
ddiff --git a/public/css/messenger.css b/public/css/messenger.css
iindex 1bdc3aa1..db53bddf 100644
---- a/public/css/messenger.css
++++ b/public/css/messenger.css
@@@ -21,7 +21,7 @@
justify-content: center;
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
z-index: 9998;
-- transition: transform 0.15s, background 0.15s;
++ transition: transform var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
}
.messenger-toggle:hover {
transform: scale(1.08);
@@@ -65,7 +65,7 @@
display: none;
flex-direction: column;
overflow: hidden;
-- animation: msgSlideUp 0.2s ease-out;
++ animation: msgSlideUp var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease-out);
}
@media (prefers-reduced-motion: reduce) { .messenger-panel { animation: none; } }
.messenger-panel.open {
@@@ -112,7 +112,7 @@
display: flex;
align-items: center;
justify-content: center;
-- transition: background 0.15s, color 0.15s;
++ transition: background var(--ov-dur-2, 0.15s), color var(--ov-dur-2, 0.15s);
}
.msg-header-btn:hover {
background: var(--bg-hover);
@@@ -147,7 +147,7 @@
padding: 10px 16px;
gap: 10px;
cursor: pointer;
-- transition: background 0.12s;
++ transition: background var(--ov-dur-1, 0.12s);
border-bottom: 1px solid var(--border);
position: relative;
}
@@@ -368,7 +368,7 @@
color: var(--text-primary);
font-size: 13px;
outline: none;
-- transition: border-color 0.15s;
++ transition: border-color var(--ov-dur-2, 0.15s);
font-family: var(--font);
}
.msg-compose input:focus {
@@@ -390,7 +390,7 @@
align-items: center;
justify-content: center;
flex-shrink: 0;
-- transition: background 0.15s, transform 0.1s;
++ transition: background var(--ov-dur-2, 0.15s), transform var(--ov-dur-1, 0.1s);
}
.msg-send-btn:hover {
background: var(--accent-light);
@@@ -448,7 +448,7 @@
cursor: pointer;
font-size: 10px;
opacity: 0.7;
-- transition: opacity 0.15s;
++ transition: opacity var(--ov-dur-2, 0.15s);
}
.msg-search-chip .remove-chip:hover {
opacity: 1;
@@@ -464,7 +464,7 @@
padding: 8px 16px;
gap: 10px;
cursor: pointer;
-- transition: background 0.12s;
++ transition: background var(--ov-dur-1, 0.12s);
}
.msg-user-item:hover {
background: var(--bg-hover);
@@@ -499,7 +499,7 @@
font-weight: 600;
font-size: 14px;
cursor: pointer;
-- transition: background 0.15s;
++ transition: background var(--ov-dur-2, 0.15s);
}
.msg-search-actions button:hover {
background: var(--accent-light);
@@@ -554,7 +554,7 @@
color: var(--danger-text, var(--danger));
cursor: pointer;
opacity: 0.7;
-- transition: opacity 0.15s;
++ transition: opacity var(--ov-dur-2, 0.15s);
background: none;
border: none;
}
@@@ -572,7 +572,7 @@
cursor: pointer;
background: none;
border: none;
-- transition: color 0.15s;
++ transition: color var(--ov-dur-2, 0.15s);
}
.msg-group-add-btn:hover {
color: var(--accent-light);
@@@ -593,7 +593,7 @@
border-radius: var(--radius);
font-size: 13px;
cursor: pointer;
-- transition: background 0.15s;
++ transition: background var(--ov-dur-2, 0.15s);
width: 100%;
}
.msg-group-leave button:hover {
@@@ -795,7 +795,7 @@
height: 20px;
background: var(--bg-hover);
border-radius: 10px;
-- transition: background 0.2s;
++ transition: background var(--ov-dur-2, 0.2s);
margin-left: 12px;
}
.msg-toggle-slider::after {
@@@ -807,7 +807,7 @@
height: 16px;
border-radius: 50%;
background: var(--text-muted);
-- transition: transform 0.2s, background 0.2s;
++ transition: transform var(--ov-dur-2, 0.2s), background var(--ov-dur-2, 0.2s);
}
.msg-settings-toggle input:checked + .msg-toggle-slider {
background: var(--accent);
@@@ -823,7 +823,7 @@
align-items: center;
gap: 10px;
padding: 8px 0;
-- transition: opacity 0.2s;
++ transition: opacity var(--ov-dur-2, 0.2s);
}
.msg-settings-range {
flex: 1;
@@@ -917,7 +917,7 @@
display: flex;
align-items: center;
gap: 6px;
-- transition: background 0.15s, color 0.15s;
++ transition: background var(--ov-dur-2, 0.15s), color var(--ov-dur-2, 0.15s);
}
.msg-settings-test-btn:hover {
background: var(--bg-tertiary);
ddiff --git a/public/css/ov-guide.css b/public/css/ov-guide.css
iindex 155dca64..191d717c 100644
---- a/public/css/ov-guide.css
++++ b/public/css/ov-guide.css
@@@ -4,10 +4,10 @@
.ovg-root { position: fixed; inset: 0; z-index: 21000; display: none; }
.ovg-root.is-open { display: block; }
body.ovg-lock { overflow: hidden; }
--.ovg-scrim { position: absolute; inset: 0; background: rgba(4,5,10,0.72); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); animation: ovgFade .25s ease; }
++.ovg-scrim { position: absolute; inset: 0; background: rgba(4,5,10,0.72); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); animation: ovgFade var(--ov-dur-2, .25s) var(--ov-ease-out, ease); }
.ovg-card { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: min(720px, calc(100vw - 24px)); max-height: calc(100vh - 32px); display: flex; flex-direction: column;
border-radius: 22px; background: var(--bg-card); border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border)); box-shadow: 0 40px 100px rgba(0,0,0,0.65), 0 0 0 1px rgba(255,255,255,0.04), 0 0 80px color-mix(in srgb, var(--accent) 25%, transparent);
-- animation: ovgPop .4s cubic-bezier(.2,1.3,.3,1); overflow: hidden; }
++ animation: ovgPop var(--ov-dur-3, .4s) var(--ov-ease-spring, cubic-bezier(.2,1.3,.3,1)); overflow: hidden; }
@keyframes ovgFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes ovgPop { from { opacity: 0; transform: translate(-50%, -46%) scale(0.96); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } }
.ovg-close { position: absolute; top: 10px; right: 10px; width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--border); background: var(--bg-tertiary); color: var(--text-secondary); cursor: pointer; z-index: 2; }
@@@ -17,14 +17,14 @@ body.ovg-lock { overflow: hidden; }
.ovg-title { font-size: 1.25rem; font-weight: 900; line-height: 1.15; }
.ovg-steps { --p: 0%; list-style: none; margin: 8px 20px 0; padding: 0 0 12px; display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; position: relative; }
.ovg-steps::before { content: ''; position: absolute; left: 12%; right: 12%; top: 15px; height: 3px; background: var(--bg-tertiary); border-radius: 2px; }
--.ovg-steps::after { content: ''; position: absolute; left: 12%; top: 15px; height: 3px; width: calc(76% * var(--p) / 100%); background: linear-gradient(90deg, var(--accent), #22d3ee); border-radius: 2px; transition: width .5s cubic-bezier(.2,.9,.3,1); }
++.ovg-steps::after { content: ''; position: absolute; left: 12%; top: 15px; height: 3px; width: calc(76% * var(--p) / 100%); background: linear-gradient(90deg, var(--accent), #22d3ee); border-radius: 2px; transition: width var(--ov-dur-4, .5s) var(--ov-ease-out, cubic-bezier(.2,.9,.3,1)); }
.ovg-steps li { position: relative; z-index: 1; display: grid; justify-items: center; gap: 5px; font-size: 0.68rem; font-weight: 700; color: var(--text-muted); text-align: center; }
--.ovg-step-dot { width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center; background: var(--bg-tertiary); border: 2px solid var(--border); font-size: 0.8rem; color: var(--text-muted); transition: all .3s; }
++.ovg-step-dot { width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center; background: var(--bg-tertiary); border: 2px solid var(--border); font-size: 0.8rem; color: var(--text-muted); transition: all var(--ov-dur-3, .3s); }
.ovg-steps li.now .ovg-step-dot { background: var(--accent-strong, var(--accent, #3472d8)); border-color: var(--accent-strong, var(--accent, #3472d8)); color: var(--on-accent-strong, var(--on-accent, #fff)); box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 22%, transparent); transform: scale(1.08); }
.ovg-steps li.now { color: var(--text-primary); }
.ovg-steps li.done .ovg-step-dot { background: color-mix(in srgb, #06d6a0 25%, var(--bg-tertiary)); border-color: #06d6a0; color: #06d6a0; }
.ovg-body { flex: 1; min-height: 0; overflow: auto; padding: 4px 20px 12px; }
--.ovg-panel { animation-duration: .35s; animation-timing-function: cubic-bezier(.2,.9,.3,1); animation-fill-mode: both; }
++.ovg-panel { animation-duration: .; animation-timing-function: var(--ov-ease-out, cubic-bezier(.2,.9,.3,1)); animation-fill-mode: both; }
.ovg-panel.in-r { animation-name: ovgInR; } .ovg-panel.in-l { animation-name: ovgInL; }
@keyframes ovgInR { from { opacity: 0; transform: translateX(28px); } to { opacity: 1; transform: none; } }
@keyframes ovgInL { from { opacity: 0; transform: translateX(-28px); } to { opacity: 1; transform: none; } }
@@@ -32,7 +32,7 @@ body.ovg-lock { overflow: hidden; }
.ovg-lead b { color: var(--text-primary); }
.ovg-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 12px; }
.ovg-grid--3 { grid-template-columns: repeat(3, 1fr); }
--.ovg-pick { --c: var(--accent); display: flex; align-items: center; gap: 12px; text-align: left; padding: 12px; border-radius: 14px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); cursor: pointer; font: inherit; transition: transform .15s, border-color .15s, box-shadow .15s; animation: ovgRise .4s both; }
++.ovg-pick { --c: var(--accent); display: flex; align-items: center; gap: 12px; text-align: left; padding: 12px; border-radius: 14px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); cursor: pointer; font: inherit; transition: transform var(--ov-dur-2, .15s), border-color var(--ov-dur-2, .15s), box-shadow var(--ov-dur-2, .15s); animation: ovgRise var(--ov-dur-3, .4s) both; }
.ovg-pick:nth-child(2) { animation-delay: .05s; } .ovg-pick:nth-child(3) { animation-delay: .1s; } .ovg-pick:nth-child(4) { animation-delay: .15s; } .ovg-pick:nth-child(5) { animation-delay: .2s; }
@keyframes ovgRise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.ovg-pick:hover { transform: translateY(-2px); border-color: color-mix(in srgb, var(--c) 60%, var(--border)); }
@@@ -46,10 +46,10 @@ body.ovg-lock { overflow: hidden; }
.ovg-form { display: grid; gap: 10px; } .ovg-form.is-hidden { display: none; }
.ovg-form label { display: grid; gap: 6px; font-size: 0.8rem; font-weight: 700; color: var(--text-secondary); }
.ovg-chips { display: flex; flex-wrap: wrap; gap: 6px; }
--.ovg-chip { padding: 6px 11px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-tertiary); color: var(--text-primary); font: inherit; font-size: 0.8rem; font-weight: 700; cursor: pointer; transition: all .15s; }
++.ovg-chip { padding: 6px 11px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-tertiary); color: var(--text-primary); font: inherit; font-size: 0.8rem; font-weight: 700; cursor: pointer; transition: all var(--ov-dur-2, .15s); }
.ovg-chip:hover { border-color: var(--accent); } .ovg-chip.on { background: var(--accent-strong, var(--accent, #3472d8)); border-color: var(--accent-strong, var(--accent, #3472d8)); color: var(--on-accent-strong, var(--on-accent, #fff)); }
.ovg-detail { margin-top: 4px; }
--.ovg-box { padding: 14px; border-radius: 14px; background: var(--bg-secondary); border: 1px solid var(--border); animation: ovgRise .35s both; }
++.ovg-box { padding: 14px; border-radius: 14px; background: var(--bg-secondary); border: 1px solid var(--border); animation: ovgRise var(--ov-dur-3, .35s) both; }
.ovg-box p { margin: 0 0 10px; font-size: 0.86rem; color: var(--text-secondary); line-height: 1.5; }
.ovg-box-head { font-weight: 800; margin-bottom: 8px; display: flex; align-items: center; gap: 8px; } .ovg-box-head i { color: var(--accent); }
.ovg-kv { display: grid; grid-template-columns: 90px 1fr auto auto; align-items: center; gap: 8px; padding: 8px 0; border-top: 1px dashed var(--border); font-size: 0.84rem; }
@@@ -72,21 +72,21 @@ body.ovg-lock { overflow: hidden; }
.ovg-added li { display: inline-flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 999px; background: color-mix(in srgb, #06d6a0 12%, var(--bg-tertiary)); border: 1px solid color-mix(in srgb, #06d6a0 45%, var(--border)); font-size: 0.8rem; font-weight: 700; }
.ovg-ok { color: #06d6a0; }
.ovg-summary { display: grid; gap: 8px; margin-bottom: 14px; }
--.ovg-sum-row { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: 12px; background: var(--bg-secondary); border: 1px solid var(--border); animation: ovgRise .35s both; }
++.ovg-sum-row { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: 12px; background: var(--bg-secondary); border: 1px solid var(--border); animation: ovgRise var(--ov-dur-3, .35s) both; }
.ovg-sum-row:nth-child(2) { animation-delay: .06s; } .ovg-sum-row:nth-child(3) { animation-delay: .12s; }
.ovg-sum-row > i { width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center; color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); flex: none; }
.ovg-sum-row span { display: grid; min-width: 0; } .ovg-sum-row b { font-size: 0.9rem; } .ovg-sum-row small { color: var(--text-muted); font-size: 0.74rem; }
.ovg-launch { text-align: center; padding: 8px 0 4px; }
--.ovg-big { font-size: 1.1rem; padding: 14px 28px; box-shadow: 0 12px 34px color-mix(in srgb, var(--accent) 45%, transparent); animation: ovgPulse 2.2s ease-out infinite; }
++.ovg-big { font-size: 1.1rem; padding: 14px 28px; box-shadow: 0 12px 34px color-mix(in srgb, var(--accent) 45%, transparent); animation: ovgPulse 2.2s var(--ov-ease-out, ease-out) infinite; }
@keyframes ovgPulse { 0% { box-shadow: 0 12px 34px color-mix(in srgb, var(--accent) 45%, transparent), 0 0 0 0 color-mix(in srgb, var(--accent) 50%, transparent); } 100% { box-shadow: 0 12px 34px color-mix(in srgb, var(--accent) 45%, transparent), 0 0 0 20px transparent; } }
.ovg-wait { display: grid; justify-items: center; gap: 6px; padding: 10px; }
.ovg-wait b { font-size: 1rem; } .ovg-wait em { color: var(--accent-light, var(--accent)); font-style: normal; } .ovg-wait small { color: var(--text-muted); font-size: 0.8rem; }
.ovg-radar { position: relative; width: 64px; height: 64px; margin-bottom: 6px; }
--.ovg-radar i { position: absolute; inset: 0; border-radius: 50%; border: 2px solid var(--accent); opacity: 0; animation: ovgRadar 2.4s ease-out infinite; }
++.ovg-radar i { position: absolute; inset: 0; border-radius: 50%; border: 2px solid var(--accent); opacity: 0; animation: ovgRadar 2.4s var(--ov-ease-out, ease-out) infinite; }
.ovg-radar i:nth-child(2) { animation-delay: .8s; } .ovg-radar i:nth-child(3) { animation-delay: 1.6s; }
@keyframes ovgRadar { 0% { transform: scale(0.3); opacity: 0.9; } 100% { transform: scale(1.2); opacity: 0; } }
.ovg-live { text-align: center; padding: 16px 6px; display: grid; justify-items: center; gap: 8px; }
--.ovg-live-badge { font-size: 0.8rem; font-weight: 900; letter-spacing: 0.14em; padding: 4px 12px; border-radius: 6px; background: #e0245e; color: #fff; animation: ovgPulse 1.6s ease-out infinite; }
++.ovg-live-badge { font-size: 0.8rem; font-weight: 900; letter-spacing: 0.14em; padding: 4px 12px; border-radius: 6px; background: #e0245e; color: #fff; animation: ovgPulse 1.6s var(--ov-ease-out, ease-out) infinite; }
.ovg-live h3 { margin: 0; font-size: 1.6rem; font-weight: 900; } .ovg-live p { margin: 0 0 8px; color: var(--text-secondary); }
.ovg-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 20px 16px; border-top: 1px solid var(--border); background: var(--bg-card); }
.ovg-foot-note { font-size: 0.74rem; color: var(--text-muted); font-weight: 700; }
@@@ -99,7 +99,7 @@ body.ovg-lock { overflow: hidden; }
there, which is why Next could not be tapped. 94vh also measured the *large* viewport, so
with the browser toolbar showing the sheet ran below the visible area entirely. dvh tracks
the viewport that is actually on screen, and the footer reserves the safe-area inset. */
-- .ovg-card { left: 0; right: 0; top: auto; bottom: 0; transform: none; width: 100%; max-height: 94vh; border-radius: 20px 20px 0 0; animation: ovgSheet .35s cubic-bezier(.2,1,.3,1); }
++ .ovg-card { left: 0; right: 0; top: auto; bottom: 0; transform: none; width: 100%; max-height: 94vh; border-radius: 20px 20px 0 0; animation: ovgSheet var(--ov-dur-3, .35s) var(--ov-ease-out, cubic-bezier(.2,1,.3,1)); }
@supports (height: 100dvh) {
.ovg-card { max-height: min(94dvh, calc(100dvh - 6px)); }
}
@@@ -121,7 +121,7 @@ body.ovg-lock { overflow: hidden; }
.ovg-field-label { font-size: 0.8rem; font-weight: 700; color: var(--text-secondary); margin: 2px 0 6px; }
.ovg-field-text .muted { font-weight: 500; color: var(--text-muted); }
.ovg-prefix { display: flex; align-items: center; gap: 0; } .ovg-prefix span { padding: 9px 12px; border: 1px solid var(--border); border-right: 0; border-radius: 10px 0 0 10px; background: var(--bg-tertiary); font-weight: 900; } .ovg-prefix .form-input { border-radius: 0 10px 10px 0; }
--.ovg-drop { display: flex; align-items: center; justify-content: center; gap: 10px; min-height: 96px; border: 2px dashed color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 14px; background: var(--bg-secondary); cursor: pointer; color: var(--text-secondary); font-weight: 700; padding: 12px; transition: border-color .15s, background .15s; }
++.ovg-drop { display: flex; align-items: center; justify-content: center; gap: 10px; min-height: 96px; border: 2px dashed color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 14px; background: var(--bg-secondary); cursor: pointer; color: var(--text-secondary); font-weight: 700; padding: 12px; transition: border-color var(--ov-dur-2, .15s), background var(--ov-dur-2, .15s); }
.ovg-drop:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--bg-secondary)); }
.ovg-drop img { width: 64px; height: 64px; object-fit: contain; border-radius: 10px; background: var(--bg-tertiary); }
.ovg-drop i { font-size: 1.4rem; color: var(--accent); }
@@@ -144,10 +144,10 @@ body.ovg-lock { overflow: hidden; }
.ovg-mini-ring::before { content: ''; position: absolute; inset: 4px; border-radius: 50%; background: var(--accent); }
.btn.is-ready small { opacity: 0.75; font-weight: 600; margin-left: 4px; }
/* Spotlight: point at a real element */
--.ovg-spot { position: fixed; inset: 0; z-index: 20500; pointer-events: none; opacity: 0; transition: opacity .3s; }
++.ovg-spot { position: fixed; inset: 0; z-index: 20500; pointer-events: none; opacity: 0; transition: opacity var(--ov-dur-3, .3s); }
.ovg-spot.is-on { opacity: 1; }
--.ovg-spot-hole { position: fixed; border-radius: 14px; pointer-events: none; box-shadow: 0 0 0 9999px rgba(3,3,8,0.68), 0 0 0 3px var(--accent), 0 0 30px color-mix(in srgb, var(--accent) 60%, transparent); transition: left .25s, top .25s, width .25s, height .25s, opacity .25s; }
--.ovg-spot-card { position: fixed; pointer-events: auto; width: 380px; max-width: calc(100vw - 24px); padding: 16px 18px; border-radius: 16px; color: #fff; background: linear-gradient(160deg, #17132b, #0c0c12 60%); border: 1px solid rgba(255,255,255,0.14); box-shadow: 0 30px 70px rgba(0,0,0,0.6); transition: top .25s, left .25s; }
++.ovg-spot-hole { position: fixed; border-radius: 14px; pointer-events: none; box-shadow: 0 0 0 9999px rgba(3,3,8,0.68), 0 0 0 3px var(--accent), 0 0 30px color-mix(in srgb, var(--accent) 60%, transparent); transition: left var(--ov-dur-2, .25s), top var(--ov-dur-2, .25s), width var(--ov-dur-2, .25s), height var(--ov-dur-2, .25s), opacity var(--ov-dur-2, .25s); }
++.ovg-spot-card { position: fixed; pointer-events: auto; width: 380px; max-width: calc(100vw - 24px); padding: 16px 18px; border-radius: 16px; color: #fff; background: linear-gradient(160deg, #17132b, #0c0c12 60%); border: 1px solid rgba(255,255,255,0.14); box-shadow: 0 30px 70px rgba(0,0,0,0.6); transition: top var(--ov-dur-2, .25s), left var(--ov-dur-2, .25s); }
.ovg-spot-step { font-size: 0.68rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent-light, var(--accent)); margin-bottom: 4px; }
.ovg-spot-card h4 { margin: 0 0 6px; font-size: 1.05rem; font-weight: 900; } .ovg-spot-card p { margin: 0 0 12px; font-size: 0.86rem; line-height: 1.5; color: #e2e8f0; }
.ovg-spot-actions { display: flex; flex-wrap: wrap; gap: 8px; }
ddiff --git a/public/css/style.css b/public/css/style.css
iindex 1fbf3369..799b2d5a 100644
---- a/public/css/style.css
++++ b/public/css/style.css
@@@ -177,7 +177,7 @@ html { -webkit-tap-highlight-color: transparent; }
-webkit-backdrop-filter: blur(18px);
box-shadow: 0 10px 30px rgba(0,0,0,0.22);
overflow: visible;
-- transition: background 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease, backdrop-filter 0.4s ease;
++ transition: background var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease), border-color var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease), box-shadow var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease), backdrop-filter var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease);
}
/* At the top of the home hero the bar is transparent so it blends into the hero, and its glass background fades in the
moment you scroll: body.ov-nav-overlay (app.js, the home route) plus the shared navbar's own .ovnav-top state, which
@@@ -219,7 +219,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
font-size: 0.75rem; font-weight: 400; line-height: 1.4;
white-space: normal; width: 260px;
opacity: 0; pointer-events: none;
-- transition: opacity 0.2s;
++ transition: opacity var(--ov-dur-2, 0.2s);
z-index: 1002;
box-shadow: var(--shadow-lg);
}
@@@ -229,7 +229,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
display: flex; align-items: center; gap: 6px;
padding: 8px 14px; border-radius: var(--radius);
color: var(--text-secondary); font-size: 0.9rem;
-- transition: all 0.2s; cursor: pointer;
++ transition: all var(--ov-dur-2, 0.2s); cursor: pointer;
white-space: nowrap; flex-shrink: 0;
border: 1px solid transparent;
text-decoration: none;
@@@ -248,7 +248,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
/* Live indicator on Go Live nav link */
.nav-live-dot {
font-size: 0.45rem; color: #e74c3c; margin-left: 4px;
-- animation: nav-live-pulse 1.5s ease-in-out infinite;
++ animation: nav-live-pulse 1.5s var(--ov-ease-in-out, ease-in-out) infinite;
vertical-align: middle;
}
@keyframes nav-live-pulse {
@@@ -288,7 +288,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
display: inline-flex; align-items: center; gap: 6px;
padding: 8px 16px; border-radius: var(--radius);
font-size: 0.9rem; font-weight: 500;
-- border: 1px solid transparent; transition: all 0.2s; cursor: pointer;
++ border: 1px solid transparent; transition: all var(--ov-dur-2, 0.2s); cursor: pointer;
box-shadow: 0 6px 18px rgba(0,0,0,0.14);
}
.btn-primary { background: var(--accent-strong, var(--accent, #3472d8)); color: var(--on-accent-strong, var(--on-accent, #0b0d10)); }
@@@ -322,7 +322,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
padding: 6px 8px; background: transparent;
border: 1px solid var(--border); border-radius: var(--radius);
color: var(--accent); font-size: 0.85rem; cursor: pointer;
-- transition: color 0.15s, border-color 0.15s, background 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
line-height: 1;
}
.btn-openvibe-bucks-mini:hover {
@@@ -425,7 +425,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
border-radius: 14px;
border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
background: color-mix(in srgb, var(--accent) 6%, transparent);
-- transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
++ transition: border-color var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease), background var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease), transform var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease);
}
.hero-standout:hover {
border-color: color-mix(in srgb, var(--accent) 45%, transparent);
@@@ -468,17 +468,17 @@ body.ov-nav-overlay .navbar.ovnav-top {
background: color-mix(in srgb, var(--accent) 8%, transparent);
color: var(--accent-light);
font-weight: 600;
-- transition: all 0.2s ease;
++ transition: all var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease);
}
/* ── Daily easter-egg hero widget ─────────────────────────────── */
@keyframes eggWobble { 0%, 100% { transform: rotate(-6deg); } 50% { transform: rotate(6deg); } }
.hero-egg-titles { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; flex: 1; }
/* Body opens to its real height (the old 340px cap clipped the third clue) */
--.egg-slot.is-filled { border-color: color-mix(in srgb, var(--accent) 70%, transparent); background: color-mix(in srgb, var(--accent) 16%, var(--bg-tertiary)); animation: eggSlotIn .18s cubic-bezier(.2,1.5,.3,1); }
++.egg-slot.is-filled { border-color: color-mix(in srgb, var(--accent) 70%, transparent); background: color-mix(in srgb, var(--accent) 16%, var(--bg-tertiary)); animation: eggSlotIn var(--ov-dur-2, .18s) var(--ov-ease-spring, cubic-bezier(.2,1.5,.3,1)); }
@keyframes eggSlotIn { from { transform: scale(0.7); } to { transform: scale(1); } }
@keyframes eggShakeSlot { 20% { transform: translateX(-4px); } 40% { transform: translateX(4px); } 60% { transform: translateX(-3px); } 80% { transform: translateX(3px); } }
--.egg-key { min-width: 34px; height: 34px; padding: 0 8px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-tertiary); color: var(--text-primary); font: inherit; font-weight: 800; cursor: pointer; transition: transform .1s, background .15s, border-color .15s; }
++.egg-key { min-width: 34px; height: 34px; padding: 0 8px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-tertiary); color: var(--text-primary); font: inherit; font-weight: 800; cursor: pointer; transition: transform var(--ov-dur-1, .1s), background var(--ov-dur-2, .15s), border-color var(--ov-dur-2, .15s); }
.egg-key--letter { min-width: 0; height: 30px; padding: 0; font-size: 0.8rem; text-transform: uppercase; }
/* Celebration effects */
@@@ -524,7 +524,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
/* ── OpenVibeApp Popover ─────────────────────────────────────────── */
.openvibeapp-popover {
max-height: 0; overflow: hidden; opacity: 0;
-- transition: max-height 0.4s cubic-bezier(0.4,0,0.2,1), opacity 0.3s ease;
++ transition: max-height var(--ov-dur-3, 0.4s) var(--ov-ease-out, cubic-bezier(0.4,0,0.2,1)), opacity var(--ov-dur-3, 0.3s) var(--ov-ease-out, ease);
pointer-events: none;
}
.openvibeapp-popover.open {
@@@ -543,7 +543,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
position: absolute; top: 12px; right: 16px;
background: none; border: none; color: var(--text-muted); font-size: 1.1rem;
cursor: pointer; padding: 4px 8px; border-radius: var(--radius-sm);
-- transition: color 0.2s, background 0.2s;
++ transition: color var(--ov-dur-2, 0.2s), background var(--ov-dur-2, 0.2s);
}
.openvibeapp-popover-close:hover { color: var(--text-primary); background: var(--bg-hover); }
.openvibeapp-header {
@@@ -639,7 +639,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
display: flex; align-items: flex-start; gap: 10px;
padding: 10px 12px; border-radius: var(--radius);
background: var(--bg-tertiary); border: 1px solid var(--border);
-- transition: border-color 0.2s, background 0.2s;
++ transition: border-color var(--ov-dur-2, 0.2s), background var(--ov-dur-2, 0.2s);
}
.openvibeapp-feat:hover { border-color: var(--accent-dark); background: var(--bg-hover); }
.openvibeapp-feat-icon {
@@@ -719,7 +719,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
padding: 6px 12px;
font-size: 0.84rem;
font-weight: 600;
-- transition: all 0.2s ease;
++ transition: all var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease);
}
.media-filter-chip:hover {
border-color: var(--accent-dark);
@@@ -773,7 +773,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
background: rgba(0,0,0,0.55); border-radius: 6px; padding: 3px 5px;
display: flex; align-items: center; cursor: pointer;
-webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
-- opacity: 0; transition: opacity 0.12s ease;
++ opacity: 0; transition: opacity var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease);
}
/* Reveal on hover, and keep visible while checked. Shift+click anywhere on the
card also toggles selection (see _selWrap handler). */
@@@ -798,7 +798,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
border-radius: var(--radius-lg);
overflow: hidden;
cursor: pointer;
-- transition: transform 0.2s, border-color 0.2s, box-shadow 0.2s, background 0.2s;
++ transition: transform var(--ov-dur-2, 0.2s), border-color var(--ov-dur-2, 0.2s), box-shadow var(--ov-dur-2, 0.2s), background var(--ov-dur-2, 0.2s);
box-shadow: var(--shadow-soft);
background-image: linear-gradient(180deg, rgba(255,255,255,0.02), transparent 45%);
color: inherit;
@@@ -866,7 +866,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9), 0 0 5px rgba(0, 0, 0, 0.55);
opacity: 0.72;
pointer-events: none;
-- transition: opacity 0.18s ease, background-color 0.18s ease;
++ transition: opacity var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease), background-color var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease);
}
.stream-card-duration { left: 5px; }
.stream-card-viewers { right: 5px; }
@@@ -1048,7 +1048,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
perspective: 1200px; perspective-origin: 50% 42%; transform-style: preserve-3d;
transform: rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
-- transition: transform 0.5s cubic-bezier(0.2, 0.7, 0.2, 1);
++ transition: transform var(--ov-dur-4, 0.5s) var(--ov-ease-out, cubic-bezier(0.2, 0.7, 0.2, 1));
}
/* Outer = transform container: ONLY the float drift + depth + fade-in live here, so hover /
swap scaling (on the inner) never fights the animation and can't jump. */
@@@ -1056,9 +1056,9 @@ body.ov-nav-overlay .navbar.ovnav-top {
position: absolute; display: block; width: var(--w, 120px); aspect-ratio: 16 / 10;
pointer-events: auto; text-decoration: none; transform-style: preserve-3d; transform-origin: center;
opacity: 0; transform: translate3d(0, 12px, var(--z, 0px));
-- animation: heroFloatIn 0.9s cubic-bezier(0.2, 0.8, 0.2, 1) var(--in-delay, 0s) forwards,
-- heroFloat3d var(--dur, 14s) ease-in-out var(--delay, 0s) infinite alternate;
-- transition: opacity 0.6s ease;
++ animation: heroFloatIn 0.9s var(--ov-ease-out, cubic-bezier(0.2, 0.8, 0.2, 1)) var(--in-delay, 0s) forwards,
++ heroFloat3d var(--dur, 14s) var(--ov-ease-in-out, ease-in-out) var(--delay, 0s) infinite alternate;
++ transition: opacity var(--ov-dur-4, 0.6s) var(--ov-ease-out, ease);
will-change: transform, opacity;
}
/* Inner = the visible card. Hover + swap scale THIS, smoothly, independent of the drift. */
@@@ -1068,7 +1068,7 @@ body.ov-nav-overlay .navbar.ovnav-top {
border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
box-shadow: 0 18px 40px -14px rgba(0, 0, 0, 0.72), 0 0 0 1px rgba(0, 0, 0, 0.22) inset;
filter: blur(var(--blur, 0px)); opacity: 1; transform: scale(1); transform-origin: center;
-- transition: transform 0.55s cubic-bezier(0.2, 0.7, 0.2, 1), opacity 0.55s ease, box-shadow 0.35s ease, filter 0.35s ease;
++ transition: transform var(--ov-dur-4, 0.55s) var(--ov-ease-out, cubic-bezier(0.2, 0.7, 0.2, 1)), opacity var(--ov-dur-4, 0.55s) var(--ov-ease-out, ease), box-shadow var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease), filter var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease);
}
.hero-float img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hero-float-body {
@@@ -1103,13 +1103,13 @@ body.ov-nav-overlay .navbar.ovnav-top {
position: absolute; inset: 0;
background-position: center; background-size: cover; background-repeat: no-repeat;
opacity: 0;
-- transition: opacity 1.8s ease;
++ transition: opacity 1.8s var(--ov-ease-out, ease);
filter: brightness(0.5) saturate(1.2) contrast(1.03);
transform: scale(1.08);
will-change: opacity, transform;
}
/* The active frame fades in AND slowly drifts/zooms (Ken Burns) for constant subtle motion. */
--.hero-bg-layer.active { opacity: 1; animation: heroBgKenBurns 12s ease-out both; }
++.hero-bg-layer.active { opacity: 1; animation: heroBgKenBurns 12s var(--ov-ease-out, ease-out) both; }
@keyframes heroBgKenBurns {
from { transform: scale(1.06) translate(-1.4%, 1%); }
to { transform: scale(1.16) translate(1.8%, -1.8%); }
@@@ -1201,7 +1201,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
color: var(--text-primary);
background: color-mix(in srgb, var(--bg-card) 90%, transparent);
border: 1px solid var(--border-light); box-shadow: var(--shadow);
-- transition: opacity 0.2s, background 0.15s, color 0.15s, border-color 0.15s, scale 0.12s;
++ transition: opacity var(--ov-dur-2, 0.2s), background var(--ov-dur-2, 0.15s), color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s), scale var(--ov-dur-1, 0.12s);
}
.ov-rail-prev { left: 8px; }
.ov-rail-next { right: 8px; }
@@@ -1224,7 +1224,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.ov-rail > .moments-row { margin-bottom: 0; }
.moment-card {
position: relative; flex: 0 0 220px; height: 124px; border-radius: 12px; overflow: hidden;
-- border: 1px solid var(--border); transition: border-color 0.15s ease, transform 0.15s ease, scale 0.12s ease;
++ border: 1px solid var(--border); transition: border-color var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease), transform var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease), scale var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease);
}
.moment-card img { width: 100%; height: 100%; object-fit: cover; display: block; }
.moment-overlay {
@@@ -1245,7 +1245,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.digest-chip {
position: relative; display: inline-flex; align-items: center; gap: 8px; padding: 6px 12px 6px 7px; border-radius: 999px;
background: var(--bg-tertiary); border: 1px solid var(--border); text-decoration: none; color: var(--text-primary);
-- font-size: 0.84rem; transition: border-color 0.15s ease, transform 0.15s ease;
++ font-size: 0.84rem; transition: border-color var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease), transform var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
.digest-extra .live-dot { font-size: 0.5rem; }
.digest-hot-line i { color: #f472b6; margin-right: 4px; font-style: normal; }
@@@ -1266,7 +1266,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.hero-stat-num { font-size: 1.22rem; font-weight: 800; color: var(--text-primary); font-variant-numeric: tabular-nums; }
.hero-stat-label { font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.hero-stat-sub { font-size: 0.6rem; font-weight: 700; color: color-mix(in srgb, var(--accent, #8b5cf6) 85%, var(--text-muted)); letter-spacing: 0.02em; }
--.hero-stat--live > i { color: #e0245e; animation: heroLivePulse 1.6s ease-in-out infinite; }
++.hero-stat--live > i { color: #e0245e; animation: heroLivePulse 1.6s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes heroLivePulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
/* Playful "next slogan batch" countdown */
@@@ -1317,15 +1317,15 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
color: #f4f2ef; font-size: 0.86rem; font-weight: 700; letter-spacing: 0.01em;
border-radius: 999px; cursor: pointer;
-- transition: background .15s ease, border-color .15s ease, transform .1s ease;
++ transition: background var(--ov-dur-2, .15s) var(--ov-ease-out, ease), border-color var(--ov-dur-2, .15s) var(--ov-ease-out, ease), transform var(--ov-dur-1, .1s) var(--ov-ease-out, ease);
}
.home-about-body {
overflow: hidden;
max-height: 8000px; opacity: 1;
-- transition: max-height 0.6s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.4s ease, margin 0.4s ease;
++ transition: max-height var(--ov-dur-4, 0.6s) var(--ov-ease-out, cubic-bezier(0.22, 1, 0.36, 1)), opacity var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease), margin var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease);
}
/* Toggle label swaps with collapsed state. */
--@media (prefers-reduced-motion: reduce) { .home-about-body { transition: opacity 0.3s ease; } }
++@media (prefers-reduced-motion: reduce) { .home-about-body { transition: opacity var(--ov-dur-3, 0.3s) var(--ov-ease-out, ease); } }
/* Animated glowing border sweep */
@property --cta-angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
@@@ -1381,7 +1381,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
background: color-mix(in srgb, var(--accent) 8%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
margin-bottom: 16px;
}
--.cta-live-dot { width: 8px; height: 8px; border-radius: 50%; background: #ff4a4a; box-shadow: 0 0 0 0 rgba(255,74,74,0.6); animation: ctaPulse 1.8s ease-out infinite; }
++.cta-live-dot { width: 8px; height: 8px; border-radius: 50%; background: #ff4a4a; box-shadow: 0 0 0 0 rgba(255,74,74,0.6); animation: ctaPulse 1.8s var(--ov-ease-out, ease-out) infinite; }
@keyframes ctaPulse { 0% { box-shadow: 0 0 0 0 rgba(255,74,74,0.55); } 70% { box-shadow: 0 0 0 8px rgba(255,74,74,0); } 100% { box-shadow: 0 0 0 0 rgba(255,74,74,0); } }
/* Hero */
@@@ -1411,7 +1411,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
position: relative; padding: 18px 18px 16px; border-radius: 14px;
background: linear-gradient(180deg, rgba(255,255,255,0.045), rgba(255,255,255,0.015));
border: 1px solid rgba(255,255,255,0.08);
-- transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
++ transition: transform var(--ov-dur-2, .18s) var(--ov-ease-out, ease), border-color var(--ov-dur-2, .18s) var(--ov-ease-out, ease), box-shadow var(--ov-dur-2, .18s) var(--ov-ease-out, ease);
overflow: hidden;
}
.cta-method-num { position: absolute; top: 10px; right: 14px; font-size: 1.6rem; font-weight: 900; color: color-mix(in srgb, var(--accent) 16%, transparent); letter-spacing: -0.03em; }
@@@ -1426,13 +1426,13 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
padding: 16px 16px; border-radius: 13px;
background: rgba(255,255,255,0.028);
border: 1px solid rgba(255,255,255,0.07);
-- transition: transform .16s ease, background .16s ease, border-color .16s ease, box-shadow .16s ease;
++ transition: transform var(--ov-dur-2, .16s) var(--ov-ease-out, ease), background var(--ov-dur-2, .16s) var(--ov-ease-out, ease), border-color var(--ov-dur-2, .16s) var(--ov-ease-out, ease), box-shadow var(--ov-dur-2, .16s) var(--ov-ease-out, ease);
/* `both` holds the from-state (opacity 0) for the whole animation-delay, and a subtree whose
rendering is skipped or deferred can sit in that state indefinitely — which is how a card
ends up as an empty outline in the middle of the list. `backwards` is not needed here: the
card's resting style is already the end state, so without a fill mode it is simply visible
until the animation runs, and visible after. Nothing can leave it blank. */
-- animation: ctaRise .5s ease;
++ animation: ctaRise var(--ov-dur-4, .5s) var(--ov-ease-out, ease);
}
.home-cta-features .home-cta-feature:nth-child(1){ animation-delay: .02s; }
.home-cta-features .home-cta-feature:nth-child(2){ animation-delay: .06s; }
@@@ -1625,7 +1625,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
border: 1px solid rgba(231,76,60,0.22);
background: linear-gradient(135deg, rgba(231,76,60,0.08), rgba(192,24,24,0.04));
text-decoration: none;
-- transition: all 0.25s ease;
++ transition: all var(--ov-dur-2, 0.25s) var(--ov-ease-out, ease);
}
.timeline-yt-link:hover {
border-color: rgba(231,76,60,0.45);
@@@ -1658,7 +1658,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
font-size: 0.78rem;
flex-shrink: 0;
opacity: 0.6;
-- transition: opacity 0.2s;
++ transition: opacity var(--ov-dur-2, 0.2s);
}
.timeline-yt-link:hover > .fa-arrow-up-right-from-square {
opacity: 1;
@@@ -1775,7 +1775,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
background: #000; aspect-ratio: 16/9;
flex-shrink: 0; max-height: 80vh;
overflow: hidden;
-- transition: aspect-ratio 0.3s ease, max-height 0.3s ease, background 0.35s ease, box-shadow 0.35s ease;
++ transition: aspect-ratio var(--ov-dur-3, 0.3s) var(--ov-ease-out, ease), max-height var(--ov-dur-3, 0.3s) var(--ov-ease-out, ease), background var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease), box-shadow var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease);
}
.stream-nsfw-gate-overlay {
@@@ -1846,7 +1846,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
opacity: 1;
transform: scale(1);
filter: blur(0);
-- transition: opacity 0.48s cubic-bezier(0.22, 1, 0.36, 1), transform 0.7s cubic-bezier(0.22, 1, 0.36, 1), filter 0.7s cubic-bezier(0.22, 1, 0.36, 1);
++ transition: opacity var(--ov-dur-4, 0.48s) var(--ov-ease-out, cubic-bezier(0.22, 1, 0.36, 1)), transform var(--ov-dur-4, 0.7s) var(--ov-ease-out, cubic-bezier(0.22, 1, 0.36, 1)), filter var(--ov-dur-4, 0.7s) var(--ov-ease-out, cubic-bezier(0.22, 1, 0.36, 1));
will-change: opacity, transform, filter;
background:
radial-gradient(circle at 18% 18%, color-mix(in srgb, var(--loader-a) 22%, transparent), transparent 28%),
@@@ -1871,7 +1871,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
filter: blur(32px) brightness(0.5) saturate(1.4);
transform: scale(1.28);
opacity: 0.92;
-- animation: loaderThumbZoom 26s ease-in-out infinite alternate;
++ animation: loaderThumbZoom 26s var(--ov-ease-in-out, ease-in-out) infinite alternate;
}
@keyframes loaderThumbZoom {
from { transform: scale(1.26) translate(-1.2%, 0.6%); }
@@@ -1918,7 +1918,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3.5px), #000 calc(100% - 3px));
mask: radial-gradient(farthest-side, transparent calc(100% - 3.5px), #000 calc(100% - 3px));
filter: drop-shadow(0 0 7px color-mix(in srgb, var(--loader-a) 55%, transparent));
-- animation: playerLoaderRotate 1.5s cubic-bezier(0.6, 0.1, 0.4, 0.9) infinite;
++ animation: playerLoaderRotate 1.5s var(--ov-ease-out, cubic-bezier(0.6, 0.1, 0.4, 0.9)) infinite;
}
.player-loader-core-dot {
@@@ -1927,7 +1927,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
border-radius: 50%;
background: radial-gradient(circle at 45% 40%, #fff, color-mix(in srgb, var(--loader-c) 78%, white 8%));
box-shadow: 0 0 18px color-mix(in srgb, var(--loader-c) 70%, transparent);
-- animation: playerLoaderPulse 2s ease-in-out infinite;
++ animation: playerLoaderPulse 2s var(--ov-ease-in-out, ease-in-out) infinite;
}
/* The mesh grid read as "tacky" — keep it as the faintest texture only. */
@@@ -1950,7 +1950,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
border-radius: inherit;
background: linear-gradient(90deg, color-mix(in srgb, var(--loader-a) 88%, white 4%), color-mix(in srgb, var(--loader-b) 88%, white 8%), color-mix(in srgb, var(--loader-c) 88%, white 4%));
box-shadow: 0 0 24px color-mix(in srgb, var(--loader-b) 36%, transparent);
-- transition: width 0.52s cubic-bezier(0.22, 1, 0.36, 1);
++ transition: width var(--ov-dur-4, 0.52s) var(--ov-ease-out, cubic-bezier(0.22, 1, 0.36, 1));
}
/* Step labels are shadowed text — no boxes. State shows via colour + glow. */
@@@ -1962,7 +1962,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
opacity: 0;
overflow: hidden;
transform: translateY(10px);
-- transition: max-height 0.45s ease, opacity 0.35s ease, transform 0.35s ease;
++ transition: max-height var(--ov-dur-4, 0.45s) var(--ov-ease-out, ease), opacity var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease), transform var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease);
pointer-events: none;
}
@@@ -1983,7 +1983,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
overflow: hidden;
transform: translateY(8px);
pointer-events: none;
-- transition: max-height 0.35s ease, opacity 0.28s ease, transform 0.35s ease, padding 0.35s ease, margin 0.35s ease;
++ transition: max-height var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease), opacity var(--ov-dur-3, 0.28s) var(--ov-ease-out, ease), transform var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease), padding var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease), margin var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease);
}
.player-loader-detail.is-visible,
@@@ -2059,7 +2059,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
/* The same backdrop as the network's player (openvibe-shared player.css), so every OpenVibe video looks alike. */
background: linear-gradient(to top, rgba(0,0,0,0.88) 0%, rgba(0,0,0,0.62) 42%, rgba(0,0,0,0.22) 75%, transparent 100%);
padding: 0; opacity: 0;
-- transition: opacity 0.3s;
++ transition: opacity var(--ov-dur-3, 0.3s);
z-index: 5;
}
.video-container:hover .video-overlay { opacity: 1; }
@@@ -2073,7 +2073,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.vc-btn {
background: none; border: none; color: #fff;
font-size: 1rem; padding: 0 8px; border-radius: 8px;
-- opacity: 0.92; transition: background 0.15s, opacity 0.15s; cursor: pointer;
++ opacity: 0.92; transition: background var(--ov-dur-2, 0.15s), opacity var(--ov-dur-2, 0.15s); cursor: pointer;
display: flex; align-items: center; justify-content: center;
min-width: 38px; min-height: 38px;
text-shadow: 0 1px 2px rgba(0,0,0,0.6);
@@@ -2100,7 +2100,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
}
.vc-volume-group .volume-slider {
/* Collapse by width only — NOT overflow:hidden, which would clip the round thumb. */
-- width: 0; opacity: 0; transition: width 0.3s, opacity 0.3s;
++ width: 0; opacity: 0; transition: width var(--ov-dur-3, 0.3s), opacity var(--ov-dur-3, 0.3s);
}
.vc-volume-group:hover .volume-slider,
.vc-volume-group .volume-slider:focus {
@@@ -2128,7 +2128,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
/* Center the thumb on the 4px track: (track - thumb) / 2 = (4 - 13) / 2. */
margin-top: -4.5px;
-- transition: transform 0.12s ease;
++ transition: transform var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease);
}
.volume-slider::-webkit-slider-thumb:hover,
.volume-slider:active::-webkit-slider-thumb { transform: scale(1.15); }
@@@ -2143,7 +2143,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
/* Muted by the browser's autoplay rule: the stream stays in view, a pill says so, and a tap anywhere on it brings the sound
in (stream-player.js showUnmuteOverlay; the same look as the shared player's "Tap for sound"). */
.unmute-overlay { position: absolute; inset: 0; z-index: 20; cursor: pointer; background: transparent; -webkit-tap-highlight-color: transparent; }
--.unmute-pill { position: absolute; left: 12px; top: 12px; display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px 8px 11px; border-radius: 999px; background: rgba(0,0,0,0.66); color: #fff; font: 650 13px/1 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); box-shadow: 0 4px 18px rgba(0,0,0,0.35); animation: unmute-pop .3s ease-out both; }
++.unmute-pill { position: absolute; left: 12px; top: 12px; display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px 8px 11px; border-radius: 999px; background: rgba(0,0,0,0.66); color: #fff; font: 650 13px/1 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); box-shadow: 0 4px 18px rgba(0,0,0,0.35); animation: unmute-pop var(--ov-dur-3, .3s) var(--ov-ease-out, ease-out) both; }
.unmute-overlay:hover .unmute-pill, .unmute-overlay:focus-visible .unmute-pill { background: var(--accent-strong, var(--accent, #3b82f6)); color: var(--on-accent-strong, #fff); }
/* Muted by the person's own choice (stream-player.js showMutedHint): the same pill as a button, for six seconds. */
.muted-hint { z-index: 21; border: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; }
@@@ -2160,7 +2160,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
position: relative;
height: 4px; background: rgba(255,255,255,0.2);
border-radius: 2px; overflow: visible;
-- transition: height 0.15s;
++ transition: height var(--ov-dur-2, 0.15s);
}
.dvr-progress-fill {
height: 100%; background: #e53e3e;
@@@ -2174,7 +2174,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
width: 14px; height: 14px; border-radius: 50%;
background: #e53e3e; border: 2px solid #fff;
box-shadow: 0 1px 4px rgba(0,0,0,0.5);
-- transition: transform 0.12s ease;
++ transition: transform var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease);
}
.dvr-progress-buffer {
position: absolute; top: 0; left: 0;
@@@ -2216,7 +2216,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
position: relative;
height: 4px; background: rgba(255,255,255,0.2);
border-radius: 2px; overflow: visible;
-- transition: height 0.15s;
++ transition: height var(--ov-dur-2, 0.15s);
}
.vc-progress-fill {
height: 100%; background: var(--accent);
@@@ -2230,7 +2230,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
width: 14px; height: 14px; border-radius: 50%;
background: var(--accent); border: 2px solid #fff;
box-shadow: 0 1px 4px rgba(0,0,0,0.5);
-- transition: transform 0.12s ease;
++ transition: transform var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease);
}
.vc-progress-buffer {
position: absolute; top: 0; left: 0;
@@@ -2296,7 +2296,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
word-break: break-word;
opacity: 1;
transform: translateY(0);
-- transition: opacity 0.45s ease, transform 0.45s ease;
++ transition: opacity var(--ov-dur-4, 0.45s) var(--ov-ease-out, ease), transform var(--ov-dur-4, 0.45s) var(--ov-ease-out, ease);
}
.fullscreen-chat-msg.is-fading {
opacity: 0;
@@@ -2333,7 +2333,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
max-width: 100%;
margin-top: 12px;
pointer-events: auto;
-- transition: opacity 0.25s ease, transform 0.25s ease;
++ transition: opacity var(--ov-dur-2, 0.25s) var(--ov-ease-out, ease), transform var(--ov-dur-2, 0.25s) var(--ov-ease-out, ease);
}
.fullscreen-chat-header span {
display: inline-flex;
@@@ -2383,7 +2383,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
}
.goal-fill {
height: 100%; background: linear-gradient(90deg, var(--accent-dark), var(--accent));
-- border-radius: 10px; transition: width 0.5s ease;
++ border-radius: 10px; transition: width var(--ov-dur-4, 0.5s) var(--ov-ease-out, ease);
}
.goal-amounts {
font-size: 0.8rem; color: var(--text-secondary);
@@@ -2426,7 +2426,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
display: flex; align-items: center; justify-content: center;
background: var(--bg-hover); border: 1px solid var(--border);
border-radius: var(--radius); color: var(--text-primary);
-- cursor: pointer; transition: all 0.1s;
++ cursor: pointer; transition: all var(--ov-dur-1, 0.1s);
font-size: 1rem;
}
@@@ -2461,7 +2461,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
padding: 6px 16px; border-radius: var(--radius);
background: none; border: 1px solid var(--border);
color: var(--text-secondary); font-size: 0.85rem;
-- transition: all 0.2s;
++ transition: all var(--ov-dur-2, 0.2s);
}
.tab-btn:hover { border-color: var(--accent-dark); }
.tab-btn.active { background: var(--accent); color: #000; border-color: var(--accent); }
@@@ -2473,7 +2473,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.chat-resize-handle {
width: 5px; cursor: col-resize;
background: transparent; flex-shrink: 0;
-- transition: background 0.15s;
++ transition: background var(--ov-dur-2, 0.15s);
position: relative; z-index: 10;
}
.chat-resize-handle:hover,
@@@ -2496,16 +2496,16 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
text-transform: uppercase; letter-spacing: 0.5px;
}
.chat-header-actions { display: flex; align-items: center; gap: 2px; }
--.chat-settings-btn { background: none; border: none; color: var(--text-muted); font-size: 1rem; cursor: pointer; padding: 4px 6px; border-radius: var(--radius-sm); transition: all 0.15s; }
++.chat-settings-btn { background: none; border: none; color: var(--text-muted); font-size: 1rem; cursor: pointer; padding: 4px 6px; border-radius: var(--radius-sm); transition: all var(--ov-dur-2, 0.15s); }
.chat-settings-btn:hover { color: var(--text-primary); background: var(--bg-tertiary); }
--.mobile-chat-close { display: none; background: none; border: none; color: var(--text-muted); font-size: 1.1rem; cursor: pointer; padding: 4px 8px; border-radius: var(--radius-sm); transition: all 0.15s; }
++.mobile-chat-close { display: none; background: none; border: none; color: var(--text-muted); font-size: 1.1rem; cursor: pointer; padding: 4px 8px; border-radius: var(--radius-sm); transition: all var(--ov-dur-2, 0.15s); }
.mobile-chat-close:hover { color: var(--text-primary); background: var(--bg-tertiary); }
/* ── Chat Users Panel ────────────────────────────────────── */
.chat-users-panel {
display: none; background: var(--bg-tertiary); border-bottom: 1px solid var(--border);
max-height: 280px; overflow-y: auto; font-size: 0.82rem;
-- animation: cspSlideDown 0.15s ease-out;
++ animation: cspSlideDown var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease-out);
}
.chat-users-panel.open { display: block; }
.chat-users-header {
@@@ -2523,7 +2523,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.chat-users-list { padding: 4px 0; }
.chat-users-row {
display: flex; align-items: center; gap: 8px; padding: 4px 12px;
-- transition: background 0.1s;
++ transition: background var(--ov-dur-1, 0.1s);
}
.chat-users-row:hover { background: var(--bg-secondary); }
.chat-users-link {
@@@ -2551,7 +2551,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.chat-settings-panel {
background: var(--bg-tertiary); border-bottom: 1px solid var(--border);
max-height: min(70vh, 500px); overflow-y: auto; font-size: 0.82rem;
-- animation: cspSlideDown 0.15s ease-out;
++ animation: cspSlideDown var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease-out);
overscroll-behavior-y: contain; scrollbar-gutter: stable;
}
@keyframes cspSlideDown {
@@@ -2616,7 +2616,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.chat-msg-mention {
background: rgba(255, 200, 50, 0.12); border-left: 2px solid var(--accent);
padding: 2px 4px 2px 6px; border-radius: 2px;
-- animation: mentionFlash 0.4s ease-out;
++ animation: mentionFlash var(--ov-dur-3, 0.4s) var(--ov-ease-out, ease-out);
}
@keyframes mentionFlash {
0% { background: rgba(255, 200, 50, 0.3); }
@@@ -2654,7 +2654,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
cursor: pointer;
z-index: 5;
line-height: 1;
-- transition: color 0.15s, border-color 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s);
}
.chat-msg-hoverable:hover .chat-reply-btn { display: inline-flex; }
.chat-msg-hoverable .chat-reply-btn:hover {
@@@ -2701,7 +2701,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
font-size: 0.78rem;
color: var(--text-secondary, #aaa);
overflow: hidden;
-- animation: chat-reply-bar-in 0.15s ease-out;
++ animation: chat-reply-bar-in var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease-out);
}
@keyframes chat-reply-bar-in {
from { opacity: 0; transform: translateY(4px); }
@@@ -2742,7 +2742,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
cursor: pointer;
font-size: 0.8rem;
border-radius: var(--radius-sm, 4px);
-- transition: color 0.15s, background 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
}
.chat-reply-bar-close:hover {
color: var(--text-primary, #fff);
@@@ -2751,7 +2751,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
/* ── Reply flash (scroll-to highlight) ───────────────────── */
.chat-msg-reply-flash {
-- animation: reply-flash 1.5s ease-out;
++ animation: reply-flash 1.5s var(--ov-ease-out, ease-out);
}
@keyframes reply-flash {
0% { background: color-mix(in srgb, var(--accent, var(--accent)) 25%, transparent); }
@@@ -2780,7 +2780,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.chat-msg.news .chat-user { color: #60a5fa !important; }
.chat-msg.news .news-link {
color: rgba(96,165,250,0.7); margin-left: 4px; font-size: 0.75em;
-- text-decoration: none; transition: color 0.15s;
++ text-decoration: none; transition: color var(--ov-dur-2, 0.15s);
}
.chat-msg.news .news-link:hover { color: #93bbfc; }
.chat-msg.soundboard {
@@@ -2892,7 +2892,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
background: var(--accent, var(--accent)); color: #fff; font-size: 0.78rem; font-weight: 600;
border: none; cursor: pointer; gap: 6px; align-items: center; justify-content: center;
box-shadow: 0 -2px 8px rgba(0,0,0,0.3);
-- transition: opacity 0.2s;
++ transition: opacity var(--ov-dur-2, 0.2s);
white-space: nowrap;
flex-shrink: 0;
}
@@@ -2929,7 +2929,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
margin-right: 4px; vertical-align: middle; position: relative;
cursor: default; flex-shrink: 0;
border: 1px solid transparent;
-- transition: transform 0.12s ease;
++ transition: transform var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease);
}
.chat-badge i { font-size: 0.66rem; line-height: 1; }
.chat-badge:hover { transform: translateY(-1px); }
@@@ -2959,14 +2959,14 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
opacity: 0; z-index: 60;
box-shadow: 0 8px 22px rgba(0,0,0,0.4);
-webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
-- transition: opacity 0.18s ease, transform 0.2s cubic-bezier(.2,.9,.3,1.25);
++ transition: opacity var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease), transform var(--ov-dur-2, 0.2s) var(--ov-ease-out, cubic-bezier(.2,.9,.3,1.25));
}
.chat-badge[data-tip]::before {
content: ''; position: absolute; bottom: calc(100% + 2px); left: 50%;
transform: translateX(-50%) translateY(5px);
border: 5px solid transparent; border-top-color: var(--border-light, var(--border));
opacity: 0; pointer-events: none; z-index: 60;
-- transition: opacity 0.18s ease, transform 0.2s cubic-bezier(.2,.9,.3,1.25);
++ transition: opacity var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease), transform var(--ov-dur-2, 0.2s) var(--ov-ease-out, cubic-bezier(.2,.9,.3,1.25));
}
.chat-badge[data-tip]:hover::after { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
.chat-badge[data-tip]:hover::before { opacity: 1; transform: translateX(-50%) translateY(0); }
@@@ -2976,7 +2976,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
display: inline-flex; align-items: center; justify-content: center; vertical-align: middle;
width: 22px; height: 22px; border-radius: 6px;
border: 1px solid currentColor; position: relative; cursor: default;
-- transition: transform 0.12s ease;
++ transition: transform var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease);
}
.staff-badge i { font-size: 0.72rem; }
/* Animated tooltip revealing "Staff - Admin" / "Staff - Mod" on hover. */
@@@ -3033,7 +3033,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
opacity: 0.5;
}
.chat-slowmode-banner.cooldown .chat-slowmode-info i {
-- animation: slowmode-pulse 1s ease-in-out infinite;
++ animation: slowmode-pulse 1s var(--ov-ease-in-out, ease-in-out) infinite;
}
.chat-send-btn.disabled {
opacity: 0.4;
@@@ -3053,7 +3053,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
background: var(--bg-input); color: var(--text-primary);
border: 1px solid var(--border); border-radius: var(--radius);
font-family: var(--font); font-size: 0.85rem; outline: none;
-- transition: border-color 0.2s;
++ transition: border-color var(--ov-dur-2, 0.2s);
resize: none; overflow-y: hidden;
line-height: 1.4;
box-sizing: border-box;
@@@ -3071,7 +3071,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
padding: 6px 8px; background: var(--accent-dark);
border: none; border-radius: var(--radius);
color: var(--text-primary); font-size: 0.85rem;
-- cursor: pointer; transition: background 0.15s;
++ cursor: pointer; transition: background var(--ov-dur-2, 0.15s);
z-index: 1;
}
.chat-send-btn:hover { background: var(--accent); color: #000; }
@@@ -3102,7 +3102,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
font-size: 0.84rem;
scrollbar-width: thin;
scrollbar-color: var(--border, #444) transparent;
-- animation: ac-fade-in 0.1s ease;
++ animation: ac-fade-in var(--ov-dur-1, 0.1s) var(--ov-ease-out, ease);
}
@keyframes ac-fade-in {
from { opacity: 0; transform: translateY(4px); }
@@@ -3119,7 +3119,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
padding: 6px 12px;
cursor: pointer;
color: var(--text-primary, #cdd6f4);
-- transition: background 0.08s;
++ transition: background var(--ov-dur-1, 0.08s);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@@ -3178,7 +3178,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
color: var(--text-muted);
font-size: 0.85rem;
cursor: pointer;
-- transition: all 0.2s;
++ transition: all var(--ov-dur-2, 0.2s);
opacity: 0.5;
}
.chat-tts-toggle.tts-active {
@@@ -3227,7 +3227,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
border-radius: var(--radius);
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
overflow: hidden;
-- animation: ctxFadeIn 0.12s ease-out;
++ animation: ctxFadeIn var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease-out);
}
@keyframes ctxFadeIn {
from { opacity: 0; transform: scale(0.95); }
@@@ -3306,7 +3306,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
color: var(--text-primary); font-size: 0.85rem;
border-radius: var(--radius-sm);
cursor: pointer; text-align: left;
-- transition: background 0.1s;
++ transition: background var(--ov-dur-1, 0.1s);
}
.ctx-btn:hover { background: var(--bg-hover); }
.ctx-btn i { width: 16px; text-align: center; color: var(--text-muted); font-size: 0.8rem; }
@@@ -3321,7 +3321,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.chat-msg-deleted {
opacity: 0;
transform: translateX(-20px);
-- transition: opacity 0.3s, transform 0.3s;
++ transition: opacity var(--ov-dur-3, 0.3s), transform var(--ov-dur-3, 0.3s);
max-height: 0;
overflow: hidden;
margin: 0;
@@@ -3331,7 +3331,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
/* ── Rename submenu ── */
.ctx-rename-group { position: relative; }
.ctx-rename-group > .ctx-btn { justify-content: flex-start; }
--.ctx-rename-arrow { margin-left: auto; font-size: 0.65rem; transition: transform 0.15s; }
++.ctx-rename-arrow { margin-left: auto; font-size: 0.65rem; transition: transform var(--ov-dur-2, 0.15s); }
.ctx-rename-group.open .ctx-rename-arrow { transform: rotate(90deg); }
.ctx-rename-submenu {
display: none; flex-direction: column;
@@@ -3413,7 +3413,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
padding: 8px 10px; background: transparent;
border: 1px solid var(--border); border-radius: var(--radius);
color: var(--text-muted); font-size: 1rem; cursor: pointer;
-- transition: color 0.15s, border-color 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s);
}
.emote-picker-btn:hover {
color: var(--accent); border-color: var(--accent);
@@@ -3467,7 +3467,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.emote-picker-item {
display: flex; align-items: center; justify-content: center;
padding: 4px; border-radius: var(--radius-sm);
-- cursor: pointer; transition: background 0.12s;
++ cursor: pointer; transition: background var(--ov-dur-1, 0.12s);
}
.emote-picker-item:hover {
background: var(--bg-hover);
@@@ -3517,7 +3517,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
position: absolute; top: calc(100% + 6px); left: 0; z-index: 40; min-width: 230px;
background: var(--bg-secondary, #17171d); border: 1px solid var(--border-color, #2a2a33);
border-radius: 10px; padding: 6px; box-shadow: 0 10px 30px rgba(0,0,0,0.45);
-- display: flex; flex-direction: column; gap: 2px; animation: pcFadeIn .16s ease-out;
++ display: flex; flex-direction: column; gap: 2px; animation: pcFadeIn var(--ov-dur-2, .16s) var(--ov-ease-out, ease-out);
}
/* `display: flex` above beats the [hidden] attribute's UA display:none — restore it,
or the menu renders open on load and the JS toggle can't close it. */
@@@ -3559,7 +3559,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.aiv-status.ok { background: rgba(52,211,153,0.18); color: #34d399; }
.aiv-status.off { background: rgba(148,148,148,0.18); color: #b0b0b0; }
.aiv-meter-bar { margin-top: 8px; height: 8px; border-radius: 999px; background: rgba(255,255,255,0.08); overflow: hidden; }
--.aiv-meter-fill { height: 100%; background: linear-gradient(90deg, #4ecdc4, #60a5fa); transition: width 0.3s; }
++.aiv-meter-fill { height: 100%; background: linear-gradient(90deg, #4ecdc4, #60a5fa); transition: width var(--ov-dur-3, 0.3s); }
.aiv-meter-fill.over { background: linear-gradient(90deg, #fb923c, #ff6b6b); }
#aiv-roster-list { display: flex; flex-direction: column; gap: 10px; }
@@@ -3582,13 +3582,13 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.btn-tiny { padding: 3px 7px; font-size: 0.72rem; line-height: 1; }
/* Clip-published chat card */
--.clip-chat-card { display: flex; gap: 10px; align-items: stretch; margin-top: 4px; padding: 8px; border-radius: 10px; background: var(--bg-tertiary); border: 1px solid var(--border); cursor: pointer; max-width: 340px; color: inherit; text-decoration: none; transition: border-color .15s, background .15s; }
++.clip-chat-card { display: flex; gap: 10px; align-items: stretch; margin-top: 4px; padding: 8px; border-radius: 10px; background: var(--bg-tertiary); border: 1px solid var(--border); cursor: pointer; max-width: 340px; color: inherit; text-decoration: none; transition: border-color var(--ov-dur-2, .15s), background var(--ov-dur-2, .15s); }
.clip-chat-card:hover { border-color: var(--accent); background: var(--bg-hover); }
.clip-chat-thumb { position: relative; width: 96px; min-width: 96px; height: 56px; border-radius: 7px; overflow: hidden; background: #000; display: flex; align-items: center; justify-content: center; }
.clip-chat-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.clip-chat-thumb-ph { color: var(--text-muted); font-size: 1.2rem; }
.clip-chat-dur { position: absolute; right: 3px; bottom: 3px; background: rgba(0,0,0,0.75); color: #fff; font-size: 0.68rem; padding: 0 4px; border-radius: 4px; }
--.clip-chat-play { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #fff; opacity: 0; transition: opacity .15s; text-shadow: 0 1px 4px rgba(0,0,0,0.6); }
++.clip-chat-play { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #fff; opacity: 0; transition: opacity var(--ov-dur-2, .15s); text-shadow: 0 1px 4px rgba(0,0,0,0.6); }
.clip-chat-card:hover .clip-chat-play { opacity: 0.95; }
/* Streamer "Chat limits" popover (message + TTS max length) */
.chat-limits-popover {
@@@ -3610,8 +3610,8 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.clip-chat-meta { display: flex; align-items: center; gap: 6px; font-size: 0.76rem; color: var(--text-muted); min-width: 0; }
.clip-chat-meta > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.clip-chat-avatar { width: 18px; height: 18px; min-width: 18px; border-radius: 50%; object-fit: cover; display: inline-flex; align-items: center; justify-content: center; font-size: 0.62rem; font-weight: 700; color: #fff; }
--.clip-chat-cta { font-size: 0.72rem; font-weight: 700; color: var(--accent); opacity: 0; max-height: 0; transition: opacity .15s ease, max-height .15s ease; }
--.clip-chat-cta i { font-size: 0.66rem; transition: transform .15s ease; }
++.clip-chat-cta { font-size: 0.72rem; font-weight: 700; color: var(--accent); opacity: 0; max-height: 0; transition: opacity var(--ov-dur-2, .15s) var(--ov-ease-out, ease), max-height var(--ov-dur-2, .15s) var(--ov-ease-out, ease); }
++.clip-chat-cta i { font-size: 0.66rem; transition: transform var(--ov-dur-2, .15s) var(--ov-ease-out, ease); }
.clip-chat-card:hover .clip-chat-cta { opacity: 1; max-height: 1.2em; }
.clip-chat-card:hover .clip-chat-cta i { transform: translateX(3px); }
/* Slightly larger, accent-tinted card so a fresh clip stands out in chat. */
@@@ -3670,7 +3670,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.gif-picker-item {
width: 100%; height: auto; max-height: 120px;
object-fit: cover; border-radius: var(--radius-sm);
-- cursor: pointer; transition: transform 0.12s, box-shadow 0.12s;
++ cursor: pointer; transition: transform var(--ov-dur-1, 0.12s), box-shadow var(--ov-dur-1, 0.12s);
}
.gif-picker-item:hover { transform: scale(1.05); box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
.gif-picker-footer {
@@@ -3752,7 +3752,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
/* Spendable Vibes badge on the profile card */
/* Vibes history modal */
--.bucks-log-overlay { position: fixed; inset: 0; z-index: 4000; background: rgba(0,0,0,0.55); display: flex; align-items: center; justify-content: center; padding: 16px; animation: fadeIn .15s ease; }
++.bucks-log-overlay { position: fixed; inset: 0; z-index: 4000; background: rgba(0,0,0,0.55); display: flex; align-items: center; justify-content: center; padding: 16px; animation: fadeIn var(--ov-dur-2, .15s) var(--ov-ease-out, ease); }
.bucks-log-head h3 { margin: 0; font-size: 1rem; color: var(--accent); display: flex; align-items: center; gap: 8px; }
.bucks-log-main { flex: 1; min-width: 0; }
@@@ -3802,7 +3802,7 @@ a.pulse-card:hover { border-color: var(--accent); transform: translateY(-2px); }
background: var(--bg-input); color: var(--text-primary);
border: 1px solid var(--border); border-radius: var(--radius);
font-size: 0.9rem; outline: none;
-- transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
++ transition: border-color var(--ov-dur-2, 0.15s), box-shadow var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
}
.form-input:focus {
border-color: var(--accent-dark);
@@@ -3907,7 +3907,7 @@ textarea.form-input { resize: vertical; min-height: 60px; }
}
.admin-sensitive-textarea {
-- transition: filter 0.15s ease;
++ transition: filter var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
.admin-sensitive-textarea.is-masked {
@@@ -4007,11 +4007,11 @@ body :is(.modal-overlay, .bc-ws-confirm-overlay, .bc-modal, .bc-perm-overlay, .b
display: flex;
align-items: center;
gap: 8px;
-- animation: toast-in 0.22s ease;
++ animation: toast-in var(--ov-dur-2, 0.22s) var(--ov-ease-out, ease);
max-width: min(280px, calc(100vw - 24px));
backdrop-filter: blur(12px);
opacity: 0.96;
-- transition: opacity .22s ease, transform .22s ease, border-color .15s ease, box-shadow .15s ease;
++ transition: opacity var(--ov-dur-2, .22s) var(--ov-ease-out, ease), transform var(--ov-dur-2, .22s) var(--ov-ease-out, ease), border-color var(--ov-dur-2, .15s) var(--ov-ease-out, ease), box-shadow var(--ov-dur-2, .15s) var(--ov-ease-out, ease);
}
.toast .toast-text { -webkit-user-select: text; user-select: text; }
.toast.toast-copied {
@@@ -4116,7 +4116,7 @@ body :is(.modal-overlay, .bc-ws-confirm-overlay, .bc-modal, .bc-perm-overlay, .b
z-index: 901;
box-shadow: 0 4px 16px rgba(0,0,0,0.5);
cursor: pointer;
-- transition: background 0.2s, transform 0.2s;
++ transition: background var(--ov-dur-2, 0.2s), transform var(--ov-dur-2, 0.2s);
}
.mobile-chat-toggle:hover { background: var(--accent); }
.mobile-chat-toggle:active { transform: scale(0.92); }
@@@ -4172,7 +4172,7 @@ body :is(.modal-overlay, .bc-ws-confirm-overlay, .bc-modal, .bc-perm-overlay, .b
background: var(--bg-secondary);
z-index: 1002;
transform: translateY(100%);
-- transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
++ transition: transform var(--ov-dur-3, 0.3s) var(--ov-ease-out, cubic-bezier(0.4, 0, 0.2, 1));
border-radius: 16px 16px 0 0;
box-shadow: 0 -8px 32px rgba(0,0,0,0.45);
}
@@@ -4398,7 +4398,7 @@ body :is(.modal-overlay, .bc-ws-confirm-overlay, .bc-modal, .bc-perm-overlay, .b
background: transparent;
color: var(--text-muted); font-size: 0.8rem;
white-space: nowrap; cursor: pointer;
-- transition: color 0.15s, background 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
font-family: var(--font);
position: relative; flex-shrink: 0;
border-bottom: 2px solid transparent;
@@@ -4451,10 +4451,10 @@ body :is(.modal-overlay, .bc-ws-confirm-overlay, .bc-modal, .bc-perm-overlay, .b
background: rgba(0, 0, 0, 0.75);
color: var(--text-muted); gap: 14px;
font-size: 0.88rem; backdrop-filter: blur(4px);
-- transition: opacity 0.2s;
++ transition: opacity var(--ov-dur-2, 0.2s);
}
.stream-switch-overlay.visible {
-- display: flex; animation: fadeIn 0.15s ease;
++ display: flex; animation: fadeIn var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
.stream-switch-spinner {
position: relative;
@@@ -4466,7 +4466,7 @@ body :is(.modal-overlay, .bc-ws-confirm-overlay, .bc-modal, .bc-perm-overlay, .b
0 0 0 1px rgba(255,255,255,0.1),
0 18px 32px rgba(0,0,0,0.3),
inset 0 0 26px rgba(255,255,255,0.08);
-- animation: playerLoaderMorph 5.8s ease-in-out infinite alternate;
++ animation: playerLoaderMorph 5.8s var(--ov-ease-in-out, ease-in-out) infinite alternate;
}
/* "spin" is kept as a name for older rules, but it breathes: nothing spins (design lint, plan L45). */
@keyframes spin { 50% { opacity: 0.45; transform: scale(0.85); } }
@@@ -4521,7 +4521,7 @@ body :is(.modal-overlay, .bc-ws-confirm-overlay, .bc-modal, .bc-perm-overlay, .b
color: var(--text-muted); font-size: 0.9rem; font-weight: 650;
padding: 10px 12px; border-radius: 8px 8px 0 0; position: relative; flex: none; white-space: nowrap;
display: inline-flex; align-items: center; gap: 7px;
-- transition: color 0.15s ease, background 0.15s ease;
++ transition: color var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease), background var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
.ch-tab i { font-size: 0.85em; opacity: 0.85; }
.ch-tab:hover { color: var(--text-primary); background: color-mix(in srgb, var(--accent) 8%, transparent); }
@@@ -4542,12 +4542,12 @@ body :is(.modal-overlay, .bc-ws-confirm-overlay, .bc-modal, .bc-perm-overlay, .b
.ch-tab-badge[hidden] { display: none; }
.ch-tab.active .ch-tab-badge { color: var(--accent); background: color-mix(in srgb, var(--accent) 22%, transparent); }
.ch-tab-panel { display: none; }
--.ch-tab-panel.active { display: block; animation: chTabFade 0.2s ease; }
++.ch-tab-panel.active { display: block; animation: chTabFade var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease); }
@keyframes chTabFade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.ch-panel-title { font-size: 1.05rem; font-weight: 700; display: inline-flex; align-items: center; gap: 8px; }
.ch-panel-title i { color: var(--accent); opacity: 0.9; }
/* Save fades in only once something changes. */
--.ch-about-save-btn { opacity: 0; transform: translateY(-2px); pointer-events: none; transition: opacity .18s ease, transform .18s ease; }
++.ch-about-save-btn { opacity: 0; transform: translateY(-2px); pointer-events: none; transition: opacity var(--ov-dur-2, .18s) var(--ov-ease-out, ease), transform var(--ov-dur-2, .18s) var(--ov-ease-out, ease); }
.ch-about-bio a { color: var(--accent); }
/* 12-col responsive panel grid; each panel spans a width. */
.ch-panel-w-sm { grid-column: span 3; }
@@@ -4574,7 +4574,7 @@ body :is(.modal-overlay, .bc-ws-confirm-overlay, .bc-modal, .bc-perm-overlay, .b
padding: 8px 10px;
font: inherit;
font-size: 0.88rem;
-- transition: border-color .15s, box-shadow .15s;
++ transition: border-color var(--ov-dur-2, .15s), box-shadow var(--ov-dur-2, .15s);
}
.ch-about-edit-bio textarea:focus,
.ch-panel-edit input:focus,
@@@ -4639,8 +4639,8 @@ body :is(.modal-overlay, .bc-ws-confirm-overlay, .bc-modal, .bc-perm-overlay, .b
/* Cycler floated OVER a streamer's offline image (dismissable to reveal the image). */
.ch-offline-overlay { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: flex-end; padding: 0 0 clamp(14px, 3vh, 26px); pointer-events: none; background: linear-gradient(to top, rgba(0,0,0,.80) 0%, rgba(0,0,0,.5) 34%, rgba(0,0,0,0) 62%); }
.ch-offline-overlay > * { pointer-events: auto; }
--.ch-offline-overlay-close { position: absolute; top: 12px; right: 12px; width: 34px; height: 34px; border-radius: 50%; border: 1px solid rgba(255,255,255,.24); background: rgba(0,0,0,.55); color: #fff; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; font-size: 0.95rem; backdrop-filter: blur(4px); transition: background .15s, border-color .15s; z-index: 2; }
--.ch-offline-reopen { position: absolute; bottom: 12px; right: 12px; display: none; align-items: center; gap: 7px; padding: 7px 13px; border-radius: 999px; border: 1px solid rgba(255,255,255,.24); background: rgba(0,0,0,.6); color: #fff; font-size: 0.78rem; font-weight: 700; cursor: pointer; backdrop-filter: blur(4px); transition: background .15s, border-color .15s; z-index: 2; }
++.ch-offline-overlay-close { position: absolute; top: 12px; right: 12px; width: 34px; height: 34px; border-radius: 50%; border: 1px solid rgba(255,255,255,.24); background: rgba(0,0,0,.55); color: #fff; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; font-size: 0.95rem; backdrop-filter: blur(4px); transition: background var(--ov-dur-2, .15s), border-color var(--ov-dur-2, .15s); z-index: 2; }
++.ch-offline-reopen { position: absolute; bottom: 12px; right: 12px; display: none; align-items: center; gap: 7px; padding: 7px 13px; border-radius: 999px; border: 1px solid rgba(255,255,255,.24); background: rgba(0,0,0,.6); color: #fff; font-size: 0.78rem; font-weight: 700; cursor: pointer; backdrop-filter: blur(4px); transition: background var(--ov-dur-2, .15s), border-color var(--ov-dur-2, .15s); z-index: 2; }
.ch-offline-reopen.show { display: inline-flex; }
.ch-offline-reopen i { color: var(--accent); }
/* Over an image the cycler title/chips sit on a dark scrim — force light text regardless of theme. */
@@@ -4762,10 +4762,10 @@ body :is(.modal-overlay, .bc-ws-confirm-overlay, .bc-modal, .bc-perm-overlay, .b
/* Collapsible overview sections (collapsed by default; header toggles open) */
.uai-collapsible .uai-collapse-toggle { cursor: pointer; user-select: none; }
.uai-collapsible .uai-collapse-toggle:hover { color: var(--text-primary); }
--.uai-collapse-caret { margin-left: 6px; font-size: 0.72em; opacity: 0.6; transition: transform 0.2s ease; }
++.uai-collapse-caret { margin-left: 6px; font-size: 0.72em; opacity: 0.6; transition: transform var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease); }
.uai-collapsible.uai-open .uai-collapse-caret { transform: rotate(180deg); }
.uai-collapse-preview { font-size: 0.82rem; line-height: 1.4; color: var(--text-secondary); opacity: 0.75; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
--.uai-collapse-body { max-height: 0; overflow: hidden; transition: max-height 0.28s ease; }
++.uai-collapse-body { max-height: 0; overflow: hidden; transition: max-height var(--ov-dur-3, 0.28s) var(--ov-ease-out, ease); }
.uai-collapsible.uai-open .uai-collapse-body { max-height: 1600px; }
.uai-collapsible.uai-open .uai-collapse-preview { display: none; }
/* Streamer overview leads the popover, set apart from the chat insight below it. */
@@@ -4866,7 +4866,7 @@ button.uai-tl-more { background: none; border: none; cursor: pointer; padding: 0
.clip-preview-play-btn {
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
background: rgba(0,0,0,0.3); border: none; color: #fff; font-size: 2rem;
-- cursor: pointer; transition: background 0.2s; opacity: 0;
++ cursor: pointer; transition: background var(--ov-dur-2, 0.2s); opacity: 0;
}
/* Timeline section */
@@@ -4874,7 +4874,7 @@ button.uai-tl-more { background: none; border: none; cursor: pointer; padding: 0
position: absolute; top: 50%; transform: translate(-50%, -50%);
width: 20px; height: 28px; cursor: ew-resize; z-index: 3;
display: flex; align-items: center; justify-content: center;
-- transition: left 0.05s;
++ transition: left var(--ov-dur-1, 0.05s);
}
.clip-playhead {
position: absolute; top: -4px; width: 2px; height: 16px;
@@@ -4931,7 +4931,7 @@ button.uai-tl-more { background: none; border: none; cursor: pointer; padding: 0
.comment-item {
display: flex; gap: 12px; padding: 12px 0;
border-bottom: 1px solid var(--border);
-- transition: background 0.15s ease;
++ transition: background var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
.comment-item:hover { background: rgba(255,255,255,0.02); }
.comment-item:last-child { border-bottom: none; }
@@@ -5005,7 +5005,7 @@ button.uai-tl-more { background: none; border: none; cursor: pointer; padding: 0
border-radius: var(--radius);
cursor: pointer;
overflow: hidden;
-- transition: border-color 0.2s, transform 0.15s;
++ transition: border-color var(--ov-dur-2, 0.2s), transform var(--ov-dur-2, 0.15s);
}
.theme-card-preview {
height: 64px;
@@@ -5161,7 +5161,7 @@ body.theme-light .btn-outline:hover {
padding: 6px 8px; background: transparent;
border: 1px solid #6c3fb5; border-radius: var(--radius);
color: #b388ff; font-size: 0.85rem; cursor: pointer;
-- transition: color 0.15s, border-color 0.15s, background 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
line-height: 1;
}
.btn-openvibe-coins-mini:hover {
@@@ -5198,7 +5198,7 @@ body.theme-light .btn-outline:hover {
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text-primary);
-- cursor: pointer; transition: all 0.2s;
++ cursor: pointer; transition: all var(--ov-dur-2, 0.2s);
font-size: 0.8rem; text-align: center;
}
.reward-btn:hover {
@@@ -5394,7 +5394,7 @@ body.theme-light .btn-outline:hover {
font-size: 1.3rem;
font-weight: 700;
border: 3px solid transparent;
-- transition: border-color 0.15s ease, box-shadow 0.15s ease;
++ transition: border-color var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease), box-shadow var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
.call-avatar.speaking, .call-participant-tile.is-speaking .call-avatar { border-color: #22c55e; }
/* The ring around a speaker follows their audio level (--lvl, 0–1, set by call.js) with a transform
@@@ -5405,14 +5405,14 @@ body.theme-light .btn-outline:hover {
content: ''; position: absolute; inset: -6px; border-radius: 50%; pointer-events: none;
border: 2px solid rgba(34, 197, 94, 0.9); box-shadow: 0 0 14px rgba(34, 197, 94, 0.45);
opacity: 0; transform: scale(0.9);
-- transition: transform 90ms linear, opacity 120ms linear;
++ transition: transform var(--ov-dur-1, 90ms) linear, opacity var(--ov-dur-1, 120ms) linear;
}
.call-participant-tile.is-speaking .call-avatar::after { opacity: calc(0.35 + var(--lvl) * 0.65); transform: scale(calc(1 + var(--lvl) * 0.18)); }
.call-participant-tile.is-connecting { opacity: 0.6; }
--.call-participant-tile.is-connecting .call-avatar { animation: callTileConnecting 1.4s ease-in-out infinite; }
++.call-participant-tile.is-connecting .call-avatar { animation: callTileConnecting 1.4s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes callTileConnecting { 50% { opacity: 0.45; } }
--.call-participant-tile.is-entering { animation: callTileIn 220ms cubic-bezier(.2, .9, .3, 1.2); }
--.call-participant-tile.is-leaving { animation: callTileOut 240ms ease-in forwards; pointer-events: none; }
++.call-participant-tile.is-entering { animation: callTileIn var(--ov-dur-2, 220ms) var(--ov-ease-out, cubic-bezier(.2, .9, .3, 1.2)); }
++.call-participant-tile.is-leaving { animation: callTileOut var(--ov-dur-2, 240ms) ease-in forwards; pointer-events: none; }
@keyframes callTileIn { from { opacity: 0; transform: scale(0.86); } }
@keyframes callTileOut { to { opacity: 0; transform: scale(0.86); } }
@media (prefers-reduced-motion: reduce) {
@@@ -5514,7 +5514,7 @@ body.theme-light .btn-outline:hover {
justify-content: center;
font-size: 0.7rem;
opacity: 0;
-- transition: opacity 0.15s;
++ transition: opacity var(--ov-dur-2, 0.15s);
z-index: 3;
}
.call-participant-tile:hover .call-tile-menu-btn {
@@@ -5548,7 +5548,7 @@ body.theme-light .btn-outline:hover {
justify-content: center;
font-size: 0.7rem;
opacity: 0;
-- transition: opacity 0.15s;
++ transition: opacity var(--ov-dur-2, 0.15s);
z-index: 3;
}
.call-participant-tile:hover .call-tile-popout-btn {
@@@ -5603,7 +5603,7 @@ body.theme-light .btn-outline:hover {
cursor: pointer;
font-size: 0.8rem;
text-align: left;
-- transition: background 0.12s;
++ transition: background var(--ov-dur-1, 0.12s);
}
.call-peer-menu-item:hover {
background: var(--bg-tertiary, rgba(255,255,255,0.05));
@@@ -5684,7 +5684,7 @@ body.theme-light .btn-outline:hover {
font-size: 1rem;
background: var(--bg-primary);
color: var(--text-primary, #fff);
-- transition: background 0.15s, color 0.15s;
++ transition: background var(--ov-dur-2, 0.15s), color var(--ov-dur-2, 0.15s);
gap: 4px;
}
.call-ctrl-btn:hover {
@@@ -5805,7 +5805,7 @@ body.theme-light .btn-outline:hover {
}
.call-connected-badge i {
font-size: 0.5rem;
-- animation: call-pulse 2s ease-in-out infinite;
++ animation: call-pulse 2s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes call-pulse {
0%, 100% { opacity: 1; }
@@@ -5913,7 +5913,7 @@ body.theme-light .btn-outline:hover {
margin-bottom: 0;
border-radius: 12px;
overflow: hidden;
-- transition: max-height 0.35s ease;
++ transition: max-height var(--ov-dur-3, 0.35s) var(--ov-ease-out, ease);
}
.home-changelog-wrapper.collapsed {
max-height: 400px;
@@@ -5963,7 +5963,7 @@ body.theme-light .btn-outline:hover {
gap: 10px;
padding: 8px 12px;
border-radius: 6px;
-- transition: background 0.15s;
++ transition: background var(--ov-dur-2, 0.15s);
}
.update-entry:hover {
background: var(--bg-tertiary, rgba(255,255,255,0.04));
@@@ -6025,7 +6025,7 @@ body.theme-light .btn-outline:hover {
border-radius: 6px;
cursor: pointer;
font-size: 0.8rem;
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
}
.pastes-filter-btn.active,
.pastes-filter-btn:hover {
@@@ -6068,7 +6068,7 @@ body.theme-light .btn-outline:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
-- transition: transform 0.15s, border-color 0.15s, box-shadow 0.15s;
++ transition: transform var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s), box-shadow var(--ov-dur-2, 0.15s);
}
.paste-card:hover {
transform: translateY(-2px);
@@@ -6257,7 +6257,7 @@ body.theme-light .btn-outline:hover {
padding: 5px 12px;
border-radius: 999px;
cursor: pointer;
-- transition: background 0.15s ease, color 0.15s ease;
++ transition: background var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease), color var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
.sort-btn:hover { color: var(--text-primary); }
.sort-btn.active {
@@@ -6276,7 +6276,7 @@ body.theme-light .btn-outline:hover {
#page-documentation ul, #page-documentation ol { margin: 0.5rem 0 1rem 1.5rem; line-height: 1.7; }
/* Endpoint tables have nowrap first columns; on a phone they were up to 100px wider than the screen
and scrolled the whole page sideways. Below tablet width a table scrolls inside its own box. */
--.doc-copy-btn { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.5rem 1rem; background: var(--accent-strong, var(--accent, #3472d8)); color: var(--on-accent-strong, var(--on-accent, #fff)); border: none; border-radius: var(--radius); cursor: pointer; font-size: 0.9rem; font-weight: 600; transition: opacity 0.15s; }
++.doc-copy-btn { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.5rem 1rem; background: var(--accent-strong, var(--accent, #3472d8)); color: var(--on-accent-strong, var(--on-accent, #fff)); border: none; border-radius: var(--radius); cursor: pointer; font-size: 0.9rem; font-weight: 600; transition: opacity var(--ov-dur-2, 0.15s); }
.doc-copy-btn:hover { opacity: 0.85; }
.doc-vibe-hero { background: linear-gradient(135deg, var(--surface) 0%, rgba(var(--accent-rgb, 99,102,241), 0.08) 100%); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.5rem; margin-bottom: 2rem; }
.doc-vibe-hero h2 { margin-top: 0 !important; border: none !important; padding: 0 !important; }
@@@ -6338,7 +6338,7 @@ body.theme-light .btn-outline:hover {
color: var(--text-secondary);
font-size: 0.85rem;
cursor: pointer;
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
}
.paste-like-btn:hover {
background: var(--bg-tertiary);
@@@ -6506,7 +6506,7 @@ body.theme-light .btn-outline:hover {
aspect-ratio: 1 / 1;
background: var(--bg-card, #1c1c22);
cursor: pointer;
-- transition: border-color .15s;
++ transition: border-color var(--ov-dur-2, .15s);
}
.settings-avatar-hist-item:hover { border-color: var(--accent, var(--accent)); }
.settings-avatar-hist-item.active { border-color: var(--accent, var(--accent)); }
@@@ -6648,7 +6648,7 @@ textarea.paste-textarea {
border-radius: 12px;
text-align: center;
color: var(--text-secondary);
-- transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
++ transition: border-color var(--ov-dur-2, 0.2s), background var(--ov-dur-2, 0.2s), box-shadow var(--ov-dur-2, 0.2s);
background: rgba(var(--primary-rgb, 99,102,241), 0.03);
}
.screenshot-upload-zone:hover,
@@@ -6668,7 +6668,7 @@ textarea.paste-textarea {
.screenshot-upload-icon {
font-size: 2.5rem;
opacity: 0.45;
-- transition: opacity 0.2s, transform 0.2s;
++ transition: opacity var(--ov-dur-2, 0.2s), transform var(--ov-dur-2, 0.2s);
}
.screenshot-upload-zone:hover .screenshot-upload-icon {
opacity: 0.75;
@@@ -6894,7 +6894,7 @@ textarea.paste-textarea {
color: #fbbf24;
font-size: 0.8rem;
cursor: pointer;
-- transition: background 0.12s, color 0.12s;
++ transition: background var(--ov-dur-1, 0.12s), color var(--ov-dur-1, 0.12s);
display: flex;
align-items: center;
gap: 5px;
@@@ -6996,7 +6996,7 @@ textarea.paste-textarea {
cursor: pointer;
padding: 4px 8px;
border-radius: var(--radius-sm);
-- transition: color 0.15s, background 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
}
.modal-close:hover {
color: var(--text-primary);
@@@ -7058,7 +7058,7 @@ textarea.paste-textarea {
transform: translateX(-50%);
border-radius: 999px;
border: 2px solid color-mix(in srgb, var(--accent) 35%, transparent);
-- animation: vc-call-ring-pulse 1.7s ease-out infinite;
++ animation: vc-call-ring-pulse 1.7s var(--ov-ease-out, ease-out) infinite;
}
@keyframes vc-call-ring-pulse {
0% { opacity: 0.8; transform: translateX(-50%) scale(0.78); }
@@@ -7120,7 +7120,7 @@ textarea.paste-textarea {
align-items: center;
justify-content: center;
gap: 8px;
-- transition: transform 0.16s ease, opacity 0.16s ease;
++ transition: transform var(--ov-dur-2, 0.16s) var(--ov-ease-out, ease), opacity var(--ov-dur-2, 0.16s) var(--ov-ease-out, ease);
}
.vc-call-btn:hover { transform: translateY(-1px); }
.vc-call-btn:active { transform: translateY(0) scale(0.98); }
@@@ -7155,7 +7155,7 @@ textarea.paste-textarea {
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 42%, transparent);
-- animation: vc-call-toast-ping 1.35s ease-out infinite;
++ animation: vc-call-toast-ping 1.35s var(--ov-ease-out, ease-out) infinite;
flex-shrink: 0;
}
@keyframes vc-call-toast-ping {
@@@ -7193,7 +7193,7 @@ textarea.paste-textarea {
/* Error shake for form inputs */
.form-input.error {
border-color: var(--danger) !important;
-- animation: vc-shake 0.3s ease;
++ animation: vc-shake var(--ov-dur-3, 0.3s) var(--ov-ease-out, ease);
}
@keyframes vc-shake {
0%, 100% { transform: translateX(0); }
@@@ -7298,7 +7298,7 @@ textarea.paste-textarea {
}
/* Phones, scrolling down: the floating buttons step aside (draggable-fab.js) and come back on the way up. */
--body .messenger-toggle, body .floating-chat-fab, body .mobile-chat-toggle { transition: transform 0.2s, opacity 0.2s, background 0.15s; }
++body .messenger-toggle, body .floating-chat-fab, body .mobile-chat-toggle { transition: transform var(--ov-dur-2, 0.2s), opacity var(--ov-dur-2, 0.2s), background var(--ov-dur-2, 0.15s); }
body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fabs-tucked .mobile-chat-toggle { opacity: 0; transform: scale(0.6); pointer-events: none; }
@media (prefers-reduced-motion: reduce) { body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fabs-tucked .mobile-chat-toggle { transform: none; } }
@@@ -7320,7 +7320,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
display: flex;
align-items: center;
justify-content: center;
-- transition: transform 0.15s, background 0.15s;
++ transition: transform var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
}
.floating-chat-fab:hover {
transform: scale(1.08);
@@@ -7341,7 +7341,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
70% { transform: scale(0.96); }
100% { transform: scale(1); }
}
--.fab-bounce { animation: fabBounceBack 0.5s cubic-bezier(.34,1.56,.64,1); }
++.fab-bounce { animation: fabBounceBack var(--ov-dur-4, 0.5s) var(--ov-ease-spring, cubic-bezier(.34,1.56,.64,1)); }
.fcw-unread-badge {
position: absolute;
top: -4px;
@@@ -7618,7 +7618,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
border-radius: 6px;
cursor: pointer;
font-size: 0.82rem;
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
}
.image-censor-tool:hover {
background: var(--bg-primary, #0e0e1a);
@@@ -7650,7 +7650,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
font-size: 0.72rem;
border-radius: 4px;
opacity: 0.5;
-- transition: opacity 0.15s;
++ transition: opacity var(--ov-dur-2, 0.15s);
}
.btn-xs:hover { opacity: 1; }
.paste-view-title-row { display: flex; align-items: center; gap: 8px; }
@@@ -7761,7 +7761,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
background: var(--bg-secondary);
z-index: 1002;
transform: translateY(100%);
-- transition: transform 0.3s ease;
++ transition: transform var(--ov-dur-3, 0.3s) var(--ov-ease-out, ease);
border-radius: 16px 16px 0 0;
box-shadow: 0 -8px 32px rgba(0,0,0,0.15);
}
@@@ -7818,7 +7818,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
font-size: 0.75rem; font-weight: 500;
white-space: nowrap;
opacity: 0; pointer-events: none;
-- transition: opacity 0.15s;
++ transition: opacity var(--ov-dur-2, 0.15s);
z-index: 1002;
}
.nav-link[data-tooltip]:hover::after { opacity: 1; }
@@@ -8083,7 +8083,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent);
text-underline-offset: 2px;
cursor: pointer;
-- transition: color 0.15s, text-decoration-color 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), text-decoration-color var(--ov-dur-2, 0.15s);
word-break: break-all;
}
.chat-link:hover {
@@@ -8107,7 +8107,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
padding: 0;
max-width: 440px; width: 92%;
box-shadow: 0 16px 48px rgba(0,0,0,0.5);
-- animation: linkTrustIn 0.18s ease-out;
++ animation: linkTrustIn var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease-out);
overflow: hidden;
}
@keyframes linkTrustIn {
@@@ -8169,7 +8169,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
cursor: pointer; padding: 4px 6px;
border-radius: 4px;
font-size: 0.85rem;
-- transition: color 0.15s, background 0.15s;
++ transition: color var(--ov-dur-2, 0.15s), background var(--ov-dur-2, 0.15s);
}
.link-trust-copy:hover {
color: var(--text-primary, #eee);
@@@ -8188,7 +8188,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
border-radius: 8px;
font-size: 0.82rem; font-weight: 500;
cursor: pointer;
-- transition: all 0.15s;
++ transition: all var(--ov-dur-2, 0.15s);
background: var(--bg-secondary, #2e2e38);
color: var(--text-primary, #eee);
}
@@@ -8229,7 +8229,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
overflow: hidden;
padding: 4px;
-- animation: ctxFadeIn 0.12s ease-out;
++ animation: ctxFadeIn var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease-out);
}
.link-ctx-btn {
@@@ -8241,7 +8241,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
font-size: 0.82rem;
cursor: pointer;
border-radius: 6px;
-- transition: background 0.12s;
++ transition: background var(--ov-dur-1, 0.12s);
text-align: left;
}
.link-ctx-btn:hover {
@@@ -8278,7 +8278,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
display: flex; flex-direction: column;
overflow: hidden;
-- animation: linkTrustIn 0.2s ease-out;
++ animation: linkTrustIn var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease-out);
}
.link-preview-header {
@@@ -8313,7 +8313,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
color: var(--text-muted, #777);
cursor: pointer; padding: 2px 4px;
font-size: 0.8rem;
-- transition: color 0.15s;
++ transition: color var(--ov-dur-2, 0.15s);
}
.link-preview-copy:hover { color: var(--text-primary); }
@@@ -8329,7 +8329,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
color: var(--text-primary, #eee);
font-size: 0.78rem;
cursor: pointer;
-- transition: all 0.12s;
++ transition: all var(--ov-dur-1, 0.12s);
display: flex; align-items: center; gap: 5px;
}
.link-preview-btn:hover {
@@@ -8466,7 +8466,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
font-size: 0.85rem; color: var(--text-primary);
cursor: pointer; padding: 2px 4px;
border-radius: 3px;
-- transition: background 0.15s;
++ transition: background var(--ov-dur-2, 0.15s);
}
.at-ip-address:hover { background: var(--bg-hover); }
.at-ip-hits {
@@@ -8476,7 +8476,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
}
.at-live-badge {
font-size: 0.7rem; color: #2ecc71; font-weight: 600;
-- margin-left: 8px; animation: at-pulse 1.5s ease-in-out infinite;
++ margin-left: 8px; animation: at-pulse 1.5s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes at-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
.at-geo {
@@@ -8503,7 +8503,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
background: var(--bg-secondary); border: 1px solid var(--border);
border-radius: var(--radius); padding: 8px 12px;
margin-bottom: 4px;
-- transition: border-color 0.15s;
++ transition: border-color var(--ov-dur-2, 0.15s);
}
.at-alt-card:hover { border-color: var(--accent); }
.at-alt-banned { border-left: 3px solid #e74c3c; }
@@@ -8617,7 +8617,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
padding: 10px 12px;
cursor: pointer;
border-radius: 0;
-- transition: background 0.15s;
++ transition: background var(--ov-dur-2, 0.15s);
position: relative;
}
.bc-ws-item:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
@@@ -8879,7 +8879,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
box-shadow: none;
opacity: 1;
cursor: pointer;
-- transition: background 0.15s, color 0.15s, border-color 0.15s;
++ transition: background var(--ov-dur-2, 0.15s), color var(--ov-dur-2, 0.15s), border-color var(--ov-dur-2, 0.15s);
}
.chat-tools-row > button:hover,
.pc-tools-row > button:hover {
@@@ -8957,7 +8957,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
border-radius: var(--radius);
background: var(--bg-tertiary);
cursor: pointer;
-- transition: border-color 0.12s, background 0.12s;
++ transition: border-color var(--ov-dur-1, 0.12s), background var(--ov-dur-1, 0.12s);
}
.emote-sound-tile:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.emote-sound-bang { font-weight: 800; color: var(--accent); font-size: 0.95em; }
@@@ -8968,7 +8968,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
/* ── Stream-card info row: uptime + viewers on the right, expandable AI overview ── */
.stream-card-metaright i { margin-right: 3px; opacity: 0.85; }
/* AI overview: clamped to 2 lines, click to expand with a smooth reveal. */
--.stream-card-ai { cursor: pointer; max-height: 2.7em; transition: max-height 0.5s ease; }
++.stream-card-ai { cursor: pointer; max-height: 2.7em; transition: max-height var(--ov-dur-4, 0.5s) var(--ov-ease-out, ease); }
.stream-card-ai.expanded { -webkit-line-clamp: unset; max-height: 4000px; overflow-y: auto; }
/* ── Stream page (TRUE desktop only, ≥1181px — matches the JS mobile threshold of
@@@ -8981,7 +8981,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
0% { opacity: 0; transform: scale(0.94) translateY(8px); box-shadow: 0 0 0 3px var(--accent, var(--accent)), var(--shadow-soft); }
100% { opacity: 1; transform: none; box-shadow: var(--shadow-soft); }
}
--.stream-card-thumb img { transition: opacity 0.4s; }
++.stream-card-thumb img { transition: opacity var(--ov-dur-3, 0.4s); }
/* ── Home hero: animated aurora glow + shimmering title ── */
.hero { position: relative; overflow: hidden; }
@@@ -9038,7 +9038,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
color: var(--accent); font-size: 1.5rem; cursor: pointer;
box-shadow: 0 8px 24px rgba(0,0,0,.5);
display: flex; align-items: center; justify-content: center;
-- animation: cpChestPop .4s ease, cpChestPulse 1.6s ease-in-out infinite;
++ animation: cpChestPop var(--ov-dur-3, .4s) var(--ov-ease-out, ease), cpChestPulse 1.6s var(--ov-ease-in-out, ease-in-out) infinite;
}
.cp-bonus-chest:hover { transform: scale(1.08); }
@keyframes cpChestPop { from { transform: scale(0) rotate(-20deg); } to { transform: none; } }
@@@ -9065,7 +9065,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
display: flex; align-items: baseline; gap: 10px; width: 100%; text-align: left;
background: transparent; border: none; border-radius: 6px; cursor: pointer;
padding: 5px 8px; color: var(--text-secondary); font-size: 0.85rem; line-height: 1.5;
-- transition: background 0.12s ease, color 0.12s ease;
++ transition: background var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease), color var(--ov-dur-1, 0.12s) var(--ov-ease-out, ease);
}
.ts-time {
flex-shrink: 0; font-variant-numeric: tabular-nums; font-weight: 700;
@@@ -9080,7 +9080,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
background: color-mix(in srgb, var(--accent) 8%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
cursor: pointer;
-- transition: background 0.15s ease, border-color 0.15s ease;
++ transition: background var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease), border-color var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
.card-ai-overview:hover {
background: color-mix(in srgb, var(--accent) 15%, transparent);
@@@ -9106,7 +9106,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
.card-ai-toggle {
position: absolute; right: 8px; bottom: 7px;
cursor: pointer; font-size: 0.64rem; color: var(--accent); opacity: 0.8;
-- transition: transform 0.2s ease, opacity 0.15s ease;
++ transition: transform var(--ov-dur-2, 0.2s) var(--ov-ease-out, ease), opacity var(--ov-dur-2, 0.15s) var(--ov-ease-out, ease);
}
.card-ai-toggle:hover { opacity: 1; }
.card-ai-overview.expanded .card-ai-toggle { transform: rotate(180deg); opacity: 1; }
@@@ -9143,7 +9143,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
background: linear-gradient(180deg, rgba(0,0,0,0), var(--bg-tertiary) 80%);
border: none; cursor: pointer; color: var(--accent); font-size: 0.85rem; z-index: 4;
}
--.cgw-more i { animation: cgwCaretBounce 1.4s ease-in-out infinite; }
++.cgw-more i { animation: cgwCaretBounce 1.4s var(--ov-ease-in-out, ease-in-out) infinite; }
.cgw-more:hover i { color: var(--accent-light); }
@keyframes cgwCaretBounce { 0%,100% { transform: translateY(0); } 50% { transform: translateY(3px); } }
/* Expanded: caret becomes a normal collapse button under the list (no fade/bounce). */
@@@ -9155,7 +9155,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
.cgw-goal {
position: relative; height: 42px; border-radius: 9px; overflow: hidden; cursor: pointer;
background: var(--bg-card); border: 1px solid var(--border);
-- transition: transform .12s ease, box-shadow .12s ease;
++ transition: transform var(--ov-dur-1, .12s) var(--ov-ease-out, ease), box-shadow var(--ov-dur-1, .12s) var(--ov-ease-out, ease);
}
.cgw-goal:hover { transform: translateY(-1px); box-shadow: 0 4px 14px rgba(0,0,0,.35); }
.cgw-goal.has-img { border-color: rgba(255,255,255,.08); }
@@@ -9183,17 +9183,17 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
.cgw-goal:not(.has-img) .cgw-amt { color: var(--text-muted); text-shadow: none; }
.cgw-goal:not(.has-img) .cgw-pct { color: var(--accent); text-shadow: none; }
.cgw-bar { position: absolute; left: 0; right: 0; bottom: 0; height: 4px; background: rgba(0,0,0,.45); z-index: 2; }
--.cgw-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent)); transition: width 0.5s cubic-bezier(.2,.8,.2,1); }
++.cgw-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent)); transition: width var(--ov-dur-4, 0.5s) var(--ov-ease-out, cubic-bezier(.2,.8,.2,1)); }
.cgw-goal.reached .cgw-fill { background: linear-gradient(90deg, #38c172, #6ee7a8); }
--.cgw-goal.reached { animation: cgwGlow 2.4s ease-in-out infinite; }
++.cgw-goal.reached { animation: cgwGlow 2.4s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes cgwGlow { 0%,100% { box-shadow: 0 0 0 rgba(110,231,168,0); } 50% { box-shadow: 0 0 12px rgba(110,231,168,.55); } }
--.cgw-pulse { animation: cgwPulse 0.6s ease; }
++.cgw-pulse { animation: cgwPulse var(--ov-dur-4, 0.6s) var(--ov-ease-out, ease); }
@keyframes cgwPulse { 0% { transform: scale(1); } 40% { transform: scale(1.05); } 100% { transform: scale(1); } }
/* Goal popover — click a widget goal to see a larger image + full progress. */
--.cgw-pop-overlay { position: fixed; inset: 0; z-index: 4000; background: rgba(0,0,0,.72); display: flex; align-items: center; justify-content: center; padding: 20px; animation: cgwFade .15s ease; }
++.cgw-pop-overlay { position: fixed; inset: 0; z-index: 4000; background: rgba(0,0,0,.72); display: flex; align-items: center; justify-content: center; padding: 20px; animation: cgwFade var(--ov-dur-2, .15s) var(--ov-ease-out, ease); }
@keyframes cgwFade { from { opacity: 0; } to { opacity: 1; } }
--.cgw-pop { position: relative; width: min(440px, 94vw); background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 14px; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,.6); animation: cgwPopIn .18s cubic-bezier(.2,.9,.3,1.15); }
++.cgw-pop { position: relative; width: min(440px, 94vw); background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 14px; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,.6); animation: cgwPopIn var(--ov-dur-2, .18s) var(--ov-ease-out, cubic-bezier(.2,.9,.3,1.15)); }
@keyframes cgwPopIn { from { transform: scale(.92); opacity: 0; } to { transform: none; opacity: 1; } }
.cgw-pop.reached { border-color: #38c172; }
.cgw-pop-media { width: 100%; max-height: 320px; object-fit: cover; display: block; background: #000; }
@@@ -9217,7 +9217,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
border: 1px solid color-mix(in srgb, var(--accent) 50%, transparent);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 15%, transparent), 0 8px 26px rgba(0,0,0,.45);
display: flex; align-items: center; gap: 14px;
-- animation: cgrIn 0.5s cubic-bezier(.2,.9,.3,1.2);
++ animation: cgrIn var(--ov-dur-4, 0.5s) var(--ov-ease-out, cubic-bezier(.2,.9,.3,1.2));
}
@keyframes cgrIn { 0% { transform: scale(.85) translateY(8px); opacity: 0; } 100% { transform: none; opacity: 1; } }
.cgr-media { width: 72px; height: 72px; border-radius: 10px; object-fit: cover; flex-shrink: 0; background: #000; box-shadow: 0 3px 10px rgba(0,0,0,.5); }
@@@ -9225,7 +9225,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
.cgr-badge { display: inline-flex; align-items: center; gap: 6px; font-size: 0.7rem; font-weight: 800; letter-spacing: 0.08em; color: #ffd781; text-transform: uppercase; }
.cgr-title { font-size: 1.02rem; font-weight: 800; color: #fff; margin: 2px 0; word-break: break-word; }
.cgr-sub { font-size: 0.8rem; color: #d9c9a4; }
--.cgr-shine { position: absolute; inset: 0; z-index: 1; background: linear-gradient(115deg, transparent 30%, rgba(255,255,255,.16) 50%, transparent 70%); transform: translateX(-120%); animation: cgrShine 2.6s ease-in-out infinite; }
++.cgr-shine { position: absolute; inset: 0; z-index: 1; background: linear-gradient(115deg, transparent 30%, rgba(255,255,255,.16) 50%, transparent 70%); transform: translateX(-120%); animation: cgrShine 2.6s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes cgrShine { 0% { transform: translateX(-120%); } 60%,100% { transform: translateX(120%); } }
/* Small buttons in the channel Settings alert-sounds group */
@@@ -9233,7 +9233,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
/* ═══════════════ Offline screen "explore" cards ═══════════════ */
/* When the default (with cards) is shown, size to content instead of forcing 16/9. */
.ch-offline-explore-cards { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
--.ch-offline-card { width: 240px; max-width: 44%; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; text-decoration: none; transition: border-color .15s, transform .15s; }
++.ch-offline-card { width: 240px; max-width: 44%; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; text-decoration: none; transition: border-color var(--ov-dur-2, .15s), transform var(--ov-dur-2, .15s); }
.ch-offline-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.ch-offline-card .stream-card-title { color: var(--text-primary); font-size: 0.85rem; }
.ch-offline-card-kind { position: absolute; top: 6px; left: 6px; background: rgba(0,0,0,.72); color: #fff; font-size: 0.64rem; padding: 2px 7px; border-radius: 999px; font-weight: 700; letter-spacing: .02em; }
@@@ -9269,12 +9269,12 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
.ai-tl-empty p { margin: 4px 0; }
/* Month-jump bar */
/* Collapsible moments */
--.ai-tl-moments-toggle i { transition: transform .18s ease; }
++.ai-tl-moments-toggle i { transition: transform var(--ov-dur-2, .18s) var(--ov-ease-out, ease); }
.ai-tl-session-vodlink a { color: var(--text-muted); text-decoration: none; display: inline-flex; align-items: center; gap: 6px; }
.ai-tl-session-vodlink a:hover { color: var(--accent, #8b5cf6); }
.ai-tl-tr-line { display: flex; gap: 11px; padding: 3px 0; font-size: 0.86rem; line-height: 1.5; }
.ai-tl-ts { flex-shrink: 0; font-variant-numeric: tabular-nums; font-size: 0.73rem; font-weight: 700; color: var(--accent, #8b5cf6); text-decoration: none; padding-top: 2px; min-width: 46px; }
--.ai-tl-session.ai-tl-flash { animation: aiTlFlash 1.4s ease; }
++.ai-tl-session.ai-tl-flash { animation: aiTlFlash 1.4s var(--ov-ease-out, ease); }
@keyframes aiTlFlash { 0%, 100% { background: transparent; } 20% { background: color-mix(in srgb, var(--accent, #8b5cf6) 14%, transparent); } }
/* AI overview at the top of the About tab */
/* AI Timeline "As a streamer" / "As a chatter" section dividers */
@@@ -9392,7 +9392,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
box-shadow: 0 6px 22px rgba(0, 0, 0, .55);
cursor: grab;
touch-action: none; /* pointer events drive drag/resize ourselves */
-- transition: box-shadow .15s ease, border-color .15s ease;
++ transition: box-shadow var(--ov-dur-2, .15s) var(--ov-ease-out, ease), border-color var(--ov-dur-2, .15s) var(--ov-ease-out, ease);
}
.ov-pip-bar {
@@@ -9400,7 +9400,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
display: flex; align-items: center; justify-content: space-between; gap: 6px;
padding: 5px 7px;
background: linear-gradient(180deg, rgba(0,0,0,.72), rgba(0,0,0,0));
-- opacity: 0; transition: opacity .15s ease;
++ opacity: 0; transition: opacity var(--ov-dur-2, .15s) var(--ov-ease-out, ease);
pointer-events: none;
}
.ov-pip-title i { opacity: .85; margin-right: 4px; }
@@@ -9408,7 +9408,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
background: rgba(0,0,0,.45); border: none; color: #fff;
width: 22px; height: 22px; border-radius: 5px; cursor: pointer;
font-size: .68rem; display: grid; place-items: center;
-- transition: background .12s ease;
++ transition: background var(--ov-dur-1, .12s) var(--ov-ease-out, ease);
}
.ov-pip-actions button:hover { background: rgba(255,255,255,.24); }
@@@ -9418,7 +9418,7 @@ body.fabs-tucked .messenger-toggle, body.fabs-tucked .floating-chat-fab, body.fa
background:
linear-gradient(135deg, transparent 50%, rgba(255,255,255,.55) 50%, rgba(255,255,255,.55) 62%,
transparent 62%, transparent 74%, rgba(255,255,255,.55) 74%, rgba(255,255,255,.55) 86%, transparent 86%);
-- opacity: 0; transition: opacity .15s ease;
++ opacity: 0; transition: opacity var(--ov-dur-2, .15s) var(--ov-ease-out, ease);
}
.ov-pip-offline {
@@@ -9487,8 +9487,8 @@ label.staff-inline-toggle.aiv-field { flex-direction:row; align-items:center; }
.ai-tl-sound--animal, .ai-tl-sound--kitchen { color: #9be7a6; }
/* ── Hero stats: tooltips, click-through charts, mobile ── */
--.hero-stat-chart-hint { font-size: 0.6em; opacity: 0; margin-left: 4px; transition: opacity 0.15s; }
--.hero-tip { position: fixed; z-index: 4000; max-width: 300px; padding: 10px 12px; border-radius: 12px; background: var(--bg-card, #1a1a20); border: 1px solid var(--border-light, #3a3a44); box-shadow: 0 12px 34px rgba(0, 0, 0, 0.5); color: var(--text-primary); font-size: 0.8rem; line-height: 1.45; pointer-events: none; opacity: 0; transform: translateY(4px); transition: opacity 0.12s, transform 0.12s; }
++.hero-stat-chart-hint { font-size: 0.6em; opacity: 0; margin-left: 4px; transition: opacity var(--ov-dur-2, 0.15s); }
++.hero-tip { position: fixed; z-index: 4000; max-width: 300px; padding: 10px 12px; border-radius: 12px; background: var(--bg-card, #1a1a20); border: 1px solid var(--border-light, #3a3a44); box-shadow: 0 12px 34px rgba(0, 0, 0, 0.5); color: var(--text-primary); font-size: 0.8rem; line-height: 1.45; pointer-events: none; opacity: 0; transform: translateY(4px); transition: opacity var(--ov-dur-1, 0.12s), transform var(--ov-dur-1, 0.12s); }
.hero-tip-recent b { display: block; font-size: 0.9rem; color: var(--accent-light, #a78bfa); }
.hero-tip-recent span { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
@media (max-width: 720px) {
@@@ -9517,7 +9517,7 @@ label.staff-inline-toggle.aiv-field { flex-direction:row; align-items:center; }
app.js adds for five seconds after a route change. Six looping animations on an element that is
on screen the entire session is a composited repaint per frame for no reason while someone is
reading. */
--.brand-mark svg { overflow: visible; transform-origin: 50% 50%; animation: ovFloat 5s ease-in-out infinite; }
++.brand-mark svg { overflow: visible; transform-origin: 50% 50%; animation: ovFloat 5s var(--ov-ease-in-out, ease-in-out) infinite; }
.brand-mark svg,
.brand-mark .ov-glow,
.brand-mark .ov-comet,
@@@ -9535,17 +9535,17 @@ label.staff-inline-toggle.aiv-field { flex-direction:row; align-items:center; }
.nav-brand:hover .brand-mark .ov-glow,
.nav-brand:hover .brand-mark .ov-comet,
.nav-brand:hover .brand-mark .ov-dot { animation-play-state: running; }
--.ov-glow { fill: currentColor; opacity: 0.1; transform-box: fill-box; transform-origin: center; animation: ovGlow 3.4s ease-in-out infinite; }
++.ov-glow { fill: currentColor; opacity: 0.1; transform-box: fill-box; transform-origin: center; animation: ovGlow 3.4s var(--ov-ease-in-out, ease-in-out) infinite; }
.ov-ring { fill: none; stroke: currentColor; stroke-width: 4; opacity: 0.28; }
.ov-comet { fill: none; stroke-width: 4; stroke-linecap: round; stroke-dasharray: 34 79; transform-box: fill-box; transform-origin: center; animation: ovComet 3.6s linear infinite; }
--.ov-v { fill: none; stroke-width: 4.6; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 46; stroke-dashoffset: 0; animation: ovDraw 1.1s cubic-bezier(.2,.8,.2,1) both; }
--.ov-dot { fill: #fff; transform-box: fill-box; transform-origin: center; animation: ovDot 2.4s ease-in-out infinite; }
++.ov-v { fill: none; stroke-width: 4.6; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 46; stroke-dashoffset: 0; animation: ovDraw 1.1s var(--ov-ease-out, cubic-bezier(.2,.8,.2,1)) both; }
++.ov-dot { fill: #fff; transform-box: fill-box; transform-origin: center; animation: ovDot 2.4s var(--ov-ease-in-out, ease-in-out) infinite; }
.ov-orbit { fill: #fff; filter: drop-shadow(0 0 3px currentColor); }
.ov-orbit--tail { opacity: 0.5; }
--.nav-brand:hover .brand-mark svg, .hero h1:hover .brand-mark--hero svg { animation: ovSpin 0.9s cubic-bezier(.2,1.5,.3,1) 1; }
--.nav-brand:hover .ov-comet, .hero h1:hover .ov-comet { animation-duration: 0.9s; }
++.nav-brand:hover .brand-mark svg, .hero h1:hover .brand-mark--hero svg { animation: ovSpin 0.9s var(--ov-ease-spring, cubic-bezier(.2,1.5,.3,1)) 1; }
++.nav-brand:hover .ov-comet, .hero h1:hover .ov-comet { animation-duration: 0; }
.nav-brand:hover .ov-glow, .hero h1:hover .ov-glow { opacity: 0.28; }
--.nav-brand:hover .ov-v, .hero h1:hover .ov-v { animation: ovDraw 0.7s cubic-bezier(.2,.8,.2,1) both; }
++.nav-brand:hover .ov-v, .hero h1:hover .ov-v { animation: ovDraw var(--ov-dur-4, 0.7s) var(--ov-ease-out, cubic-bezier(.2,.8,.2,1)) both; }
@keyframes ovFloat { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-1px) rotate(2deg); } }
@keyframes ovGlow { 0%, 100% { opacity: 0.08; transform: scale(0.92); } 50% { opacity: 0.2; transform: scale(1.06); } }
@keyframes ovComet { 50% { opacity: 0.35; } }
@@@ -9553,7 +9553,7 @@ label.staff-inline-toggle.aiv-field { flex-direction:row; align-items:center; }
@keyframes ovDot { 0%, 100% { transform: scale(1); opacity: 0.85; } 50% { transform: scale(1.5); opacity: 1; } }
@keyframes ovSpin { 40% { transform: scale(1.18); } }
/* One spring as the route lands: it grows past its size and settles (it no longer turns). */
--.brand-mark.is-spin svg { animation: ovSpinIn 1.05s cubic-bezier(.16, 1.16, .3, 1) 1 both; animation-play-state: running; }
++.brand-mark.is-spin svg { animation: ovSpinIn 1.05s var(--ov-ease-spring, cubic-bezier(.16, 1.16, .3, 1)) 1 both; animation-play-state: running; }
@keyframes ovSpinIn {
0% { transform: scale(0.86); }
45% { transform: scale(1.2); }
@@@ -9561,7 +9561,7 @@ label.staff-inline-toggle.aiv-field { flex-direction:row; align-items:center; }
100% { transform: scale(1); }
}
/* And a short overshoot as it comes to rest, so going still reads as deliberate. */
--.brand-mark.is-settling svg { animation: ovSettle 0.68s cubic-bezier(.26, 1.32, .42, 1) 1 both; animation-play-state: running; }
++.brand-mark.is-settling svg { animation: ovSettle var(--ov-dur-4, 0.68s) var(--ov-ease-spring, cubic-bezier(.26, 1.32, .42, 1)) 1 both; animation-play-state: running; }
@keyframes ovSettle {
0% { transform: rotate(5deg) scale(1.07); }
55% { transform: rotate(-2.5deg) scale(0.982); }
@@@ -9570,7 +9570,7 @@ label.staff-inline-toggle.aiv-field { flex-direction:row; align-items:center; }
/* Hero: bigger, plus sparks off the V */
.brand-mark--hero { width: 1.05em; height: 1.05em; vertical-align: -0.16em; color: var(--accent); margin-right: 0.08em; }
.brand-mark--hero svg { width: 100%; height: 100%; }
--.ov-spark { fill: #fff; opacity: 0; transform-box: fill-box; transform-origin: center; animation: ovSpark 3.4s ease-out infinite; }
++.ov-spark { fill: #fff; opacity: 0; transform-box: fill-box; transform-origin: center; animation: ovSpark 3.4s var(--ov-ease-out, ease-out) infinite; }
.ov-spark--2 { animation-delay: 1.1s; animation-name: ovSpark2; } .ov-spark--3 { animation-delay: 2.3s; animation-name: ovSpark3; }
@keyframes ovSpark { 0% { opacity: 0; transform: translate(0,10px) scale(0.6); } 10% { opacity: 1; } 60% { opacity: 0; transform: translate(12px,-12px) scale(1.4); } 100% { opacity: 0; } }
@keyframes ovSpark2 { 0% { opacity: 0; transform: translate(0,10px) scale(0.6); } 10% { opacity: 1; } 60% { opacity: 0; transform: translate(-13px,-8px) scale(1.3); } 100% { opacity: 0; } }
@@@ -9599,10 +9599,10 @@ label.staff-inline-toggle.aiv-field { flex-direction:row; align-items:center; }
/* Below ~360px a label like "Hours watched" cannot fit on one line in a third of the screen.
Let it wrap rather than truncating it into something unreadable. */
/* Landscape phones are short, not narrow — give the strip back its vertical room. */
--.hero-stat-more i { font-size: 0.7rem; transition: transform 0.28s cubic-bezier(.2, 1, .3, 1); }
++.hero-stat-more i { font-size: 0.7rem; transition: transform var(--ov-dur-3, 0.28s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1)); }
/* The incoming block fades up while the container animates its height — see the swap() helper.
Only the element that is arriving animates, so the one leaving doesn't flash on its way out. */
--.hero-stat-full.is-entering, .hero-stat-strip.is-entering { animation: heroStatsOpen 0.42s cubic-bezier(.2, 1, .3, 1); }
++.hero-stat-full.is-entering, .hero-stat-strip.is-entering { animation: heroStatsOpen var(--ov-dur-3, 0.42s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1)); }
@keyframes heroStatsOpen { from { opacity: 0; transform: translateY(-7px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
.hero-stat-full.is-entering, .hero-stat-strip.is-entering { animation: none; }
@@@ -9689,7 +9689,7 @@ label.staff-inline-toggle.aiv-field { flex-direction:row; align-items:center; }
/* ── Load more ───────────────────────────────────────────────────────────────
A Prev/Next pager with a page counter belongs on a browse page where someone is hunting for a
particular thing. On a front-page rail it is machinery nobody wants to operate. */
--.home-load-more i { font-size: 0.74rem; transition: transform 0.25s; }
++.home-load-more i { font-size: 0.74rem; transition: transform var(--ov-dur-2, 0.25s); }
.home-load-more small { font-size: 0.72rem; font-weight: 600; color: var(--text-muted); }
/* ── Hero stat chips ─────────────────────────────────────────────────────────
@@@ -9704,7 +9704,7 @@ label.staff-inline-toggle.aiv-field { flex-direction:row; align-items:center; }
background: linear-gradient(165deg, color-mix(in srgb, var(--accent) 9%, transparent), transparent 70%),
color-mix(in srgb, var(--bg-card) 72%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 17%, var(--border));
-- transition: transform 0.18s cubic-bezier(.2, 1, .3, 1), border-color 0.2s, background 0.2s;
++ transition: transform var(--ov-dur-2, 0.18s) var(--ov-ease-out, cubic-bezier(.2, 1, .3, 1)), border-color var(--ov-dur-2, 0.2s), background var(--ov-dur-2, 0.2s);
}
/* The tile is sized off the chip, and the glyph is deliberately a little under half of it — a
glyph that fills its tile reads as cramped next to a nine-point label. Circular, because a
@@@ -9941,7 +9941,7 @@ label.staff-inline-toggle.aiv-field { flex-direction:row; align-items:center; }
}
.ov-conn-pill.is-shown { opacity: 1; transform: translate3d(-50%, 0, 0) scale(1); }
.ov-conn-pill .ov-conn-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--warning); flex: none; }
--.ov-conn-pill[data-state="reconnecting"] .ov-conn-dot, .ov-conn-pill[data-state="updating"] .ov-conn-dot { animation: ovConnPulse 1.1s ease-in-out infinite; }
++.ov-conn-pill[data-state="reconnecting"] .ov-conn-dot, .ov-conn-pill[data-state="updating"] .ov-conn-dot { animation: ovConnPulse 1.1s var(--ov-ease-in-out, ease-in-out) infinite; }
.ov-conn-pill[data-state="updating"] .ov-conn-dot { background: var(--info); }
.ov-conn-pill[data-state="offline"] .ov-conn-dot { background: var(--danger); }
.ov-conn-pill[data-state="connected"] .ov-conn-dot { background: var(--success); }
@@@ -9953,7 +9953,7 @@ label.staff-inline-toggle.aiv-field { flex-direction:row; align-items:center; }
.ov-conn-pill .ov-conn-dot { animation: none !important; }
}
--body.ov-panel-open #floating-chat-fab { opacity: 0; pointer-events: none; transform: scale(.85); transition: opacity .15s, transform .15s; }
++body.ov-panel-open #floating-chat-fab { opacity: 0; pointer-events: none; transform: scale(.85); transition: opacity var(--ov-dur-2, .15s), transform var(--ov-dur-2, .15s); }
/* ── Live on the shared navbar ────────────────────────────────────────────────
The bar is the network's shared component (/shared/navbar.js). It carries the class "navbar" as well,
ddiff --git a/public/index.html b/public/index.html
iindex 5ab50f05..6ee4b3f4 100644
---- a/public/index.html
++++ b/public/index.html
@@@ -75,7 +75,7 @@
<style id="ovsk-css">
.ovsk{position:relative;overflow:hidden;background:var(--bg-card,#171826);border:1px solid var(--border,rgba(255,255,255,.08));border-radius:14px}
.ovsk-bar{position:relative;overflow:hidden;background:color-mix(in srgb,var(--text-muted,#8b93ad) 14%,transparent);border-radius:6px}
-- .ovsk::after,.ovsk-bar::after{content:'';position:absolute;inset:0;background:linear-gradient(100deg,transparent 20%,color-mix(in srgb,var(--text-primary,#f1f4fb) 7%,transparent) 50%,transparent 80%);transform:translateX(-100%);animation:ovskSweep 1.5s ease-in-out infinite}
++ .ovsk::after,.ovsk-bar::after{content:'';position:absolute;inset:0;background:linear-gradient(100deg,transparent 20%,color-mix(in srgb,var(--text-primary,#f1f4fb) 7%,transparent) 50%,transparent 80%);transform:translateX(-100%);animation:ovskSweep 1.5s var(--ov-ease-in-out, ease-in-out) infinite}
@keyframes ovskSweep{to{transform:translateX(100%)}}
.ovsk-card{display:flex;flex-direction:column}
.ovsk-thumb{aspect-ratio:16/9;width:100%;background:color-mix(in srgb,var(--text-muted,#8b93ad) 10%,transparent)}
@@@ -83,13 +83,13 @@
.ovsk-av{width:32px;height:32px;border-radius:50%;flex:none}
.ovsk-lines{flex:1;min-width:0;display:grid;gap:6px}
.ovsk-panel{padding:14px;display:grid;gap:10px}
-- .ovsk-fade{animation:ovskFade .25s ease forwards}
++ .ovsk-fade{animation:ovskFade var(--ov-dur-2, .25s) var(--ov-ease-out, ease) forwards}
@keyframes ovskFade{to{opacity:0}}
@media (max-width:760px){#stream-grid-live .ovsk-card:nth-of-type(n+3){display:none}}
@media (prefers-reduced-motion:reduce){.ovsk::after,.ovsk-bar::after{animation:none}}
/* A route's own markup is fetched on first visit; until it lands the page shows its outline. */
.ov-route-skel{display:grid;gap:14px;padding:24px 16px;max-width:1200px;margin:0 auto}
-- .ov-route-skel span{display:block;border-radius:14px;background:color-mix(in srgb,var(--text-muted,#8b93ad) 10%,transparent);animation:ovRouteSkel 1.4s ease-in-out infinite alternate}
++ .ov-route-skel span{display:block;border-radius:14px;background:color-mix(in srgb,var(--text-muted,#8b93ad) 10%,transparent);animation:ovRouteSkel 1.4s var(--ov-ease-in-out, ease-in-out) infinite alternate}
.ov-route-skel span:nth-child(1){height:44px;width:min(420px,70%)}
.ov-route-skel span:nth-child(2){height:clamp(180px,38vw,420px)}
.ov-route-skel span:nth-child(3){height:120px;animation-delay:.2s}
ddiff --git a/public/kiosk.html b/public/kiosk.html
iindex 10da19d2..a45317e5 100644
---- a/public/kiosk.html
++++ b/public/kiosk.html
@@@ -120,7 +120,7 @@ button{font:inherit}
.hero p{
margin:0;font-size:1.08vw;line-height:1.35;
font-weight:650;color:#bcc5cf;
-- min-height:1.5em;transition:opacity .5s
++ min-height:1.5em;transition:opacity var(--ov-dur-4, .5s)
}
.hero-side{
position:relative;z-index:1;text-align:right;
@@@ -196,7 +196,7 @@ button{font:inherit}
background:linear-gradient(145deg,rgba(18,22,29,.97),rgba(10,12,17,.96));
display:flex;flex-direction:column;justify-content:center;
box-shadow:0 14px 36px rgba(0,0,0,.22);
-- transition:transform .12s,border-color .12s
++ transition:transform var(--ov-dur-1, .12s),border-color var(--ov-dur-1, .12s)
}
.domain:hover{transform:translateY(-3px);border-color:var(--c)}
.domain:after{
@@@ -283,7 +283,7 @@ button{font:inherit}
min-width:0;overflow:hidden;
display:grid;grid-template-rows:1fr auto;
border:1px solid #29313b;border-radius:12px;
-- background:#0b0e13;transition:border-color .12s
++ background:#0b0e13;transition:border-color var(--ov-dur-1, .12s)
}
.card:hover{border-color:#3c4652}
.thumb{
@@@ -417,13 +417,13 @@ button{font:inherit}
display:flex;align-items:flex-start;justify-content:center;
padding-top:3.5vh;
pointer-events:none; /* the dashboard behind stays fully visible + clickable */
-- opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s;
++ opacity:0;visibility:hidden;transition:opacity var(--ov-dur-3, .3s) var(--ov-ease-out, ease),visibility var(--ov-dur-3, .3s);
}
.omni.show{opacity:1;visibility:visible}
.omni-box{
width:min(720px,92vw);pointer-events:auto; /* only the bar itself is interactive */
transform:translateY(-10px);opacity:0;
-- transition:transform .34s cubic-bezier(.2,.9,.3,1.2),opacity .3s ease;
++ transition:transform var(--ov-dur-3, .34s) var(--ov-ease-out, cubic-bezier(.2,.9,.3,1.2)),opacity var(--ov-dur-3, .3s) var(--ov-ease-out, ease);
}
.omni.show .omni-box{transform:translateY(0);opacity:1}
.omni-inputwrap{
@@@ -432,7 +432,7 @@ button{font:inherit}
border:1.5px solid var(--line);border-radius:16px;
padding:8px 10px;
box-shadow:0 24px 70px rgba(0,0,0,.55),0 0 0 1px rgba(167,139,250,.05);
-- transition:background .25s,border-color .25s,box-shadow .25s;
++ transition:background var(--ov-dur-2, .25s),border-color var(--ov-dur-2, .25s),box-shadow var(--ov-dur-2, .25s);
}
/* Active (user is typing): prominent, gold-glow. */
.omni.active .omni-box{opacity:1}
@@@ -448,7 +448,7 @@ button{font:inherit}
.omni:not(.active) .omni-hint{opacity:.3}
/* Reveal on hover (not focus — the input is always focused for the typing feature) so it stays
ghosted at rest, and go fully solid whenever the search-engine dropdown is open. */
--.omni:not(.active) .omni-box:hover{opacity:.85;transition:opacity .18s}
++.omni:not(.active) .omni-box:hover{opacity:.85;transition:opacity var(--ov-dur-2, .18s)}
.omni.provopen .omni-box{opacity:1!important}
.omni-input{
flex:1;min-width:0;background:none;border:none;outline:none;
@@@ -460,7 +460,7 @@ button{font:inherit}
display:flex;align-items:center;gap:7px;flex-shrink:0;
background:rgba(255,255,255,.04);border:1px solid var(--line);
color:var(--muted);border-radius:11px;padding:9px 12px;cursor:pointer;
-- font-size:.82rem;font-weight:800;letter-spacing:.02em;transition:background .15s,color .15s;
++ font-size:.82rem;font-weight:800;letter-spacing:.02em;transition:background var(--ov-dur-2, .15s),color var(--ov-dur-2, .15s);
}
.omni-provider:hover{background:rgba(255,255,255,.08);color:var(--text)}
.omni-provider i:first-child{color:var(--gold)}
@@@ -468,7 +468,7 @@ button{font:inherit}
.omni-go{
flex-shrink:0;background:var(--gold);color:#161009;border:none;
width:44px;height:44px;border-radius:11px;cursor:pointer;font-size:1.05rem;
-- display:grid;place-items:center;transition:transform .12s,filter .15s;
++ display:grid;place-items:center;transition:transform var(--ov-dur-1, .12s),filter var(--ov-dur-2, .15s);
}
.omni-go:hover{filter:brightness(1.08)}
.omni-go:active{transform:scale(.94)}
@@@ -489,8 +489,8 @@ button{font:inherit}
.omni-sug{
display:flex;align-items:center;gap:12px;padding:11px 14px;border-radius:11px;
cursor:pointer;background:rgba(16,20,27,.7);border:1px solid transparent;
-- transition:background .12s,border-color .12s,transform .12s;
-- animation:omniSugIn .22s ease both;
++ transition:background var(--ov-dur-1, .12s),border-color var(--ov-dur-1, .12s),transform var(--ov-dur-1, .12s);
++ animation:omniSugIn var(--ov-dur-2, .22s) var(--ov-ease-out, ease) both;
}
.omni-sug:hover,.omni-sug.sel{background:rgba(26,32,42,.95);border-color:var(--line)}
.omni-sug.sel{transform:translateX(2px)}
@@@ -505,7 +505,7 @@ button{font:inherit}
.omni-sug-title{font-size:.98rem;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.omni-sug-sub{font-size:.76rem;color:#7e8997;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px}
.omni-sug-kind{font-size:.64rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase;color:#6f7b89;flex-shrink:0}
--.omni-sug-del{flex-shrink:0;background:none;border:none;color:#5a6570;cursor:pointer;padding:4px 6px;border-radius:6px;font-size:.8rem;opacity:0;transition:opacity .12s,color .12s}
++.omni-sug-del{flex-shrink:0;background:none;border:none;color:#5a6570;cursor:pointer;padding:4px 6px;border-radius:6px;font-size:.8rem;opacity:0;transition:opacity var(--ov-dur-1, .12s),color var(--ov-dur-1, .12s)}
.omni-sug:hover .omni-sug-del{opacity:1}
.omni-sug-del:hover{color:var(--red)}
.omni-hint{
ddiff --git a/public/obs/alerts.html b/public/obs/alerts.html
iindex 04d7cd0a..e291eca0 100644
---- a/public/obs/alerts.html
++++ b/public/obs/alerts.html
@@@ -22,14 +22,14 @@ body[data-pos=bottom] .al-stage { top: auto; bottom: 3vw; }
padding: 1.1vw 1.8vw 1.1vw 1.1vw; border-radius: 1.6vw; overflow: hidden; isolation: isolate;
background: color-mix(in srgb, var(--bg-card, #111827) 88%, transparent); border: 1px solid color-mix(in srgb, var(--accent, var(--o)) 40%, transparent);
box-shadow: 0 1.4vw 3.6vw -1vw rgba(0,0,0,.55), 0 0 0 .3vw color-mix(in srgb, var(--accent, var(--o)) 14%, transparent);
-- animation: alIn .7s cubic-bezier(.34,1.56,.64,1) both; }
--.al.is-out { animation: alOut .45s cubic-bezier(.65,0,.35,1) both; }
++ animation: alIn var(--ov-dur-4, .7s) var(--ov-ease-spring, cubic-bezier(.34,1.56,.64,1)) both; }
++.al.is-out { animation: alOut var(--ov-dur-4, .45s) var(--ov-ease-out, cubic-bezier(.65,0,.35,1)) both; }
@keyframes alIn { from { opacity: 0; transform: translateY(-2.4vw) scale(.92); } }
@keyframes alOut { to { opacity: 0; transform: translateY(-1.2vw) scale(.96); } }
body[data-pos=bottom] .al { animation-name: alInUp; }
@keyframes alInUp { from { opacity: 0; transform: translateY(2.4vw) scale(.92); } }
/* The glint, as the mark's: a band of light crosses once, just after it lands. */
--.al::after { content: ''; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(105deg, transparent 35%, rgba(255,255,255,.22) 50%, transparent 65%); transform: translateX(-120%); animation: alGlint 1s .55s cubic-bezier(.45,0,.25,1) forwards; }
++.al::after { content: ''; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(105deg, transparent 35%, rgba(255,255,255,.22) 50%, transparent 65%); transform: translateX(-120%); animation: alGlint 1s .55s var(--ov-ease-out, cubic-bezier(.45,0,.25,1)) forwards; }
@keyframes alGlint { to { transform: translateX(120%); } }
.al-ic { position: relative; display: grid; place-items: center; width: 4.6vw; height: 4.6vw; border-radius: 1.2vw; font-size: 2.2vw;
background: linear-gradient(135deg, color-mix(in srgb, var(--accent, var(--o)) 85%, #fff 15%), var(--v)); color: #fff; }
ddiff --git a/public/obs/chat.html b/public/obs/chat.html
iindex 0fed5969..adc45761 100644
---- a/public/obs/chat.html
++++ b/public/obs/chat.html
@@@ -33,7 +33,7 @@ html, body {
line-height: 1.45;
word-wrap: break-word;
overflow-wrap: break-word;
-- animation: msg-in 0.25s ease-out;
++ animation: msg-in var(--ov-dur-2, 0.25s) var(--ov-ease-out, ease-out);
text-shadow:
-1px -1px 1px rgba(0,0,0,0.8),
1px -1px 1px rgba(0,0,0,0.8),
@@@ -52,7 +52,7 @@ body.no-shadow .msg { text-shadow: none; }
/* ── Fade out old messages ──────────────────────────────────── */
.msg.fading {
-- animation: msg-fade 1s ease-out forwards;
++ animation: msg-fade 1s var(--ov-ease-out, ease-out) forwards;
}
@keyframes msg-fade {
to { opacity: 0; }
@@@ -105,7 +105,7 @@ body.no-shadow .msg { text-shadow: none; }
padding: 3px 8px; border-radius: 6px; font-size: 0.62em; font-weight: 700;
white-space: nowrap; pointer-events: none; opacity: 0; z-index: 50;
box-shadow: 0 6px 18px rgba(0,0,0,0.5);
-- transition: opacity 0.18s ease, transform 0.2s cubic-bezier(.2,.9,.3,1.25);
++ transition: opacity var(--ov-dur-2, 0.18s) var(--ov-ease-out, ease), transform var(--ov-dur-2, 0.2s) var(--ov-ease-out, cubic-bezier(.2,.9,.3,1.25));
}
.chat-badge[data-tip]:hover::after { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
/* Channel !sound pill — matches the main chat's played-sound styling */
@@@ -150,7 +150,7 @@ body.no-shadow .msg { text-shadow: none; }
.name-fx-fire {
color: #ff6600 !important;
text-shadow: 0 0 6px #ff4400, 0 0 12px #ff220088, 0 0 20px #cc110044;
-- animation: nameFxFire 1.5s ease-in-out infinite alternate;
++ animation: nameFxFire 1.5s var(--ov-ease-in-out, ease-in-out) infinite alternate;
}
@keyframes nameFxFire {
0%{text-shadow:0 0 6px #ff4400,0 0 12px #ff220088;color:#ff6600}
@@@ -160,7 +160,7 @@ body.no-shadow .msg { text-shadow: none; }
.name-fx-ice {
color: #88ddff !important;
text-shadow: 0 0 6px #44bbff88, 0 0 12px #2288cc44;
-- animation: nameFxIce 2s ease-in-out infinite alternate;
++ animation: nameFxIce 2s var(--ov-ease-in-out, ease-in-out) infinite alternate;
}
@keyframes nameFxIce {
0%{text-shadow:0 0 4px #44bbff88;color:#88ddff}
@@@ -172,7 +172,7 @@ body.no-shadow .msg { text-shadow: none; }
background-size: 300% 100%;
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
-- animation: nameFxGolden 2s ease-in-out infinite;
++ animation: nameFxGolden 2s var(--ov-ease-in-out, ease-in-out) infinite;
filter: drop-shadow(0 0 4px #ffd70066);
}
@keyframes nameFxGolden { 0%,100%{background-position:0% 50%} 50%{background-position:100% 50%} }
@@@ -180,7 +180,7 @@ body.no-shadow .msg { text-shadow: none; }
.name-fx-neon {
color: #00ff88 !important;
text-shadow: 0 0 8px #00ff88, 0 0 16px #00ff8888, 0 0 24px #00cc6644;
-- animation: nameFxNeon 1.2s ease-in-out infinite alternate;
++ animation: nameFxNeon 1.2s var(--ov-ease-in-out, ease-in-out) infinite alternate;
}
@keyframes nameFxNeon {
0%{text-shadow:0 0 4px #00ff88,0 0 8px #00ff8866}
@@@ -212,7 +212,7 @@ body.no-shadow .msg { text-shadow: none; }
letter-spacing: .02em;
white-space: nowrap;
transform-origin: 50% 60%;
-- animation: nameFxVegasShine 2.6s linear infinite, nameFxVegasLights 1.4s steps(1, end) infinite, nameFxVegasJackpot 3.2s ease-out infinite;
++ animation: nameFxVegasShine 2.6s linear infinite, nameFxVegasLights 1.4s steps(1, end) infinite, nameFxVegasJackpot 3.2s var(--ov-ease-out, ease-out) infinite;
}
.name-fx-vegas::before,
.name-fx-vegas::after {
@@@ -283,7 +283,7 @@ body.no-shadow .msg { text-shadow: none; }
letter-spacing: .03em;
white-space: nowrap;
transform-origin: 50% 70%;
-- animation: nameFxTwofatLights 3s linear infinite, nameFxTwofatThump 1s ease-out infinite;
++ animation: nameFxTwofatLights 3s linear infinite, nameFxTwofatThump 1s var(--ov-ease-out, ease-out) infinite;
}
.name-fx-twofat::before {
content: '▂▅▇▃▆';
@@@ -295,7 +295,7 @@ body.no-shadow .msg { text-shadow: none; }
font-size: .6em;
letter-spacing: .12em;
vertical-align: .1em;
-- animation: nameFxTwofatEq .5s steps(1, end) infinite;
++ animation: nameFxTwofatEq var(--ov-dur-4, .5s) steps(1, end) infinite;
}
.name-fx-twofat::after {
content: '🔊';
@@@ -303,7 +303,7 @@ body.no-shadow .msg { text-shadow: none; }
margin-left: .3em;
-webkit-text-fill-color: initial;
font-size: .85em;
-- animation: nameFxTwofatSpeaker 1s ease-out infinite;
++ animation: nameFxTwofatSpeaker 1s var(--ov-ease-out, ease-out) infinite;
}
@keyframes nameFxTwofatLights {
0% { background-position: 0% 50%; }
@@@ -337,7 +337,7 @@ body.no-shadow .msg { text-shadow: none; }
.name-fx-void {
color: #9900ff !important;
text-shadow: 0 0 8px #6600cc, 0 0 16px #33006688, 0 0 24px #11003344;
-- animation: nameFxVoid 2s ease-in-out infinite;
++ animation: nameFxVoid 2s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes nameFxVoid {
0%,100%{text-shadow:0 0 8px #6600cc,0 0 16px #33006688;transform:scale(1)}
@@@ -349,7 +349,7 @@ body.no-shadow .msg { text-shadow: none; }
background-size: 200% 100%;
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
-- animation: nameFxToxic 1.8s ease-in-out infinite alternate;
++ animation: nameFxToxic 1.8s var(--ov-ease-in-out, ease-in-out) infinite alternate;
}
@keyframes nameFxToxic { 0%{background-position:0% 50%;filter:brightness(1)} 100%{background-position:200% 50%;filter:brightness(1.3)} }
@@@ -358,13 +358,13 @@ body.no-shadow .msg { text-shadow: none; }
background-size: 100% 200%;
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
-- animation: nameFxBlood 2s ease-in-out infinite;
++ animation: nameFxBlood 2s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes nameFxBlood { 0%,100%{background-position:50% 0%} 50%{background-position:50% 200%} }
.name-fx-shadow {
color: #aa88cc !important;
-- animation: nameFxShadow 2s ease-in-out infinite;
++ animation: nameFxShadow 2s var(--ov-ease-in-out, ease-in-out) infinite;
}
@keyframes nameFxShadow {
0%,100%{text-shadow:0 0 6px rgba(0,0,0,0.8),0 0 12px rgba(80,0,120,0.6),2px 2px 4px rgba(0,0,0,0.9)}
@@@ -373,7 +373,7 @@ body.no-shadow .msg { text-shadow: none; }
.name-fx-glitch {
color: #00ff88 !important;
-- animation: nameFxGlitch 0.15s infinite;
++ animation: nameFxGlitch var(--ov-dur-2, 0.15s) infinite;
text-shadow: 2px 0 #ff0044, -2px 0 #00ccff;
}
@keyframes nameFxGlitch {
@@@ -386,7 +386,7 @@ body.no-shadow .msg { text-shadow: none; }
background-size: 300% 100%;
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
-- animation: nameFxHoloFlicker 0.12s infinite, nameFxHoloShift 3s linear infinite;
++ animation: nameFxHoloFlicker var(--ov-dur-1, 0.12s) infinite, nameFxHoloShift 3s linear infinite;
}
@keyframes nameFxHoloFlicker { 0%,45%,55%,100%{opacity:1} 50%{opacity:0.6} }
@keyframes nameFxHoloShift { 0%{background-position:0% 50%} 100%{background-position:300% 50%} }
@@@ -396,7 +396,7 @@ body.no-shadow .msg { text-shadow: none; }
background-size: 300% 100%;
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
-- animation: nameFxDivine 2s ease-in-out infinite;
++ animation: nameFxDivine 2s var(--ov-ease-in-out, ease-in-out) infinite;
filter: drop-shadow(0 0 8px #ffe06688);
}
@keyframes nameFxDivine { 0%,100%{background-position:0% 50%} 50%{background-position:300% 50%} }
@@@ -413,7 +413,7 @@ body.no-shadow .msg { text-shadow: none; }
pointer-events: none;
font-size: 10px;
opacity: 0;
-- animation: chatParticleFloat 1.8s ease-out forwards;
++ animation: chatParticleFloat 1.8s var(--ov-ease-out, ease-out) forwards;
}
@keyframes chatParticleFloat {
0%{opacity:1;transform:translate(0,0) scale(1)}
@@@ -438,9 +438,9 @@ body.no-shadow .msg { text-shadow: none; }
font-size: 0.85em;
vertical-align: middle;
}
--.chat-hat.hat-float { animation: chatHatFloat 2s ease-in-out infinite; }
--.chat-hat.hat-pulse { animation: chatHatPulse 1.5s ease-in-out infinite; }
--.chat-hat.hat-warp { animation: chatHatWarp 2s ease-in-out infinite; }
++.chat-hat.hat-float { animation: chatHatFloat 2s var(--ov-ease-in-out, ease-in-out) infinite; }
++.chat-hat.hat-pulse { animation: chatHatPulse 1.5s var(--ov-ease-in-out, ease-in-out) infinite; }
++.chat-hat.hat-warp { animation: chatHatWarp 2s var(--ov-ease-in-out, ease-in-out) infinite; }
@keyframes chatHatFloat { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-3px)} }
@keyframes chatHatPulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.2)} }
@keyframes chatHatWarp { 0%,100%{transform:scale(1) rotate(0deg);filter:hue-rotate(0deg)} 50%{transform:scale(1.1) rotate(5deg);filter:hue-rotate(180deg)} }
@@@ -454,7 +454,7 @@ body.no-shadow .msg { text-shadow: none; }
color: rgba(255,255,255,0.4);
text-shadow: 0 0 3px rgba(0,0,0,0.8);
pointer-events: none;
-- transition: opacity 0.3s;
++ transition: opacity var(--ov-dur-3, 0.3s);
}
#status.hidden { opacity: 0; }
.spoiler { background: rgba(255, 255, 255, 0.35); color: transparent; border-radius: 4px; padding: 0 2px; }
ddiff --git a/public/obs/scene.html b/public/obs/scene.html
iindex a73eed17..ef6c73b1 100644
---- a/public/obs/scene.html
++++ b/public/obs/scene.html
@@@ -22,8 +22,8 @@ body {
}
/* The ground: two soft lights of the look's accent and the mark's violet, drifting slowly, and a faint dot grid. */
.sc-light { position: absolute; width: 70vmax; height: 70vmax; border-radius: 50%; filter: blur(9vmax); opacity: .38; z-index: -2; will-change: transform; }
--.sc-light.a { background: var(--accent, var(--o)); left: -18vmax; top: -24vmax; animation: scDriftA 28s ease-in-out infinite alternate; }
--.sc-light.b { background: var(--v); right: -22vmax; bottom: -30vmax; animation: scDriftB 34s ease-in-out infinite alternate; }
++.sc-light.a { background: var(--accent, var(--o)); left: -18vmax; top: -24vmax; animation: scDriftA 28s var(--ov-ease-in-out, ease-in-out) infinite alternate; }
++.sc-light.b { background: var(--v); right: -22vmax; bottom: -30vmax; animation: scDriftB 34s var(--ov-ease-in-out, ease-in-out) infinite alternate; }
@keyframes scDriftA { to { transform: translate(14vmax, 10vmax); } }
@keyframes scDriftB { to { transform: translate(-12vmax, -8vmax); } }
body::after { content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
@@@ -49,7 +49,7 @@ body::after { content: ''; position: absolute; inset: 0; z-index: -1; pointer-ev
.sc-foot b { color: var(--text-primary, #e6edf7); font-weight: 650; }
/* Each part arrives after the mark, one after another. */
--.sc > * { opacity: 0; transform: translateY(1.2vw); animation: scIn .8s cubic-bezier(.2,.8,.2,1) forwards; }
++.sc > * { opacity: 0; transform: translateY(1.2vw); animation: scIn var(--ov-dur-4, .8s) var(--ov-ease-out, cubic-bezier(.2,.8,.2,1)) forwards; }
.sc > :nth-child(1) { animation-delay: 0s; transform: none; }
.sc > :nth-child(2) { animation-delay: .9s; }
.sc > :nth-child(3) { animation-delay: 1.05s; }
ddiff --git a/public/popout-chat.html b/public/popout-chat.html
iindex 57251804..79c78334 100644
---- a/public/popout-chat.html
++++ b/public/popout-chat.html
@@@ -77,8 +77,8 @@
.pc-setting-row:last-child { border-bottom: none; }
.pc-setting-label { color: var(--text-secondary, #ccc); font-size: 0.78rem; }
.pc-toggle-wrap { display: flex; align-items: center; }
-- .pc-toggle { width: 30px; height: 17px; background: var(--border, #444); border-radius: 9px; position: relative; cursor: pointer; transition: background 0.2s; }
-- .pc-toggle::after { content: ''; position: absolute; top: 2px; left: 2px; width: 13px; height: 13px; background: #fff; border-radius: 50%; transition: transform 0.2s; }
++ .pc-toggle { width: 30px; height: 17px; background: var(--border, #444); border-radius: 9px; position: relative; cursor: pointer; transition: background var(--ov-dur-2, 0.2s); }
++ .pc-toggle::after { content: ''; position: absolute; top: 2px; left: 2px; width: 13px; height: 13px; background: #fff; border-radius: 50%; transition: transform var(--ov-dur-2, 0.2s); }
.pc-toggle.on { background: var(--accent, #8b5cf6); }
.pc-toggle.on::after { transform: translateX(13px); }