// === Frambe v1.3.0 - Vintage Polaroid Pile (Canvas) === (function () { 'use strict'; var config = {}, assets = [], currentIndex = -1, slideshowTimer = null; var overlayVisible = true, overlayTimeout = null, selectedSource = null, selectedAlbumId = null; var selectedPersonId = null, isRunning = false, refreshTimer = null, urlDriven = false; var currentVideoPlaying = false; var pileCanvas, pileCtx; // Shared polaroid proportions (match main frame CSS: ~3% sides, ~10% bottom) var FRAME_PAD_RATIO = 0.03; var FRAME_BOTTOM_RATIO = 0.10; var FRAME_COLOR = '#ede8df'; var $setupScreen = document.getElementById('setup-screen'), $slideshowScreen = document.getElementById('slideshow-screen'); var $connectionStatus = document.getElementById('connection-status'), $setupContent = document.getElementById('setup-content'); var $setupError = document.getElementById('setup-error'), $errorDetail = document.getElementById('error-detail'); var $albumsList = document.getElementById('albums-list'), $btnStart = document.getElementById('btn-start'); var $bgBlur = document.getElementById('bg-blur'), $mainFrame = document.getElementById('main-frame'); var $mainPhoto = document.getElementById('main-photo'), $mainVideo = document.getElementById('main-video'); var $clock = document.getElementById('clock'), $dateDisplay = document.getElementById('date-display'); var $exifInfo = document.getElementById('exif-info'), $progressFill = document.getElementById('progress-fill'); var $overlay = document.getElementById('overlay'), $btnSettings = document.getElementById('btn-settings'); var $progressBar = document.getElementById('progress-bar'); function getUrlParams() { var p={},s=window.location.search.substring(1);if(!s)return p;var pairs=s.split('&');for(var i=0;i';html+=thu?'':'
📁
';html+='
'+escapeHtml(a.albumName)+'
'+a.assetCount+' items
';}$albumsList.innerHTML=html;}catch(e){$albumsList.innerHTML='

Failed to load albums

';} } window.selectSource = function(src){selectedSource=src;selectedAlbumId=null;selectedPersonId=null;document.getElementById('btn-all-photos').classList.toggle('selected',src==='random');document.getElementById('btn-favorites').classList.toggle('selected',src==='favorites');var items=document.querySelectorAll('.album-item');for(var i=0;i0)console.log('[Frambe] Refresh added '+added+' new asset(s)');}catch(e){console.warn('[Frambe] Refresh failed: '+e.message);}}, (config.refreshInterval||300)*1000); } // ========================= // CANVAS PILE // ========================= function initPileCanvas() { pileCanvas = document.getElementById('pile-canvas'); 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(dpr, dpr); } function clearPileCanvas() { if (pileCtx) { pileCtx.setTransform(1,0,0,1,0,0); pileCtx.clearRect(0, 0, pileCanvas.width, pileCanvas.height); pileCtx.scale(window.devicePixelRatio || 1, window.devicePixelRatio || 1); } } function dropPhotoPile(imgSrc) { var img = new Image(); img.crossOrigin = 'anonymous'; img.onload = function () { var vw = window.innerWidth, vh = window.innerHeight; // Pile polaroid size: 18-25% of screen width var polaroidW = vw * (0.18 + Math.random() * 0.07); // Use shared proportions to match main frame var pad = polaroidW * FRAME_PAD_RATIO; var bottomPad = polaroidW * FRAME_BOTTOM_RATIO; var innerW = polaroidW - pad * 2; var innerH = innerW * (img.height / img.width); var totalH = innerH + pad + bottomPad; var cx = Math.random() * vw, cy = Math.random() * vh; var rot = (Math.random() - 0.5) * 30; var startTime = null, fadeDuration = 1200; function drawFrame(timestamp) { if (!startTime) startTime = timestamp; var alpha = Math.min((timestamp - startTime) / fadeDuration, 1); pileCtx.save(); pileCtx.globalAlpha = alpha; pileCtx.translate(cx, cy); pileCtx.rotate(rot * Math.PI / 180); pileCtx.shadowColor = 'rgba(0,0,0,0.45)'; pileCtx.shadowBlur = 18; pileCtx.shadowOffsetX = 3; pileCtx.shadowOffsetY = 6; pileCtx.fillStyle = FRAME_COLOR; pileCtx.fillRect(-polaroidW/2, -totalH/2, polaroidW, totalH); pileCtx.shadowColor = 'transparent'; pileCtx.shadowBlur = 0; pileCtx.shadowOffsetX = 0; pileCtx.shadowOffsetY = 0; pileCtx.drawImage(img, -polaroidW/2 + pad, -totalH/2 + pad, innerW, innerH); // Sepia wash pileCtx.fillStyle = 'rgba(150, 120, 70, 0.2)'; pileCtx.fillRect(-polaroidW/2 + pad, -totalH/2 + pad, 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; } // ========================= // SLIDESHOW ENGINE // ========================= async function doStartSlideshow() { if (!selectedSource) return; $btnStart.disabled = true; $btnStart.innerHTML = ' Loading…'; try { await loadAssets(); if (!assets.length) { $btnStart.textContent = 'No photos found'; setTimeout(function(){$btnStart.textContent='▶ Start Slideshow';$btnStart.disabled=false;},2000); return; } $setupScreen.style.display = 'none'; $slideshowScreen.style.display = 'block'; document.body.classList.remove('setup-mode'); isRunning = true; initPileCanvas(); if (!config.showClock) $clock.style.display = 'none'; if (!config.showDate) $dateDisplay.style.display = 'none'; if (!config.showExif) $exifInfo.style.display = 'none'; if (!config.showProgress) $progressBar.style.display = 'none'; if (!config.backgroundBlur) $bgBlur.style.display = 'none'; updateClock(); setInterval(updateClock, 1000); currentIndex = -1; showNextAsset(); scheduleOverlayHide(); startRefreshTimer(); } catch (err) { console.error('[Frambe] Start failed: '+err.message); $btnStart.textContent='Error: '+err.message; setTimeout(function(){$btnStart.textContent='▶ Start Slideshow';$btnStart.disabled=false;},3000); } } window.startSlideshow = function () { doStartSlideshow(); }; window.exitSlideshow = function () { if (urlDriven) { window.location.href = window.location.pathname; return; } isRunning=false;clearTimeout(slideshowTimer);if(refreshTimer)clearInterval(refreshTimer);stopVideo(); $slideshowScreen.style.display='none';$setupScreen.style.display='flex';document.body.classList.add('setup-mode'); $btnStart.textContent='▶ Start Slideshow';$btnStart.disabled=false; $bgBlur.style.backgroundImage='';$bgBlur.classList.remove('visible');$mainFrame.classList.remove('visible');clearPileCanvas(); }; function showNextAsset() { currentIndex++;if(currentIndex>=assets.length){if(config.shuffle)shuffleArray(assets);currentIndex=0;}showAsset(currentIndex); } function showPrevAsset() { currentIndex--;if(currentIndex<0)currentIndex=assets.length-1;showAsset(currentIndex); } function showAsset(index) { if (!assets[index]) return; clearTimeout(slideshowTimer); stopVideo(); var asset = assets[index], isVideo = asset.type === 'VIDEO'; var thumbUrl = '/api/assets/' + asset.id + '/thumbnail?size=preview'; console.log('[Frambe] Showing ' + (isVideo ? 'VIDEO' : 'PHOTO') + ': ' + (asset.originalFileName || asset.id)); 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'); } $mainFrame.classList.remove('visible'); var img = new Image(); img.onload = function () { setTimeout(function () { displayAsset(asset, thumbUrl, isVideo); }, 500); }; img.onerror = function () { setTimeout(showNextAsset, 500); }; img.src = thumbUrl; 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'; } } function displayAsset(asset, thumbUrl, isVideo) { if (config.backgroundBlur) { $bgBlur.style.backgroundImage = 'url(' + thumbUrl + ')'; $bgBlur.classList.add('visible'); } $mainVideo.style.display = 'none'; $mainPhoto.style.display = 'none'; if (isVideo) { $mainVideo.style.display = 'block'; $mainVideo.src = '/api/assets/' + asset.id + '/video'; $mainVideo.poster = thumbUrl; $mainVideo.load(); $mainVideo.play().then(function(){ currentVideoPlaying=true; }).catch(function(e){ console.warn('[Frambe] Video autoplay failed: '+e.message); }); $mainVideo.onended = function () { currentVideoPlaying=false; showNextAsset(); }; slideshowTimer = setTimeout(function(){ if(currentVideoPlaying){showNextAsset();} }, Math.max((config.slideshowInterval||30)*3, 120)*1000); } else { $mainPhoto.style.display = 'block'; $mainPhoto.src = thumbUrl; slideshowTimer = setTimeout(showNextAsset, (config.slideshowInterval||30)*1000); } requestAnimationFrame(function () { $mainFrame.classList.add('visible'); }); updateExifInfo(asset); startProgress(isVideo ? null : (config.slideshowInterval||30)*1000); } function stopVideo() { if(currentVideoPlaying||$mainVideo.src){try{$mainVideo.pause();}catch(e){}$mainVideo.removeAttribute('src');$mainVideo.load();$mainVideo.onended=null;currentVideoPlaying=false;} } function updateExifInfo(a) { if(!config.showExif||!a.exifInfo){$exifInfo.textContent='';return;}var p=[],e=a.exifInfo,loc=[e.city,e.state,e.country].filter(Boolean).join(', ');if(loc)p.push(loc);if(e.dateTimeOriginal)p.push(formatDate(new Date(e.dateTimeOriginal)));else if(a.fileCreatedAt)p.push(formatDate(new Date(a.fileCreatedAt)));if(e.make||e.model)p.push([e.make,e.model].filter(Boolean).join(' '));if(a.type==='VIDEO')p.push('Video');$exifInfo.textContent=p.join(' · '); } function startProgress(ms) { if(!config.showProgress)return;$progressFill.style.transition='none';$progressFill.style.width='0%';$progressFill.offsetWidth;if(ms){$progressFill.style.transition='width '+ms+'ms linear';$progressFill.style.width='100%';} } function updateClock() { var n=new Date();if(config.showClock)$clock.textContent=padZero(n.getHours())+':'+padZero(n.getMinutes());if(config.showDate)$dateDisplay.textContent=n.toLocaleDateString(undefined,{weekday:'long',day:'numeric',month:'long',year:'numeric'}); } window.toggleOverlay = function(){overlayVisible=!overlayVisible;if(overlayVisible){$overlay.classList.remove('hidden');$btnSettings.classList.add('visible');scheduleOverlayHide();}else{$overlay.classList.add('hidden');$btnSettings.classList.remove('visible');}}; function scheduleOverlayHide(){clearTimeout(overlayTimeout);overlayTimeout=setTimeout(function(){$overlay.classList.add('hidden');$btnSettings.classList.remove('visible');overlayVisible=false;},8000);} window.nextPhoto = function(){showNextAsset();if(overlayVisible)scheduleOverlayHide();}; window.prevPhoto = function(){showPrevAsset();if(overlayVisible)scheduleOverlayHide();}; document.addEventListener('keydown',function(e){if(!isRunning)return;switch(e.key){case 'ArrowRight':case ' ':e.preventDefault();nextPhoto();break;case 'ArrowLeft':e.preventDefault();prevPhoto();break;case 'Escape':exitSlideshow();break;case 'f':toggleFullscreen();break;case 'i':toggleOverlay();break;}}); function toggleFullscreen(){if(!document.fullscreenElement&&!document.webkitFullscreenElement){var el=document.documentElement;if(el.requestFullscreen)el.requestFullscreen();else if(el.webkitRequestFullscreen)el.webkitRequestFullscreen();}else{if(document.exitFullscreen)document.exitFullscreen();else if(document.webkitExitFullscreen)document.webkitExitFullscreen();}} function shuffleArray(a){for(var i=a.length-1;i>0;i--){var j=Math.floor(Math.random()*(i+1));var t=a[i];a[i]=a[j];a[j]=t;}} function padZero(n){return n<10?'0'+n:''+n;} function formatDate(d){var m=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];return d.getDate()+' '+m[d.getMonth()]+' '+d.getFullYear();} function escapeHtml(s){var d=document.createElement('div');d.appendChild(document.createTextNode(s));return d.innerHTML;} async function requestWakeLock(){try{if('wakeLock' in navigator)await navigator.wakeLock.request('screen');}catch(e){}} document.addEventListener('visibilitychange',function(){if(document.visibilityState==='visible'&&isRunning)requestWakeLock();}); function preventSleep(){try{var v=document.createElement('video');v.setAttribute('playsinline','');v.setAttribute('muted','');v.setAttribute('loop','');v.style.cssText='position:absolute;width:1px;height:1px;opacity:0.01';v.src='data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAAhtZGF0AAAA1m1vb3YAAABsbXZoZAAAAAAAAAAAAAAAAAAAA+gAAAAAAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAYdHJhawAAAFx0a2hkAAAAAwAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAJBtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAADIAAAAAAAAhhdmMxAAAAAAAAAAAAAAAAAAAAAAAA';document.body.appendChild(v);v.play().catch(function(){});}catch(e){}} init();requestWakeLock();preventSleep(); })();