diff --git a/src/renderer-streamers.ts b/src/renderer-streamers.ts index 6b76c4a..5f82d2f 100644 --- a/src/renderer-streamers.ts +++ b/src/renderer-streamers.ts @@ -682,7 +682,19 @@ async function selectStreamer(name: string, forceRefresh = false): Promise updateStatus(UI_TEXT.status.noLogin, false); } - byId('vodGrid').innerHTML = `

${UI_TEXT.vods.loading}

`; + // Skeleton loader — six placeholder cards while VODs come in. Much + // less jarring than a "Loading..." text block in an otherwise blank + // grid. Shimmer animation is in CSS. + byId('vodGrid').innerHTML = Array.from({ length: 6 }, () => ` +
+
+
+
+
+
+
+
+ `).join(''); const userId = await window.api.getUserId(name); if (isStaleRequest()) { diff --git a/src/styles.css b/src/styles.css index ced23b9..f8af720 100644 --- a/src/styles.css +++ b/src/styles.css @@ -166,6 +166,80 @@ body { font-weight: 600; } +/* ============================================ + VOD SKELETON CARDS — shown while VODs load + ============================================ */ +.vod-card-skeleton { + cursor: default; + pointer-events: none; + border-color: transparent !important; + box-shadow: none !important; + transform: none !important; +} + +.vod-card-skeleton .vod-skel-thumb { + width: 100%; + aspect-ratio: 16/9; + background: linear-gradient(90deg, var(--bg-elevated) 0%, rgba(255,255,255,0.04) 50%, var(--bg-elevated) 100%); + background-size: 200% 100%; + animation: skel-shimmer 1.5s linear infinite; +} + +.vod-card-skeleton .vod-skel-line { + height: 14px; + background: linear-gradient(90deg, var(--bg-elevated) 0%, rgba(255,255,255,0.06) 50%, var(--bg-elevated) 100%); + background-size: 200% 100%; + border-radius: 3px; + animation: skel-shimmer 1.5s linear infinite; +} + +@keyframes skel-shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +/* ============================================ + TAB SWITCH FADE — subtle 180ms ease-in + ============================================ */ +.tab-content.active { + animation: tab-fade-in 0.18s ease-out; +} + +@keyframes tab-fade-in { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0); } +} + +/* ============================================ + SCROLLBAR — twitch-themed, thin, subtle + ============================================ */ +*::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +*::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.02); +} + +*::-webkit-scrollbar-thumb { + background: rgba(145, 70, 255, 0.25); + border-radius: 5px; + border: 2px solid transparent; + background-clip: padding-box; + transition: background 0.2s; +} + +*::-webkit-scrollbar-thumb:hover { + background: rgba(145, 70, 255, 0.55); + background-clip: padding-box; + border: 2px solid transparent; +} + +*::-webkit-scrollbar-corner { + background: transparent; +} + .add-streamer { padding: 10px; display: flex; @@ -1836,45 +1910,77 @@ body.theme-light .modal { left: 0; right: 0; bottom: 0; - background: rgba(0, 0, 0, 0.7); + background: rgba(0, 0, 0, 0.65); display: none; justify-content: center; align-items: center; z-index: 1000; + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + animation: modal-overlay-fade 0.2s ease-out; } .modal-overlay.show { display: flex; } +@keyframes modal-overlay-fade { + from { opacity: 0; } + to { opacity: 1; } +} + .modal { background: var(--bg-card); - border-radius: 12px; - padding: 25px; + border: 1px solid var(--border-soft); + border-radius: 14px; + padding: 25px 28px; width: 90%; max-width: 500px; max-height: 90vh; overflow-y: auto; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(145, 70, 255, 0.10); + animation: modal-pop 0.22s cubic-bezier(0.16, 1, 0.3, 1); + position: relative; +} + +@keyframes modal-pop { + from { opacity: 0; transform: scale(0.96) translateY(8px); } + to { opacity: 1; transform: scale(1) translateY(0); } } .modal h2 { - margin-bottom: 20px; + margin-bottom: 18px; font-size: 18px; + font-weight: 600; + letter-spacing: -0.2px; + color: var(--text); } .modal-close { - float: right; - background: none; - border: none; + position: absolute; + top: 14px; + right: 14px; + width: 30px; + height: 30px; + background: transparent; + border: 1px solid var(--border-soft); + border-radius: 8px; color: var(--text-secondary); - font-size: 24px; + font-size: 16px; cursor: pointer; padding: 0; line-height: 1; + transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.12s; } .modal-close:hover { - color: var(--text); + color: #fff; + background: rgba(255, 70, 70, 0.18); + border-color: rgba(255, 70, 70, 0.55); +} + +.modal-close:active { + transform: scale(0.92); } .slider-group {