From 1b45afb6f42affd042af7761532349baf283b7c6 Mon Sep 17 00:00:00 2001 From: jessikitty Date: Wed, 20 May 2026 10:38:59 +1000 Subject: [PATCH] fix: pile photos fade in with sepia wash, bg transitions smoothly, drift is single-direction --- public/js/app.js | 86 +++++++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index 7466d8c..6f8b65a 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -5,7 +5,7 @@ var overlayVisible = true, overlayTimeout = null, selectedSource = null, selectedAlbumId = null; var selectedPersonId = null, isRunning = false, refreshTimer = null, urlDriven = false; var currentVideoPlaying = false; - var pileCanvas, pileCtx; // canvas for accumulated polaroid pile + var pileCanvas, pileCtx; var $setupScreen = document.getElementById('setup-screen'), $slideshowScreen = document.getElementById('slideshow-screen'); var $connectionStatus = document.getElementById('connection-status'), $setupContent = document.getElementById('setup-content'); @@ -56,12 +56,13 @@ // ========================= function initPileCanvas() { pileCanvas = document.getElementById('pile-canvas'); - pileCanvas.width = window.innerWidth * (window.devicePixelRatio || 1); - pileCanvas.height = window.innerHeight * (window.devicePixelRatio || 1); + var dpr = window.devicePixelRatio || 1; + pileCanvas.width = window.innerWidth * dpr; + pileCanvas.height = window.innerHeight * dpr; pileCanvas.style.width = window.innerWidth + 'px'; pileCanvas.style.height = window.innerHeight + 'px'; pileCtx = pileCanvas.getContext('2d'); - pileCtx.scale(window.devicePixelRatio || 1, window.devicePixelRatio || 1); + pileCtx.scale(dpr, dpr); } function clearPileCanvas() { @@ -72,49 +73,68 @@ } } + // Animate a polaroid fading onto the pile canvas function dropPhotoPile(imgSrc) { var img = new Image(); img.crossOrigin = 'anonymous'; img.onload = function () { var vw = window.innerWidth, vh = window.innerHeight; - // Polaroid size: ~18-25% of screen width var polaroidW = vw * (0.18 + Math.random() * 0.07); var padding = polaroidW * 0.04; var bottomPad = polaroidW * 0.12; var innerW = polaroidW - padding * 2; var innerH = innerW * (img.height / img.width); var totalH = innerH + padding + bottomPad; - - // Random position across the screen var cx = Math.random() * vw; var cy = Math.random() * vh; - var rot = (Math.random() - 0.5) * 30; // -15 to +15 degrees + var rot = (Math.random() - 0.5) * 30; - pileCtx.save(); - pileCtx.translate(cx, cy); - pileCtx.rotate(rot * Math.PI / 180); + // Fade in over ~1 second + var startTime = null; + var fadeDuration = 1000; - // Shadow - pileCtx.shadowColor = 'rgba(0,0,0,0.5)'; - pileCtx.shadowBlur = 15; - pileCtx.shadowOffsetX = 3; - pileCtx.shadowOffsetY = 5; + function drawFrame(timestamp) { + if (!startTime) startTime = timestamp; + var elapsed = timestamp - startTime; + var alpha = Math.min(elapsed / fadeDuration, 1); - // White polaroid border - pileCtx.fillStyle = '#f0ebe3'; - pileCtx.fillRect(-polaroidW/2, -totalH/2, polaroidW, totalH); + // We need to redraw this specific polaroid with increasing alpha + // To avoid clearing the whole canvas, we draw on a temp canvas and composite + pileCtx.save(); + pileCtx.globalAlpha = alpha; + pileCtx.translate(cx, cy); + pileCtx.rotate(rot * Math.PI / 180); - // Clear shadow for the image - pileCtx.shadowColor = 'transparent'; - pileCtx.shadowBlur = 0; - pileCtx.shadowOffsetX = 0; - pileCtx.shadowOffsetY = 0; + // Shadow + pileCtx.shadowColor = 'rgba(0,0,0,0.45)'; + pileCtx.shadowBlur = 18; + pileCtx.shadowOffsetX = 3; + pileCtx.shadowOffsetY = 6; - // Photo inside - pileCtx.drawImage(img, -polaroidW/2 + padding, -totalH/2 + padding, innerW, innerH); + // White polaroid border + pileCtx.fillStyle = '#ede8df'; + pileCtx.fillRect(-polaroidW/2, -totalH/2, polaroidW, totalH); - pileCtx.restore(); - console.log('[Frambe] Dropped polaroid onto pile'); + // Clear shadow for image + pileCtx.shadowColor = 'transparent'; + pileCtx.shadowBlur = 0; + pileCtx.shadowOffsetX = 0; + pileCtx.shadowOffsetY = 0; + + // Photo + pileCtx.drawImage(img, -polaroidW/2 + padding, -totalH/2 + padding, innerW, innerH); + + // Sepia/warm wash over the photo + pileCtx.fillStyle = 'rgba(160, 130, 80, 0.15)'; + pileCtx.fillRect(-polaroidW/2 + padding, -totalH/2 + padding, innerW, innerH); + + pileCtx.restore(); + + if (alpha < 1) { + requestAnimationFrame(drawFrame); + } + } + requestAnimationFrame(drawFrame); }; img.onerror = function () { console.warn('[Frambe] Pile image failed to load'); }; img.src = imgSrc; @@ -163,21 +183,20 @@ var thumbUrl = '/api/assets/' + asset.id + '/thumbnail?size=preview'; console.log('[Frambe] Showing ' + (isVideo ? 'VIDEO' : 'PHOTO') + ': ' + (asset.originalFileName || asset.id)); - // Drop previous photo onto the pile canvas - if (currentIndex > 0 || pileCanvas) { + // Drop previous photo onto pile + if (currentIndex > 0) { var pi = currentIndex - 1; if (pi < 0) pi = assets.length - 1; - if (assets[pi]) { dropPhotoPile('/api/assets/' + assets[pi].id + '/thumbnail?size=thumbnail'); } + if (assets[pi]) dropPhotoPile('/api/assets/' + assets[pi].id + '/thumbnail?size=thumbnail'); } // Fade out main $mainFrame.classList.remove('visible'); var img = new Image(); - img.onload = function () { setTimeout(function () { displayAsset(asset, thumbUrl, isVideo); }, 400); }; + img.onload = function () { setTimeout(function () { displayAsset(asset, thumbUrl, isVideo); }, 500); }; img.onerror = function () { setTimeout(showNextAsset, 500); }; img.src = thumbUrl; - // Preload next var ni = index + 1; if (ni >= assets.length) ni = 0; if (assets[ni]) { var pre = new Image(); pre.src = '/api/assets/' + assets[ni].id + '/thumbnail?size=preview'; } } @@ -185,7 +204,6 @@ function displayAsset(asset, thumbUrl, isVideo) { if (config.backgroundBlur) { $bgBlur.style.backgroundImage = 'url(' + thumbUrl + ')'; $bgBlur.classList.add('visible'); } - // Always polaroid style $mainVideo.style.display = 'none'; $mainPhoto.style.display = 'none'; if (isVideo) {