(function(){
if(window.rmgLoaded) return;
window.rmgLoaded=true;
function escapeHtml(s){
return String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");
}
function init(){
var gallery=document.querySelector(".rmg-gallery");
if(!gallery) return;
var dataEl=gallery.querySelector(".rmg-data");
if(!dataEl) return;
var images=[];
try {
images=JSON.parse(dataEl.textContent||dataEl.innerText||"[]");
} catch(err){ return; }
if(!images.length) return;
var title=gallery.getAttribute("data-rmg-title")||"";
var viewer=document.createElement("div");
viewer.id="rmgViewer";
viewer.innerHTML =
'<div class="rmg-page" id="rmgPage">1 / ' + images.length + '</div>' +
(title ? '<div class="rmg-vtitle">' + escapeHtml(title) + '</div>':'') +
'<button type="button" class="rmg-close" id="rmgClose" aria-label="close">&times;</button>' +
'<button type="button" class="rmg-nav rmg-prev" id="rmgPrev" aria-label="prev">&lsaquo;</button>' +
'<button type="button" class="rmg-nav rmg-next" id="rmgNext" aria-label="next">&rsaquo;</button>' +
'<div class="rmg-track" id="rmgTrack"></div>' +
'<div class="rmg-guide" id="rmgGuide">' +
'<span class="rmg-guide-arrow">&laquo;</span>' +
'<span>swipe / pinch to zoom</span>' +
'</div>';
document.body.appendChild(viewer);
var track=viewer.querySelector("#rmgTrack");
var pageEl=viewer.querySelector("#rmgPage");
var closeBtn=viewer.querySelector("#rmgClose");
var prevBtn=viewer.querySelector("#rmgPrev");
var nextBtn=viewer.querySelector("#rmgNext");
var guide=viewer.querySelector("#rmgGuide");
var currentIndex=0;
var startX=0, startY=0, currentX=0;
var isDragging=false;
var isHorizontal=null;
var trackWidth=0;
var scrollY=0;
var zoomScale=1;
var zoomTx=0, zoomTy=0;
var startDist=0;
var startScale=1;
var startMidX=0, startMidY=0;
var startTx=0, startTy=0;
var isPinching=false;
var isPanning=false;
var panStartX=0, panStartY=0;
for (var i=0; i < images.length; i++){
var slide=document.createElement("div");
slide.className="rmg-slide";
var inner=document.createElement("div");
inner.className="rmg-slide-inner";
var img=document.createElement("img");
img.alt="menu" + (i+1);
img.setAttribute("data-src", images[i]);
inner.appendChild(img);
slide.appendChild(inner);
track.appendChild(slide);
}
function getCurrentInner(){
return track.children[currentIndex] ? track.children[currentIndex].querySelector(".rmg-slide-inner"):null;
}
function applyZoom(animate){
var inner=getCurrentInner();
if(!inner) return;
inner.style.transition=animate ? "transform 0.25s ease":"none";
inner.style.transform="translate3d(" + zoomTx + "px, " + zoomTy + "px, 0) scale(" + zoomScale + ")";
}
function resetZoom(animate){
zoomScale=1; zoomTx=0; zoomTy=0;
applyZoom(animate);
}
function clampPan(){
var inner=getCurrentInner();
if(!inner) return;
var img=inner.querySelector("img");
if(!img) return;
var rect=img.getBoundingClientRect();
var vw=viewer.offsetWidth;
var vh=viewer.offsetHeight;
var maxX=Math.max(0, (rect.width - vw) / 2);
var maxY=Math.max(0, (rect.height - vh) / 2);
if(zoomTx > maxX) zoomTx=maxX;
if(zoomTx < -maxX) zoomTx=-maxX;
if(zoomTy > maxY) zoomTy=maxY;
if(zoomTy < -maxY) zoomTy=-maxY;
}
function loadImage(idx){
var slides=track.children;
var indices=[idx-1, idx, idx+1];
for (var k=0; k < indices.length; k++){
var ii=indices[k];
if(ii >=0&&ii < slides.length){
var im=slides[ii].querySelector("img");
if(im&&!im.src){
var src=im.getAttribute("data-src");
if(src) im.src=src;
}}
}}
function setTransform(animate){
track.style.transition=animate ? "transform 0.3s ease":"none";
track.style.transform="translate3d(" + (-currentIndex * 100) + "%, 0, 0)";
pageEl.textContent=(currentIndex + 1) + " / " + images.length;
loadImage(currentIndex);
}
function changeSlide(newIdx, animate){
var oldInner=getCurrentInner();
currentIndex=newIdx;
if(oldInner){
oldInner.style.transition="none";
oldInner.style.transform="";
}
zoomScale=1; zoomTx=0; zoomTy=0;
setTransform(animate);
}
function openViewer(idx){
currentIndex=idx;
scrollY=window.pageYOffset||document.documentElement.scrollTop;
document.body.style.top="-" + scrollY + "px";
document.body.classList.add("rmg-locked");
viewer.classList.add("rmg-active");
void viewer.offsetWidth;
trackWidth=viewer.offsetWidth;
zoomScale=1; zoomTx=0; zoomTy=0;
setTransform(false);
guide.style.animation="none";
void guide.offsetWidth;
guide.style.animation="rmgFadeOut 4s ease-in-out forwards";
}
function closeViewer(){
viewer.classList.remove("rmg-active");
document.body.classList.remove("rmg-locked");
document.body.style.top="";
window.scrollTo(0, scrollY);
for (var i=0; i < track.children.length; i++){
var inner=track.children[i].querySelector(".rmg-slide-inner");
if(inner){
inner.style.transition="none";
inner.style.transform="";
}}
}
function goNext(){
if(currentIndex < images.length - 1) changeSlide(currentIndex + 1, true);
}
function goPrev(){
if(currentIndex > 0) changeSlide(currentIndex - 1, true);
}
gallery.addEventListener("click", function(e){
var thumb=null;
var node=e.target;
while (node&&node!==gallery){
var nc=node.className;
if(typeof nc==="string"&&(" " + nc + " ").indexOf(" rmg-thumb ")!==-1){
thumb=node; break;
}
node=node.parentNode;
}
if(!thumb) return;
e.preventDefault();
e.stopPropagation();
var idx=parseInt(thumb.getAttribute("data-rmg-idx"), 10)||0;
openViewer(idx);
}, true);
closeBtn.addEventListener("click", function(e){
e.preventDefault(); e.stopPropagation();
closeViewer();
});
nextBtn.addEventListener("click", goNext);
prevBtn.addEventListener("click", goPrev);
var lastTapTime=0;
var lastTapX=0, lastTapY=0;
function handleDoubleTap(x, y){
if(zoomScale > 1){
resetZoom(true);
}else{
zoomScale=2.5;
var vw=viewer.offsetWidth;
var vh=viewer.offsetHeight;
zoomTx=(vw / 2 - x) * (zoomScale - 1) / zoomScale;
zoomTy=(vh / 2 - y) * (zoomScale - 1) / zoomScale;
clampPan();
applyZoom(true);
}}
function distance(t1, t2){
var dx=t1.clientX - t2.clientX;
var dy=t1.clientY - t2.clientY;
return Math.sqrt(dx*dx + dy*dy);
}
track.addEventListener("touchstart", function(e){
if(e.touches.length===2){
isPinching=true;
isDragging=false;
isPanning=false;
startDist=distance(e.touches[0], e.touches[1]);
startScale=zoomScale;
startMidX=(e.touches[0].clientX + e.touches[1].clientX) / 2;
startMidY=(e.touches[0].clientY + e.touches[1].clientY) / 2;
startTx=zoomTx;
startTy=zoomTy;
var inner=getCurrentInner();
if(inner) inner.style.transition="none";
if(e.cancelable) e.preventDefault();
return;
}
if(e.touches.length===1){
var t=e.touches[0];
if(zoomScale > 1){
isPanning=true;
isDragging=false;
panStartX=t.clientX - zoomTx;
panStartY=t.clientY - zoomTy;
var inner2=getCurrentInner();
if(inner2) inner2.style.transition="none";
}else{
startX=t.clientX; startY=t.clientY; currentX=startX;
isDragging=true; isHorizontal=null;
trackWidth=viewer.offsetWidth;
track.style.transition="none";
var now=Date.now();
var dx=Math.abs(t.clientX - lastTapX);
var dy=Math.abs(t.clientY - lastTapY);
if(now - lastTapTime < 300&&dx < 30&&dy < 30){
handleDoubleTap(t.clientX, t.clientY);
lastTapTime=0;
isDragging=false;
if(e.cancelable) e.preventDefault();
}else{
lastTapTime=now;
lastTapX=t.clientX;
lastTapY=t.clientY;
}}
}}, {passive: false});
track.addEventListener("touchmove", function(e){
if(isPinching&&e.touches.length===2){
var dist=distance(e.touches[0], e.touches[1]);
zoomScale=startScale * (dist / startDist);
if(zoomScale < 1) zoomScale=1;
if(zoomScale > 5) zoomScale=5;
var midX=(e.touches[0].clientX + e.touches[1].clientX) / 2;
var midY=(e.touches[0].clientY + e.touches[1].clientY) / 2;
zoomTx=startTx + (midX - startMidX);
zoomTy=startTy + (midY - startMidY);
clampPan();
applyZoom(false);
if(e.cancelable) e.preventDefault();
return;
}
if(isPanning&&e.touches.length===1){
var t=e.touches[0];
zoomTx=t.clientX - panStartX;
zoomTy=t.clientY - panStartY;
clampPan();
applyZoom(false);
if(e.cancelable) e.preventDefault();
return;
}
if(isDragging&&e.touches.length===1){
var tt=e.touches[0];
var ddx=tt.clientX - startX;
var ddy=tt.clientY - startY;
if(isHorizontal===null){
if(Math.abs(ddx) > 8||Math.abs(ddy) > 8){
isHorizontal=Math.abs(ddx) > Math.abs(ddy);
}}
if(isHorizontal===false) return;
currentX=tt.clientX;
var offset=(-currentIndex * 100) + (ddx / trackWidth * 100);
track.style.transform="translate3d(" + offset + "%, 0, 0)";
if(e.cancelable) e.preventDefault();
}}, {passive: false});
track.addEventListener("touchend", function(e){
if(isPinching){
if(e.touches.length===0){
isPinching=false;
if(zoomScale <=1.05){
resetZoom(true);
}}
return;
}
if(isPanning){
isPanning=false;
return;
}
if(isDragging){
isDragging=false;
var dx=currentX - startX;
var threshold=trackWidth * 0.18;
if(isHorizontal&&dx < -threshold&&currentIndex < images.length - 1){
changeSlide(currentIndex + 1, true);
}else if(isHorizontal&&dx > threshold&&currentIndex > 0){
changeSlide(currentIndex - 1, true);
}else{
setTransform(true);
}}
});
track.addEventListener("touchcancel", function(){
isDragging=false;
isPinching=false;
isPanning=false;
setTransform(true);
});
var mouseDown=false;
track.addEventListener("mousedown", function(e){
if(zoomScale > 1) return;
mouseDown=true;
startX=e.clientX; currentX=startX;
trackWidth=viewer.offsetWidth;
track.style.transition="none";
e.preventDefault();
});
document.addEventListener("mousemove", function(e){
if(!mouseDown) return;
currentX=e.clientX;
var dx=currentX - startX;
var offset=(-currentIndex * 100) + (dx / trackWidth * 100);
track.style.transform="translate3d(" + offset + "%, 0, 0)";
});
document.addEventListener("mouseup", function(){
if(!mouseDown) return;
mouseDown=false;
var dx=currentX - startX;
var threshold=trackWidth * 0.18;
if(dx < -threshold&&currentIndex < images.length - 1){
changeSlide(currentIndex + 1, true);
}else if(dx > threshold&&currentIndex > 0){
changeSlide(currentIndex - 1, true);
}else{
setTransform(true);
}});
document.addEventListener("keydown", function(e){
if(!viewer.classList.contains("rmg-active")) return;
if(e.key==="ArrowRight") goNext();
else if(e.key==="ArrowLeft") goPrev();
else if(e.key==="Escape") closeViewer();
});
window.addEventListener("resize", function(){
if(viewer.classList.contains("rmg-active")){
trackWidth=viewer.offsetWidth;
setTransform(false);
resetZoom(false);
}});
}
if(document.readyState==="loading"){
document.addEventListener("DOMContentLoaded", init);
}else{
init();
}})();