Revamp watchlist & wishlist buttons with dark glass design and animated gradient border
- Dark translucent background with backdrop blur instead of bright colored fills - Animated flowing gradient border using CSS mask-composite technique - Color-tinted labels and count badges (amber for watchlist, accent for wishlist) - Shimmer sweep clipped inside button bounds - Structured HTML: separate icon, label, badge, and shimmer elements - Badge pulses when count > 0 - Worker orbs: 7s delay before collapsing back after mouse leaves header
This commit is contained in:
parent
775307c2b5
commit
46d3309835
4 changed files with 255 additions and 31 deletions
|
|
@ -551,9 +551,18 @@
|
|||
<img src="https://cdn-icons-png.flaticon.com/512/8765/8765164.png" alt="Import"
|
||||
class="import-logo">
|
||||
</button>
|
||||
<button class="header-button watchlist-button" id="watchlist-button">👁️ Watchlist
|
||||
(0)</button>
|
||||
<button class="header-button wishlist-button" id="wishlist-button">🎵 Wishlist (0)</button>
|
||||
<button class="header-button watchlist-button" id="watchlist-button">
|
||||
<span class="hero-btn-icon">👁️</span>
|
||||
<span class="hero-btn-label">Watchlist</span>
|
||||
<span class="hero-btn-badge" id="watchlist-badge">0</span>
|
||||
<span class="hero-btn-shimmer"></span>
|
||||
</button>
|
||||
<button class="header-button wishlist-button" id="wishlist-button">
|
||||
<span class="hero-btn-icon">🎵</span>
|
||||
<span class="hero-btn-label">Wishlist</span>
|
||||
<span class="hero-btn-badge" id="wishlist-badge">0</span>
|
||||
<span class="hero-btn-shimmer"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -312,12 +312,20 @@ function handleServiceStatusUpdate(data) {
|
|||
}
|
||||
}
|
||||
|
||||
function _updateHeroBtnCount(buttonId, badgeId, count) {
|
||||
const badge = document.getElementById(badgeId);
|
||||
if (badge) {
|
||||
badge.textContent = count;
|
||||
badge.classList.toggle('has-items', count > 0);
|
||||
}
|
||||
}
|
||||
|
||||
function handleWatchlistCountUpdate(data) {
|
||||
if (data.success) {
|
||||
_updateHeroBtnCount('watchlist-button', 'watchlist-badge', data.count);
|
||||
const watchlistButton = document.getElementById('watchlist-button');
|
||||
if (watchlistButton) {
|
||||
const countdownText = data.next_run_in_seconds ? formatCountdownTime(data.next_run_in_seconds) : '';
|
||||
watchlistButton.textContent = `\u{1F441}\uFE0F Watchlist (${data.count})`;
|
||||
if (countdownText) {
|
||||
watchlistButton.title = `Next auto-scan in ${countdownText}`;
|
||||
}
|
||||
|
|
@ -399,11 +407,10 @@ function handleDashboardDbStats(stats) {
|
|||
}
|
||||
|
||||
function handleDashboardWishlistCount(data) {
|
||||
// Same logic as updateWishlistCount response handler
|
||||
const count = data.count || 0;
|
||||
_updateHeroBtnCount('wishlist-button', 'wishlist-badge', count);
|
||||
const wishlistButton = document.getElementById('wishlist-button');
|
||||
if (wishlistButton) {
|
||||
wishlistButton.textContent = `\uD83C\uDFB5 Wishlist (${count})`;
|
||||
if (count === 0) {
|
||||
wishlistButton.classList.remove('wishlist-active');
|
||||
wishlistButton.classList.add('wishlist-inactive');
|
||||
|
|
@ -20816,11 +20823,9 @@ async function updateWishlistCount() {
|
|||
const data = await response.json();
|
||||
const count = data.count || 0;
|
||||
|
||||
_updateHeroBtnCount('wishlist-button', 'wishlist-badge', count);
|
||||
const wishlistButton = document.getElementById('wishlist-button');
|
||||
if (wishlistButton) {
|
||||
wishlistButton.textContent = `🎵 Wishlist (${count})`;
|
||||
|
||||
// Update button styling based on count (matching GUI behavior)
|
||||
if (count === 0) {
|
||||
wishlistButton.classList.remove('wishlist-active');
|
||||
wishlistButton.classList.add('wishlist-inactive');
|
||||
|
|
@ -34060,11 +34065,10 @@ async function updateWatchlistButtonCount() {
|
|||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
_updateHeroBtnCount('watchlist-button', 'watchlist-badge', data.count);
|
||||
const watchlistButton = document.getElementById('watchlist-button');
|
||||
if (watchlistButton) {
|
||||
// Format countdown for button tooltip (optional enhancement)
|
||||
const countdownText = data.next_run_in_seconds ? formatCountdownTime(data.next_run_in_seconds) : '';
|
||||
watchlistButton.textContent = `👁️ Watchlist (${data.count})`;
|
||||
if (countdownText) {
|
||||
watchlistButton.title = `Next auto-scan in ${countdownText}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5727,40 +5727,236 @@ body {
|
|||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* ── Watchlist & Wishlist Hero Buttons ── */
|
||||
.watchlist-button,
|
||||
.wishlist-button {
|
||||
background: linear-gradient(135deg, rgb(var(--accent-rgb)) 0%, rgb(var(--accent-light-rgb)) 100%);
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
padding: 12px 20px;
|
||||
overflow: visible;
|
||||
background: rgba(18, 18, 22, 0.85);
|
||||
backdrop-filter: blur(16px) saturate(1.4);
|
||||
color: #e0e0e0;
|
||||
border: none;
|
||||
border-radius: 22px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.wishlist-button:hover {
|
||||
background: linear-gradient(135deg, rgb(var(--accent-light-rgb)) 0%, rgb(var(--accent-neon-rgb)) 100%);
|
||||
/* Animated gradient border using border-image-like technique */
|
||||
.watchlist-button::before,
|
||||
.wishlist-button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 22px;
|
||||
padding: 1.5px;
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Wishlist button states based on count */
|
||||
.wishlist-button.wishlist-inactive {
|
||||
background: linear-gradient(135deg, #404040 0%, #505050 100%);
|
||||
color: #888888;
|
||||
.watchlist-button::before {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
#ff8f00, #ffc107, #ffd54f, #ffab00, #ffc107, #ff8f00
|
||||
);
|
||||
background-size: 300% 300%;
|
||||
animation: heroBorderFlow 4s ease infinite;
|
||||
}
|
||||
|
||||
.wishlist-button.wishlist-inactive:hover {
|
||||
background: linear-gradient(135deg, #505050 0%, #666666 100%);
|
||||
color: #999999;
|
||||
.wishlist-button::before {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgb(var(--accent-rgb)),
|
||||
rgb(var(--accent-light-rgb)),
|
||||
rgb(var(--accent-neon-rgb)),
|
||||
rgb(var(--accent-rgb)),
|
||||
rgb(var(--accent-light-rgb)),
|
||||
rgb(var(--accent-rgb))
|
||||
);
|
||||
background-size: 300% 300%;
|
||||
animation: heroBorderFlow 4s ease infinite;
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
.wishlist-button.wishlist-active {
|
||||
background: linear-gradient(135deg, rgb(var(--accent-rgb)) 0%, rgb(var(--accent-light-rgb)) 100%);
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.wishlist-button.wishlist-active:hover {
|
||||
background: linear-gradient(135deg, rgb(var(--accent-light-rgb)) 0%, rgb(var(--accent-neon-rgb)) 100%);
|
||||
@keyframes heroBorderFlow {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
/* Outer glow */
|
||||
.watchlist-button {
|
||||
background: linear-gradient(135deg, #ffc107 0%, #ffca28 100%);
|
||||
box-shadow: 0 0 14px rgba(255, 193, 7, 0.12), 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.wishlist-button {
|
||||
box-shadow: 0 0 14px rgba(var(--accent-rgb), 0.12), 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.watchlist-button:hover {
|
||||
background: linear-gradient(135deg, #ffca28 0%, #ffd54f 100%);
|
||||
box-shadow: 0 0 24px rgba(255, 193, 7, 0.25), 0 4px 20px rgba(0, 0, 0, 0.4);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.wishlist-button:hover {
|
||||
box-shadow: 0 0 24px rgba(var(--accent-rgb), 0.25), 0 4px 20px rgba(0, 0, 0, 0.4);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Icon */
|
||||
.hero-btn-icon {
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Label */
|
||||
.hero-btn-label {
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
/* Watchlist text color tint */
|
||||
.watchlist-button .hero-btn-label {
|
||||
color: #ffd54f;
|
||||
}
|
||||
|
||||
.wishlist-button .hero-btn-label {
|
||||
color: rgb(var(--accent-light-rgb));
|
||||
}
|
||||
|
||||
/* Count badge */
|
||||
.hero-btn-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 22px;
|
||||
height: 22px;
|
||||
padding: 0 6px;
|
||||
border-radius: 11px;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.watchlist-button .hero-btn-badge {
|
||||
background: rgba(255, 193, 7, 0.15);
|
||||
color: #ffc107;
|
||||
border: 1px solid rgba(255, 193, 7, 0.3);
|
||||
}
|
||||
|
||||
.wishlist-button .hero-btn-badge {
|
||||
background: rgba(var(--accent-rgb), 0.15);
|
||||
color: rgb(var(--accent-light-rgb));
|
||||
border: 1px solid rgba(var(--accent-rgb), 0.3);
|
||||
}
|
||||
|
||||
.hero-btn-badge.has-items {
|
||||
animation: badgePulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.watchlist-button .hero-btn-badge.has-items {
|
||||
background: rgba(255, 193, 7, 0.25);
|
||||
border-color: rgba(255, 193, 7, 0.5);
|
||||
}
|
||||
|
||||
.wishlist-button .hero-btn-badge.has-items {
|
||||
background: rgba(var(--accent-rgb), 0.25);
|
||||
border-color: rgba(var(--accent-rgb), 0.5);
|
||||
}
|
||||
|
||||
@keyframes badgePulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.12); }
|
||||
}
|
||||
|
||||
/* Shimmer sweep — clipped inside button */
|
||||
.hero-btn-shimmer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 22px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-btn-shimmer::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
animation: shimmerSweep 5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.watchlist-button .hero-btn-shimmer::after {
|
||||
background: linear-gradient(
|
||||
105deg,
|
||||
transparent 30%,
|
||||
rgba(255, 193, 7, 0.1) 50%,
|
||||
transparent 70%
|
||||
);
|
||||
}
|
||||
|
||||
.wishlist-button .hero-btn-shimmer::after {
|
||||
background: linear-gradient(
|
||||
105deg,
|
||||
transparent 30%,
|
||||
rgba(var(--accent-rgb), 0.1) 50%,
|
||||
transparent 70%
|
||||
);
|
||||
animation-delay: 2.5s;
|
||||
}
|
||||
|
||||
@keyframes shimmerSweep {
|
||||
0%, 100% { left: -100%; }
|
||||
40%, 60% { left: 100%; }
|
||||
}
|
||||
|
||||
/* ── Wishlist inactive state ── */
|
||||
.wishlist-button.wishlist-inactive {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.wishlist-button.wishlist-inactive::before {
|
||||
background: linear-gradient(135deg, #444, #555, #444);
|
||||
background-size: 100% 100%;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.wishlist-button.wishlist-inactive .hero-btn-label {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.wishlist-button.wishlist-inactive .hero-btn-badge {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: #666;
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.wishlist-button.wishlist-inactive .hero-btn-shimmer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wishlist-button.wishlist-inactive:hover {
|
||||
box-shadow: 0 0 12px rgba(255, 255, 255, 0.08), 0 4px 20px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Active wishlist just uses the normal accent styles above */
|
||||
.wishlist-button.wishlist-active {
|
||||
/* already styled by default .wishlist-button rules */
|
||||
}
|
||||
|
||||
/* Service Status Grid */
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@
|
|||
let onDashboard = false;
|
||||
let expandProgress = 0;
|
||||
let staggerTimers = [];
|
||||
let collapseDelay = null;
|
||||
const COLLAPSE_DELAY_MS = 7000;
|
||||
|
||||
// ── Init ──
|
||||
|
||||
|
|
@ -251,6 +253,11 @@
|
|||
|
||||
function onMouseEnter() {
|
||||
if (!onDashboard) return;
|
||||
// Cancel any pending collapse
|
||||
if (collapseDelay) {
|
||||
clearTimeout(collapseDelay);
|
||||
collapseDelay = null;
|
||||
}
|
||||
if (state === 'orbs' || state === 'collapsing') {
|
||||
state = 'expanding';
|
||||
expandProgress = 0;
|
||||
|
|
@ -260,7 +267,14 @@
|
|||
function onMouseLeave() {
|
||||
if (!onDashboard) return;
|
||||
if (state === 'expanded' || state === 'expanding') {
|
||||
enterCollapsingState();
|
||||
// Delay before collapsing back to orbs
|
||||
if (collapseDelay) clearTimeout(collapseDelay);
|
||||
collapseDelay = setTimeout(() => {
|
||||
collapseDelay = null;
|
||||
if (state === 'expanded' || state === 'expanding') {
|
||||
enterCollapsingState();
|
||||
}
|
||||
}, COLLAPSE_DELAY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -551,6 +565,7 @@
|
|||
sparks = [];
|
||||
enterOrbState();
|
||||
} else if (!onDashboard && wasDashboard) {
|
||||
if (collapseDelay) { clearTimeout(collapseDelay); collapseDelay = null; }
|
||||
stopLoop();
|
||||
state = 'idle';
|
||||
sparks = [];
|
||||
|
|
|
|||
Loading…
Reference in a new issue