diff --git a/css/style.css b/css/style.css index 031d7f0a..bae2bb11 100644 --- a/css/style.css +++ b/css/style.css @@ -74,6 +74,29 @@ header img { box-shadow: var(--shadow-md); } +.video-card--enter { + opacity: 0; + animation: video-card-fade-in 220ms ease-out forwards; +} + +@keyframes video-card-fade-in { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (prefers-reduced-motion: reduce) { + .video-card--enter { + opacity: 1; + animation: none; + } +} + .video-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); diff --git a/js/app.js b/js/app.js index 21bb3628..82290ff5 100644 --- a/js/app.js +++ b/js/app.js @@ -500,6 +500,7 @@ class bitvidApp { this.profileCache = new Map(); this.lastRenderedVideoSignature = null; this._lastRenderedVideoListElement = null; + this.renderedVideoIds = new Set(); // NEW: reference to the login modal's close button this.closeLoginModalBtn = @@ -2158,6 +2159,7 @@ class bitvidApp { // 1) If no videos if (!dedupedVideos.length) { + this.renderedVideoIds.clear(); if (this.lastRenderedVideoSignature === EMPTY_VIDEO_LIST_SIGNATURE) { return; } @@ -2190,6 +2192,9 @@ class bitvidApp { } this.lastRenderedVideoSignature = signature; + const previouslyRenderedIds = new Set(this.renderedVideoIds); + this.renderedVideoIds.clear(); + const fullAllEventsArray = Array.from(nostrClient.allEvents.values()); const fragment = document.createDocumentFragment(); const authorSet = new Set(); @@ -2216,6 +2221,8 @@ class bitvidApp { video.isPrivate && canEdit ? "border-2 border-yellow-500" : "border-none"; + const isNewlyRendered = !previouslyRenderedIds.has(video.id); + const animationClass = isNewlyRendered ? "video-card--enter" : ""; const timeAgo = this.formatTimeAgo(video.created_at); // Check if there's an older version @@ -2332,7 +2339,7 @@ class bitvidApp { ); const cardHtml = ` -