// A native modal keeps the underlying page inert and provides keyboard dismissal. // Links remain useful when JavaScript is unavailable or the browser lacks dialog. export function installPhotoViewer(doc=document) { if(doc.documentElement.dataset.photoViewerInstalled)return; if(typeof HTMLDialogElement==='undefined'||typeof HTMLDialogElement.prototype.showModal!=='function')return; doc.documentElement.dataset.photoViewerInstalled='true'; let active=null; doc.defaultView?.addEventListener('popstate',()=>active?.close()); doc.addEventListener('click',event=>{ if(event.defaultPrevented||event.button!==0||event.ctrlKey||event.metaKey||event.shiftKey||event.altKey)return; const trigger=event.target instanceof Element?event.target.closest('a[data-photo-viewer]'):null; if(!trigger)return; const thumbnail=trigger.querySelector('img');if(!thumbnail)return; const url=new URL(trigger.href,doc.baseURI); if(url.origin!==location.origin||!/^\/photography\/[a-z0-9/_-]+\.(?:png|jpe?g|webp)$/i.test(url.pathname))return; event.preventDefault();if(active)return; const dialog=doc.createElement('dialog');dialog.className='ar-photo-viewer'; const toolbar=doc.createElement('header');toolbar.className='ar-photo-viewer-toolbar'; const heading=doc.createElement('h2');heading.id='ar-photo-viewer-title';heading.textContent=trigger.dataset.photoModel||trigger.closest('[data-photo-model]')?.dataset.photoModel||'Product image'; dialog.setAttribute('aria-labelledby',heading.id); const actions=doc.createElement('div');actions.className='ar-photo-viewer-actions'; const button=(label,title)=>{const el=doc.createElement('button');el.type='button';el.textContent=label;el.title=title;return el;}; const fit=button('Fit','Fit the complete image'),pixels=button('100%','View original image pixels'),close=button('Close ×','Close image viewer'); fit.setAttribute('aria-pressed','true');pixels.setAttribute('aria-pressed','false');close.setAttribute('aria-label','Close image viewer'); actions.append(fit,pixels,close);toolbar.append(heading,actions); const stage=doc.createElement('div');stage.className='ar-photo-viewer-stage';stage.tabIndex=0;stage.setAttribute('role','region');stage.setAttribute('aria-label','Product image. In 100% view, scroll to inspect the original image.'); const picture=doc.createElement('img');picture.src=url.href;picture.alt=thumbnail.alt;picture.decoding='async';picture.draggable=false; const notice=doc.createElement('p');notice.className='ar-photo-viewer-error';notice.hidden=true;notice.textContent='This image could not be loaded. Close the viewer and try again.';notice.setAttribute('role','status'); picture.addEventListener('error',()=>{picture.hidden=true;notice.hidden=false;fit.disabled=true;pixels.disabled=true;}); stage.append(picture,notice);dialog.append(toolbar,stage);doc.body.append(dialog);active=dialog; const setNative=native=>{stage.classList.toggle('ar-photo-viewer-native',native);fit.setAttribute('aria-pressed',String(!native));pixels.setAttribute('aria-pressed',String(native));stage.scrollTop=0;stage.scrollLeft=0;}; fit.addEventListener('click',()=>setNative(false));pixels.addEventListener('click',()=>setNative(true)); close.addEventListener('click',()=>dialog.close()); dialog.addEventListener('click',e=>{if(e.target===dialog){const rect=dialog.getBoundingClientRect();if(e.clientXrect.right||e.clientYrect.bottom)dialog.close();}}); dialog.addEventListener('close',()=>{dialog.remove();doc.documentElement.classList.remove('ar-photo-viewer-open');active=null;if(trigger.isConnected)trigger.focus();},{once:true}); doc.documentElement.classList.add('ar-photo-viewer-open');dialog.showModal();close.focus(); }); } if(typeof document!=='undefined')installPhotoViewer(document);