1(()=>{ 2 'use strict'; 3 const galleries=[...document.querySelectorAll('[data-room-gallery]')]; 4 if(!galleries.length)return; 5 const reduced=window.matchMedia('(prefers-reduced-motion: reduce)').matches; 6 const lang=document.documentElement.lang==='en'?'en':'th'; 7 let activeGallery=null,activeIndex=0,lastFocus=null,closeTimer=null,touchStartX=null; 8 9 const modal=document.createElement('div'); 10 modal.className='room-lightbox'; 11 modal.hidden=true; 12 modal.setAttribute('role','dialog'); 13 modal.setAttribute('aria-modal','true'); 14 modal.setAttribute('aria-label',lang==='en'?'Room photo gallery':'à¹à¸à¸¥à¹à¸¥à¸à¸£à¸µà¸ าà¸à¸«à¹à¸à¸à¸à¸±à¸'); 15 modal.innerHTML=` 16 <div class="room-lightbox-head"> 17 <p class="room-lightbox-title"></p> 18 <button class="room-lightbox-close" type="button" aria-label="${lang==='en'?'Close gallery':'à¸à¸´à¸à¹à¸à¸¥à¹à¸¥à¸à¸£à¸µ'}">Ã</button> 19 </div> 20 <div class="room-lightbox-stage"> 21 <button class="room-lightbox-nav prev" type="button" aria-label="${lang==='en'?'Previous photo':'ภาà¸à¸à¹à¸à¸à¸«à¸à¹à¸²'}">â¹</button> 22 <img class="room-lightbox-image" alt="" width="1440" height="810"> 23 <button class="room-lightbox-nav next" type="button" aria-label="${lang==='en'?'Next photo':'ภาà¸à¸à¸±à¸à¹à¸'}">âº</button> 24 </div> 25 <div class="room-lightbox-foot" aria-live="polite"></div>`; 26 document.body.append(modal); 27 const lbImage=modal.querySelector('.room-lightbox-image'); 28 const lbTitle=modal.querySelector('.room-lightbox-title'); 29 const lbCount=modal.querySelector('.room-lightbox-foot'); 30 const lbClose=modal.querySelector('.room-lightbox-close'); 31 32 const items=gallery=>[...gallery.querySelectorAll('.room-thumbs img')].map((img,i)=>({ 33 src:img.dataset.full||img.src,alt:img.alt||`${gallery.dataset.roomGallery} ${i+1}`,thumb:img 34 })); 35 36 const preload=(list,index)=>{ 37 [-1,1].forEach(step=>{const p=new Image();p.src=list[(index+step+list.length)%list.length].src;}); 38 }; 39 40 function setGalleryImage(gallery,index,animate=true){ 41 const list=items(gallery); if(!list.length)return; 42 index=(index+list.length)%list.length; 43 gallery.dataset.currentIndex=String(index); 44 const main=gallery.querySelector('.room-main'); 45 const stage=gallery.querySelector('.room-stage'); 46 const count=gallery.querySelector('.room-photo-count'); 47 const apply=()=>{ 48 main.src=list[index].src; main.alt=list[index].alt; 49 list.forEach((x,i)=>{x.thumb.classList.toggle('active',i===index);if(i===index)x.thumb.setAttribute('aria-current','true');else x.thumb.removeAttribute('aria-current');}); 50 if(count)count.textContent=`${index+1} / ${list.length}`; 51 list[index].thumb.scrollIntoView({behavior:reduced?'auto':'smooth',block:'nearest',inline:'nearest'}); 52 preload(list,index); 53 }; 54 if(!animate||reduced){apply();return;} 55 stage.classList.add('is-changing'); 56 const temp=new Image(); 57 temp.onload=()=>{apply();requestAnimationFrame(()=>stage.classList.remove('is-changing'));}; 58 temp.onerror=()=>{apply();stage.classList.remove('is-changing');}; 59 temp.src=list[index].src; 60 } 61 62 function showLightbox(index,animate=true){ 63 if(!activeGallery)return; 64 const list=items(activeGallery); if(!list.length)return; 65 activeIndex=(index+list.length)%list.length; 66 const item=list[activeIndex]; 67 lbTitle.textContent=activeGallery.dataset.roomGallery||''; 68 lbCount.textContent=`${activeIndex+1} / ${list.length}`; 69 lbImage.alt=item.alt; 70 if(animate&&!reduced)lbImage.classList.add('is-switching'); 71 const temp=new Image(); 72 temp.onload=()=>{ 73 lbImage.src=item.src; 74 requestAnimationFrame(()=>{lbImage.classList.add('is-ready');lbImage.classList.remove('is-switching');}); 75 preload(list,activeIndex); 76 }; 77 temp.onerror=()=>{lbImage.src=item.src;lbImage.classList.add('is-ready');lbImage.classList.remove('is-switching');}; 78 temp.src=item.src; 79 } 80 81 function openLightbox(gallery,index){ 82 clearTimeout(closeTimer); 83 activeGallery=gallery;activeIndex=index;lastFocus=document.activeElement; 84 modal.hidden=false;document.body.classList.add('no-scroll'); 85 lbImage.classList.remove('is-ready'); 86 requestAnimationFrame(()=>{modal.classList.add('is-open');showLightbox(index,false);lbClose.focus({preventScroll:true});}); 87 } 88 89 function closeLightbox(){ 90 modal.classList.remove('is-open');lbImage.classList.remove('is-ready');document.body.classList.remove('no-scroll'); 91 closeTimer=setTimeout(()=>{modal.hidden=true;activeGallery=null;if(lastFocus?.focus)lastFocus.focus({preventScroll:true});},reduced?0:280); 92 } 93
94 galleries.forEach(gallery=>{ 95 gallery.dataset.currentIndex='0'; 96 const thumbWrap=gallery.querySelector('.room-thumbs'); 97 const openBtn=gallery.querySelector('[data-room-open]'); 98 const prev=gallery.querySelector('[data-room-prev]'); 99 const next=gallery.querySelector('[data-room-next]'); 100 const current=()=>Number(gallery.dataset.currentIndex||0); 101 102 // Capture phase suppresses the earlier basic thumbnail handler from site.js. 103 thumbWrap?.addEventListener('click',e=>{ 104 const img=e.target.closest('img');if(!img)return; 105 e.preventDefault();e.stopImmediatePropagation(); 106 setGalleryImage(gallery,[...thumbWrap.querySelectorAll('img')].indexOf(img)); 107 },true); 108 thumbWrap?.addEventListener('keydown',e=>{ 109 const img=e.target.closest('img');if(!img)return; 110 const thumbs=[...thumbWrap.querySelectorAll('img')];let index=thumbs.indexOf(img); 111 if(e.key==='Enter'||e.key===' '){e.preventDefault();setGalleryImage(gallery,index);} 112 if(e.key==='ArrowRight'||e.key==='ArrowLeft'){ 113 e.preventDefault();index=(index+(e.key==='ArrowRight'?1:-1)+thumbs.length)%thumbs.length;thumbs[index].focus();setGalleryImage(gallery,index); 114 } 115 }); 116 prev?.addEventListener('click',()=>setGalleryImage(gallery,current()-1)); 117 next?.addEventListener('click',()=>setGalleryImage(gallery,current()+1)); 118 openBtn?.addEventListener('click',()=>openLightbox(gallery,current())); 119 }); 120 121 modal.querySelector('.room-lightbox-nav.prev').addEventListener('click',()=>showLightbox(activeIndex-1)); 122 modal.querySelector('.room-lightbox-nav.next').addEventListener('click',()=>showLightbox(activeIndex+1)); 123 lbClose.addEventListener('click',closeLightbox); 124 modal.addEventListener('click',e=>{if(e.target===modal)closeLightbox();}); 125 document.addEventListener('keydown',e=>{ 126 if(modal.hidden)return; 127 if(e.key==='Escape')closeLightbox(); 128 if(e.key==='ArrowLeft')showLightbox(activeIndex-1); 129 if(e.key==='ArrowRight')showLightbox(activeIndex+1); 130 if(e.key==='Tab'){ 131 const focusable=[...modal.querySelectorAll('button:not([disabled])')]; 132 if(!focusable.length)return; 133 const first=focusable[0],last=focusable[focusable.length-1]; 134 if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();} 135 else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();} 136 } 137 }); 138 modal.addEventListener('pointerdown',e=>{touchStartX=e.clientX;}); 139 modal.addEventListener('pointerup',e=>{ 140 if(touchStartX===null)return;const dx=e.clientX-touchStartX;touchStartX=null; 141 if(Math.abs(dx)>55)showLightbox(activeIndex+(dx<0?1:-1)); 142 }); 143})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.