// Example hotspot configuration (percent coordinates) const WS_HOTSPOTS = [ { left: '25%', top: '60%' }, { left: '60%', top: '40%' }, { left: '75%', top: '70%' }, ]; function wsRenderHotspots() { const $layer = $('#ws-hotspots-layer'); if (!$layer.length) return; $layer.empty(); WS_HOTSPOTS.forEach((pos) => { const $hotspot = $('
') .addClass('ws-hotspot') .css({ left: pos.left, top: pos.top, transform: 'translate(-50%, -50%)', }); const $arrow = $('
').addClass('ws-hotspot-arrow'); $hotspot.append($arrow); $layer.append($hotspot); }); } // Scene-based image mapping const WS_SCENE_IMAGES = { 215: { target: '/images/teaser_subfigures/215/tgt.png', stereospace: '/images/teaser_subfigures/215/stereospace.png', lyra: '/images/teaser_subfigures/215/lyra.png', genstereo: '/images/teaser_subfigures/215/genstereo.png', stereodiffusion: '/images/teaser_subfigures/215/stereodiffusion.png', zerostereo: '/images/teaser_subfigures/215/zerostereo.png', }, 249: { target: '/images/teaser_subfigures/249/tgt.png', stereospace: '/images/teaser_subfigures/249/stereospace.png', lyra: '/images/teaser_subfigures/249/lyra.png', genstereo: '/images/teaser_subfigures/249/genstereo.png', stereodiffusion: '/images/teaser_subfigures/249/stereodiffusion.png', zerostereo: '/images/teaser_subfigures/249/zerostereo.png', }, 252: { target: '/images/teaser_subfigures/252/tgt.png', stereospace: '/images/teaser_subfigures/252/stereospace.png', lyra: '/images/teaser_subfigures/252/lyra.png', genstereo: '/images/teaser_subfigures/252/genstereo.png', stereodiffusion: '/images/teaser_subfigures/252/stereodiffusion.png', zerostereo: '/images/teaser_subfigures/252/zerostereo.png', }, 285: { target: '/images/teaser_subfigures/285/tgt.png', stereospace: '/images/teaser_subfigures/285/stereospace.png', lyra: '/images/teaser_subfigures/285/lyra.png', genstereo: '/images/teaser_subfigures/285/genstereo.png', stereodiffusion: '/images/teaser_subfigures/285/stereodiffusion.png', zerostereo: '/images/teaser_subfigures/285/zerostereo.png', }, 297: { target: '/images/teaser_subfigures/297/tgt.png', stereospace: '/images/teaser_subfigures/297/stereospace.png', lyra: '/images/teaser_subfigures/297/lyra.png', genstereo: '/images/teaser_subfigures/297/genstereo.png', stereodiffusion: '/images/teaser_subfigures/297/stereodiffusion.png', zerostereo: '/images/teaser_subfigures/297/zerostereo.png', }, }; $(document).ready(function() { // Initialize state let currentScene = '215'; let currentModel = 'stereospace'; let sliderInitialized = false; // map model keys -> readable label shown on the slider const WS_MODEL_LABELS = { src: 'Input', tgt: 'Ground Truth', stereospace: 'StereoSpace (ours)', genstereo: 'GenStereo', lyra: 'Lyra', stereodiffusion: 'StereoDiffusion', zerostereo: 'ZeroStereo', }; // preload helper function wsPreloadImage(url) { return new Promise((resolve, reject) => { if (!url) return resolve(); // if already loaded by browser, resolve quickly const cached = Array.from(document.images).find(img => img.src && img.src.endsWith(url)); const img = new Image(); img.onload = () => resolve(url); img.onerror = () => reject(new Error('Failed to load ' + url)); img.src = url; }); } // Update labels on an already-initialized twentytwenty instance function wsSetSliderLabels(beforeLabel, afterLabel) { $('#ws-comparison-slider .twentytwenty-before-label').attr('data-content', beforeLabel); $('#ws-comparison-slider .twentytwenty-after-label').attr('data-content', afterLabel); const $slider = $('#ws-comparison-slider'); const $api = $slider.data('twentytwenty'); if ($api) { $api.adjustSlider(0.5); } } // Function to update images function updateImages(scene, model) { currentScene = scene; currentModel = model; const sceneData = WS_SCENE_IMAGES[scene]; if (!sceneData) return; // Preload all images we will use, then swap and (re)initialize slider. const leftUrl = sceneData.target; const rightUrl = sceneData[model]; // show light loading state if you have CSS for it $('#ws-comparison-container').addClass('ws-loading'); Promise.all([ wsPreloadImage(leftUrl), wsPreloadImage(rightUrl), ]).then(() => { $('#ws-image-left').attr('src', leftUrl); $('#ws-image-right').attr('src', rightUrl); const modelLabel = WS_MODEL_LABELS[model] || model; setTimeout(() => { wsSetSliderLabels('Ground Truth', modelLabel); $('#ws-comparison-container').removeClass('ws-loading'); }, 60); }).catch((err) => { console.warn('Image preload failed:', err); $('#ws-image-left').attr('src', leftUrl); $('#ws-image-right').attr('src', rightUrl); if (sliderInitialized) $('#ws-comparison-slider').trigger('destroy'); wsSetSliderLabels('Ground Truth', modelLabel); $('#ws-comparison-container').removeClass('ws-loading'); }); } // Gallery item clicks $('.ws-gallery-item').on('click', function() { $('.ws-gallery-item').removeClass('is-active'); $(this).addClass('is-active'); const scene = $(this).data('scene'); updateImages(scene, currentModel); }); // Model selector clicks $('.ws-model-pill[data-model]').on('click', function() { $('.ws-model-pill[data-model]').removeClass('is-active'); $(this).addClass('is-active'); const model = $(this).data('model'); updateImages(currentScene, model); }); // Initialize with default scene and model updateImages(currentScene, currentModel); });