.ba-wrap{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#f7f6f4;padding:40px 20px 48px} .ba-fb{font-size:13px;padding:7px 18px;border-radius:20px;border:1px solid #ddd;background:#fff;color:#555;cursor:pointer;transition:all .15s;font-family:inherit} .ba-fb:hover{border-color:#aaa;color:#111} .ba-fb.on{background:#111;color:#fff;border-color:#111} .ba-count{text-align:center;font-size:13px;color:#888;padding:10px 0 16px} .ba-card{background:#fff;border-radius:12px;overflow:hidden;border:1px solid #e8e4df;transition:box-shadow .2s;cursor:pointer;height:100%} .ba-card:hover{box-shadow:0 4px 20px rgba(0,0,0,.09)} /* single-image cards (unchanged look) */ .ba-img-wrap{position:relative;overflow:hidden;background:#f0eee9;height:210px;display:flex;align-items:center;justify-content:center} .ba-img{width:100%!important;height:100%!important;max-width:none!important;object-fit:contain!important;object-position:center!important;display:block;transition:transform .3s;padding:8px} .ba-card:hover .ba-img{transform:scale(1.01)} /* ── SPLIT-FRAME STRIP (montage cards) ── */ .ba-strip-wrap{position:relative;background:#eef0f4;height:210px} .ba-strip-scroll{display:flex;height:100%;overflow-x:auto;overflow-y:hidden;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;scrollbar-width:none} .ba-strip-scroll::-webkit-scrollbar{display:none} .ba-frame{flex:0 0 100%;min-width:0;overflow:hidden;height:100%;scroll-snap-align:center;display:flex;align-items:center;justify-content:center} .ba-frame-inner{width:100%;height:100%;background-repeat:no-repeat;background-color:#eef0f4} .ba-strip-arrow{position:absolute;top:50%;transform:translateY(-50%);width:30px;height:30px;border-radius:50%;background:rgba(0,0,0,.4);color:#fff;border:none;cursor:pointer;font-size:16px;line-height:1;display:flex;align-items:center;justify-content:center;z-index:2;opacity:0;transition:opacity .2s} .ba-card:hover .ba-strip-arrow{opacity:1} .ba-strip-arrow.pv{left:8px} .ba-strip-arrow.nx{right:8px} .ba-strip-arrow:disabled{opacity:0 !important;pointer-events:none} .ba-strip-count{position:absolute;top:8px;right:8px;background:rgba(0,0,0,.5);color:#fff;font-size:11px;padding:2px 8px;border-radius:10px;z-index:2} .ba-dots{position:absolute;bottom:8px;left:0;right:0;display:flex;justify-content:center;gap:5px;z-index:2;flex-wrap:wrap;padding:0 10px} .ba-dot{width:6px;height:6px;border-radius:50%;background:rgba(255,255,255,.55);box-shadow:0 0 2px rgba(0,0,0,.4);transition:background .2s,transform .2s} .ba-dot.on{background:#fff;transform:scale(1.35)} .ba-zoom{position:absolute;bottom:10px;right:10px;background:rgba(0,0,0,.45);color:#fff;border-radius:50%;width:32px;height:32px;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s;z-index:2} .ba-card:hover .ba-zoom{opacity:1} .ba-meta{padding:13px 15px 15px} .ba-tag{display:inline-block;font-size:11px;font-weight:500;padding:3px 10px;border-radius:4px;margin-bottom:7px} .ba-tag-aug{background:#e8f0fb;color:#1a4fa0} .ba-tag-lipo{background:#e6f4ea;color:#1a6e38} .ba-tag-bbl{background:#fce8e6;color:#c5221f} .ba-tag-body{background:#fef3e2;color:#b45309} .ba-tag-etching{background:#f0e8fb;color:#5b1aa0} .ba-tag-arm{background:#e8f7fb;color:#1a7a8f} .ba-tag-vein{background:#fce8f0;color:#a01a5b} .ba-card h3{font-size:14px;font-weight:500;color:#111;margin:0 0 4px;line-height:1.35} .ba-card p{font-size:12px;color:#666;line-height:1.55;margin:0} .ba-disc{text-align:center;font-size:11px;color:#aaa;padding:24px 0 0;max-width:640px;margin:0 auto;line-height:1.6} .ba-modal{display:none;position:fixed;inset:0;z-index:99999;align-items:center;justify-content:center} .ba-modal.open{display:flex} .ba-overlay{position:absolute;inset:0;background:rgba(0,0,0,.9)} .ba-modal-inner{position:relative;z-index:1;max-width:94vw;max-height:92vh;display:flex;flex-direction:column;align-items:center} .ba-modal-stage{max-width:94vw;display:flex;align-items:center;justify-content:center} .ba-modal-img{max-width:100%;max-height:78vh;border-radius:8px;display:block;object-fit:contain} .ba-mframe{max-width:94vw;height:78vh;background-repeat:no-repeat;background-color:#111;border-radius:8px} .ba-modal-meta{margin-top:14px;text-align:center;color:#fff} .ba-modal-meta strong{font-size:15px;display:block;margin-bottom:4px} .ba-modal-meta span{font-size:13px;color:rgba(255,255,255,.65)} .ba-modal-fcount{margin-top:8px;font-size:12px;color:rgba(255,255,255,.6)} .ba-modal-close{position:absolute;top:-14px;right:-14px;width:36px;height:36px;border-radius:50%;background:#fff;border:none;cursor:pointer;font-size:18px;display:flex;align-items:center;justify-content:center;color:#111;box-shadow:0 2px 8px rgba(0,0,0,.3);z-index:3} .ba-modal-prev,.ba-modal-next{position:fixed;top:50%;transform:translateY(-50%);background:rgba(255,255,255,.15);border:none;color:#fff;font-size:22px;width:44px;height:44px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s;z-index:2} .ba-modal-prev{left:16px} .ba-modal-next{right:16px} .ba-modal-prev:hover,.ba-modal-next:hover{background:rgba(255,255,255,.3)} All cases Breast augmentation Liposuction Brazilian butt lift Body contouring Abdominal etching Arm lift Spider vein treatment Real patient results. Individual outcomes vary. All procedures performed by board-certified plastic surgeons. ← × → const BASE="https://storage.googleapis.com/treatspace-prod-media/pracimg/u-3304/"; /* ── SPLIT MAP ────────────────────────────────────────────── Montages to break into separate frames, keyed by filename. Three ways to define one: {c: cols, r: rows} -> even grid (uses INSET) {cols:[[x0,x1],...], rows:[[y0,y1],...]} -> explicit rows x cols windows {frames:[[x,y,w,h],...]} -> fully custom windows (any layout) Frames read left-to-right, top-to-bottom (or in the order listed for `frames`). Not listed = shown as a single image (unchanged). NOTE: any entry that resolves to 2 or fewer frames is now rendered as a single whole image (see framesFor). Leaving 2-frame entries in the map is harmless — framesFor short-circuits them. INSET zooms even-grid frames in slightly to swallow dotted guide lines / seam drift — raise it if a sliver of the neighbor shows. All montage layouts below were measured from the page screenshot. Raise INSET (e.g. 0.03) if any frame shows a sliver of its neighbor. */ const INSET=0.02; const GRID={ /* stacked before / after */ "Slide2.webp":{c:1,r:2},"Slide3.webp":{c:1,r:2},"Slide4.webp":{c:1,r:2}, "Slide13.webp":{c:1,r:2},"Slide18.webp":{c:1,r:2}, /* three-across */ "Slide8.webp":{c:3,r:1}, /* 4 x 3 twelve-view grid (front / side / rear) */ "Slide9.webp":{c:4,r:3}, /* 2 x 3 six-view grids */ "Slide12.webp":{c:2,r:3},"Slide15.webp":{c:2,r:3},"Slide16.webp":{c:2,r:3}, /* two-across before | after */ "Slide1.webp":{c:2,r:1},"Slide5.webp":{c:2,r:1},"Slide6.webp":{c:2,r:1}, "Slide7.webp":{c:2,r:1},"Slide10.webp":{c:2,r:1},"Slide11.webp":{c:2,r:1}, "Slide14.webp":{c:2,r:1},"Slide17.webp":{c:2,r:1},"Slide19.webp":{c:2,r:1}, "Slide20.webp":{c:2,r:1},"Slide21.webp":{c:2,r:1}, "Lipo1.webp":{c:2,r:1},"Lipo2.webp":{c:2,r:1},"Lipo3.webp":{c:2,r:1}, "Lipo4.webp":{c:2,r:1},"Lipo5.webp":{c:2,r:1},"Lipo6.webp":{c:2,r:1}, "lipo7.webp":{c:2,r:1},"lipo8.webp":{c:2,r:1},"lipo9.webp":{c:2,r:1}, "lipo10.webp":{c:2,r:1},"lipo11.webp":{c:2,r:1},"lipo12.webp":{c:2,r:1}, "lipo13.webp":{c:2,r:1},"lipo14.webp":{c:2,r:1}, "abd.webp":{c:2,r:1},"abd1.webp":{c:2,r:1},"abd2.webp":{c:2,r:1}, "abd3.webp":{c:2,r:1},"abd4.webp":{c:2,r:1},"abd5.webp":{c:2,r:1}, "patient-5489-liposculpting-abdominal-etching-before-after-570x305.webp":{c:2,r:1}, "patient-3131-liposculpting-abdominal-etching-before-after-570x190.webp":{c:2,r:1}, "T.webp":{c:2,r:1},"T_4k6qJOH.webp":{c:2,r:1}, "patient-3180-vein-treatment-before-after-570x190.webp":{c:2,r:1}, "patient-3180-vein-treatment-before-after-1-570x190.webp":{c:2,r:1}, "patient-3180-vein-treatment-before-after-2-570x190.webp":{c:2,r:1}, "patient-3180-vein-treatment-before-after-3-570x190.webp":{c:2,r:1} }; const CASES=[ {img:"Slide1.webp",cat:"Breast Augmentation",title:"Augmentation + mastopexy",detail:"250cc Mentor Moderate+ with abdominal liposuction, age 42."}, {img:"Slide2.webp",cat:"Liposuction",title:"Abdominal liposuction",detail:"Age 19. Targeted abdominal contouring for a flatter midsection."}, {img:"Slide3.webp",cat:"BBL",title:"Brazilian butt lift",detail:"Full BBL with lipo harvest from flanks and back."}, {img:"Slide4.webp",cat:"Breast Augmentation",title:"Augmentation + lipo",detail:"300cc Mentor Moderate+ via BAM/IMF with abdominal lipo, age 37."}, {img:"Slide5.webp",cat:"Breast Augmentation",title:"Augmentation + perimasto",detail:"400cc Mentor Moderate+ with periareolar mastopexy and abdominal lipo, age 39."}, {img:"Slide6.webp",cat:"Breast Augmentation",title:"Lipo abd + breast",detail:"Abdominal liposuction combined with breast procedure."}, {img:"Slide7.webp",cat:"Breast Augmentation",title:"Augmentation + pexy",detail:"300cc Mentor Ultra with augmentation-mastopexy and abdominal lipo, age 32."}, {img:"Slide8.webp",cat:"Breast Augmentation",title:"Augmentation + pexy",detail:"325cc Mentor Moderate+ with full mastopexy and abdominal lipo, age 44."}, {img:"Slide9.webp",cat:"BBL",title:"Lipo + BBL",detail:"Abdominal lipo with BBL — front, side, and rear views."}, {img:"Slide10.webp",cat:"BBL",title:"Brazilian butt lift",detail:"BBL with back lipo harvest. Dramatic projection and waist definition."}, {img:"Slide11.webp",cat:"Body Contouring",title:"Reverse tummy tuck",detail:"Upper abdominal contouring via reverse abdominoplasty."}, {img:"Slide12.webp",cat:"Breast Augmentation",title:"Augmentation + pexy exchange",detail:"375cc implant exchange with mastopexy and abdominal lipo, age 51."}, {img:"Slide13.webp",cat:"Liposuction",title:"Abdominal liposuction",detail:"Lateral-view lipo showing significant projection reduction."}, {img:"Slide14.webp",cat:"Liposuction",title:"Liposuction",detail:"Full body liposuction with dramatic waist reduction."}, {img:"Slide15.webp",cat:"Body Contouring",title:"Abdominal etching",detail:"High-definition liposculpture for athletic abdominal definition."}, {img:"Slide16.webp",cat:"Breast Augmentation",title:"Augmentation + pexy",detail:"325cc Mentor Moderate+ via BAM/IMF with mastopexy, age 36."}, {img:"Slide17.webp",cat:"Liposuction",title:"Lipo — lateral view",detail:"Side-profile lipo result. Significant reduction in abdominal projection."}, {img:"Slide18.webp",cat:"Breast Augmentation",title:"Augmentation + pexy",detail:"400cc Mentor Moderate+ with augmentation-mastopexy and lipo, age 39."}, {img:"Slide19.webp",cat:"Liposuction",title:"Abdominal liposuction",detail:"Lateral abdominal and hip lipo. Smooth, contoured profile result."}, {img:"Slide20.webp",cat:"Breast Augmentation",title:"Augmentation + mastopexy",detail:"425cc Mentor Moderate+ with full mastopexy and abdominal lipo, age 43."}, {img:"Slide21.webp",cat:"Liposuction",title:"Liposuction",detail:"Abdominal and flank liposuction showing waist narrowing."}, {img:"Lipo1.webp",cat:"Liposuction",title:"Liposuction — posterior contouring",detail:"Posterior view showing flank and waist reduction with improved hourglass silhouette."}, {img:"Lipo2.webp",cat:"Liposuction",title:"Liposuction — BBL profile",detail:"Side view showing combined lipo harvest and BBL augmentation for improved projection."}, {img:"Lipo3.webp",cat:"Liposuction",title:"Liposuction — lateral torso",detail:"Lateral view demonstrating significant reduction in abdominal and flank projection."}, {img:"Lipo4.webp",cat:"Liposuction",title:"Liposuction — inner thighs",detail:"Inner thigh liposuction showing reduced thigh fullness and improved leg contour."}, {img:"Lipo5.webp",cat:"Liposuction",title:"Liposuction — posterior",detail:"Posterior lipo showing improved buttock shape and reduced hip fullness."}, {img:"Lipo6.webp",cat:"Liposuction",title:"Liposuction — abdomen",detail:"Frontal abdominal liposuction with significant reduction in midsection fullness."}, {img:"lipo7.webp",cat:"Liposuction",title:"Liposuction — upper abdomen",detail:"Upper and lower abdominal lipo with visible improvement in waist definition."}, {img:"lipo8.webp",cat:"Liposuction",title:"Liposuction — full torso",detail:"Full torso liposuction showing dramatic reduction from abdomen and flanks."}, {img:"lipo9.webp",cat:"Liposuction",title:"Liposuction — abdomen",detail:"Abdominal liposuction with smooth, flat midsection result."}, {img:"lipo10.webp",cat:"Liposuction",title:"Liposuction — waist contouring",detail:"Waist and flank lipo showing improved torso shape and narrower waistline."}, {img:"lipo11.webp",cat:"Liposuction",title:"Liposuction — torso",detail:"Full torso liposuction with combined breast and abdominal contouring."}, {img:"lipo12.webp",cat:"Liposuction",title:"Liposuction — upper and lower abdomen",detail:"Upper and lower abdominal lipo showing defined waistline and flatter profile."}, {img:"lipo13.webp",cat:"Liposuction",title:"Liposuction — flanks",detail:"Flank and waist liposuction with improved lateral silhouette."}, {img:"lipo14.webp",cat:"Liposuction",title:"Liposuction — full torso",detail:"Comprehensive torso lipo with significant improvement in overall body contour."}, {img:"abd.webp",cat:"Abdominal Etching",title:"Abdominal etching — liposculpting",detail:"High-definition liposculpture creating defined abdominal musculature."}, {img:"abd1.webp",cat:"Abdominal Etching",title:"Abdominal etching",detail:"Liposculpting with visible rectus abdominis definition and oblique contouring."}, {img:"abd2.webp",cat:"Abdominal Etching",title:"Abdominal etching",detail:"Athletic abdominal etching showing natural six-pack definition."}, {img:"abd3.webp",cat:"Abdominal Etching",title:"Abdominal etching",detail:"High-definition abdominal etching with improved waist and core definition."}, {img:"abd4.webp",cat:"Abdominal Etching",title:"Abdominal etching — male",detail:"Male abdominal etching showing dramatic muscular definition from liposculpture."}, {img:"abd5.webp",cat:"Abdominal Etching",title:"Abdominal etching",detail:"Full abdominal liposculpture with defined six-pack and flank reduction."}, {img:"patient-5489-liposculpting-abdominal-etching-before-after-570x305.webp",cat:"Abdominal Etching",title:"Abdominal etching — liposculpting",detail:"Liposculpting procedure showing dramatic before and after abdominal definition."}, {img:"patient-3131-liposculpting-abdominal-etching-before-after-570x190.webp",cat:"Abdominal Etching",title:"Abdominal etching — liposculpting",detail:"High-definition abdominal etching with significant improvement in muscle visibility."}, {img:"T.webp",cat:"Arm Lift",title:"Arm lift (brachioplasty) — right arm",detail:"Right arm brachioplasty showing significant reduction in upper arm skin laxity and improved contour."}, {img:"T_4k6qJOH.webp",cat:"Arm Lift",title:"Arm lift (brachioplasty) — left arm",detail:"Left arm brachioplasty demonstrating smooth, toned upper arm result following excess skin removal."}, {img:"patient-3180-vein-treatment-before-after-570x190.webp",cat:"Spider Vein Treatment",title:"Spider vein treatment — sclerotherapy",detail:"Sclerotherapy showing significant reduction in visible spider veins on the legs."}, {img:"patient-3180-vein-treatment-before-after-1-570x190.webp",cat:"Spider Vein Treatment",title:"Spider vein treatment",detail:"Sclerotherapy result with improved skin clarity and reduced vein visibility."}, {img:"patient-3180-vein-treatment-before-after-2-570x190.webp",cat:"Spider Vein Treatment",title:"Spider vein treatment",detail:"Spider vein treatment showing clearance of superficial leg veins."}, {img:"patient-3180-vein-treatment-before-after-3-570x190.webp",cat:"Spider Vein Treatment",title:"Spider vein treatment",detail:"Sclerotherapy with notable reduction in spider vein network density."} ]; const TAG={ "Breast Augmentation":"ba-tag-aug","Liposuction":"ba-tag-lipo","BBL":"ba-tag-bbl", "Body Contouring":"ba-tag-body","Abdominal Etching":"ba-tag-etching","Arm Lift":"ba-tag-arm", "Spider Vein Treatment":"ba-tag-vein" }; const ZOOM_SVG=''; let active="All",filtered=[],modalIdx=0; /* Hide every case that would generate a multi-image scrolling strip. */ function hasMultipleFrames(c){ const g=GRID[c.img]; if(!g) return false; if(g.frames) return g.frames.length>2; if(g.cols && g.rows) return (g.cols.length*g.rows.length)>2; if(g.c && g.r) return (g.c*g.r)>2; return false; } const VISIBLE_CASES=CASES.filter(function(c){ return !hasMultipleFrames(c); }); function win(x,y,w,h){ return {x:x,y:y,w:w,h:h,sizeX:100/w,sizeY:100/h,posX:w>=1?0:(x/(1-w))*100,posY:h>=1?0:(y/(1-h))*100}; } const TRIM_CACHE={}; function framesFor(c){ if(TRIM_CACHE[c.img]) return TRIM_CACHE[c.img]; const g=GRID[c.img]; if(!g) return null; const out=[]; if(g.frames){ g.frames.forEach(function(f){ out.push(win(f[0],f[1],f[2],f[3])); }); } else if(g.cols&&g.rows;){ g.rows.forEach(function(ry){ g.cols.forEach(function(cx){ out.push(win(cx[0],ry[0],cx[1]-cx[0],ry[1]-ry[0])); }); }); } else { for(let row=0;row236 && g>236 && b>236); if(!blank){ if(xxmaxX)maxX=xx; if(yymaxY)maxY=yy; } } } if(maxX<0) return f; /* all blank: keep the grid cell */ const pad=step*2; minX=Math.max(0,minX-pad); minY=Math.max(0,minY-pad); maxX=Math.min(W-1,maxX+pad); maxY=Math.min(H-1,maxY+pad); return win((X+minX)/NW,(Y+minY)/NH,(maxX-minX+1)/NW,(maxY-minY+1)/NH); } function analyzeStrip(scroll,c,nominalFrames){ if(TRIM_CACHE[c.img]) return; const direct=BASE+c.img; const proxied="https://wsrv.nl/?url="+encodeURIComponent(direct.replace(/^https?:\/\//,"")); const sources=[direct, proxied]; /* direct works if the bucket allows CORS; else the proxy adds it */ (function tryNext(idx){ if(idx>=sources.length) return; /* all failed: keep the grid windows */ const im=new Image(); im.crossOrigin="anonymous"; im.onload=function(){ let ctx; try{ const cv=document.createElement("canvas"); cv.width=im.naturalWidth; cv.height=im.naturalHeight; ctx=cv.getContext("2d",{willReadFrequently:true}); ctx.drawImage(im,0,0); }catch(e){ tryNext(idx+1); return; } const trimmed=[]; for(let i=0;ic.cat===active); const cnt=document.getElementById("ba-count"); if(cnt)cnt.textContent="Showing "+filtered.length+" case"+(filtered.length!==1?"s":""); const grid=document.getElementById("ba-grid"); if(!grid)return; grid.innerHTML=""; STRIPS=[]; filtered.forEach(function(c,i){ const col=document.createElement("div"); col.className="col-12 col-sm-6 col-lg-4"; const card=document.createElement("div"); card.className="ba-card"; card.dataset.i=i; const frames=null; /* multi-frame cards are intentionally hidden */ if(frames){ const wrap=document.createElement("div"); wrap.className="ba-strip-wrap"; const scroll=document.createElement("div"); scroll.className="ba-strip-scroll"; frames.forEach(function(f){ const fr=document.createElement("div"); fr.className="ba-frame"; const inner=document.createElement("div"); inner.className="ba-frame-inner"; inner.style.backgroundImage="url('"+BASE+c.img+"')"; fr.appendChild(inner); scroll.appendChild(fr); }); wrap.appendChild(scroll); STRIPS.push({scroll:scroll,frames:frames,img:c.img}); const pv=document.createElement("button"); pv.className="ba-strip-arrow pv"; pv.innerHTML="‹"; const nx=document.createElement("button"); nx.className="ba-strip-arrow nx"; nx.innerHTML="›"; const cnt2=document.createElement("div"); cnt2.className="ba-strip-count"; cnt2.textContent="1 / "+frames.length; const dots=document.createElement("div"); dots.className="ba-dots"; frames.forEach(function(_,di){const d=document.createElement("div"); d.className="ba-dot"+(di===0?" on":""); dots.appendChild(d);}); wrap.appendChild(pv); wrap.appendChild(nx); wrap.appendChild(cnt2); wrap.appendChild(dots); const zoom=document.createElement("div"); zoom.className="ba-zoom"; zoom.innerHTML=ZOOM_SVG; wrap.appendChild(zoom); card.appendChild(wrap); function cur(){var w=scroll.clientWidth;return w?Math.round(scroll.scrollLeft/w):0;} function update(){ const k=cur(); cnt2.textContent=(k+1)+" / "+frames.length; dots.querySelectorAll(".ba-dot").forEach(function(d,di){d.classList.toggle("on",di===k);}); pv.disabled=k<=0; nx.disabled=k>=frames.length-1; } scroll.addEventListener("scroll",function(){window.requestAnimationFrame(update);}); pv.addEventListener("click",function(e){e.stopPropagation();scroll.scrollBy({left:-scroll.clientWidth,behavior:"smooth"});}); nx.addEventListener("click",function(e){e.stopPropagation();scroll.scrollBy({left:scroll.clientWidth,behavior:"smooth"});}); zoom.addEventListener("click",function(e){e.stopPropagation();openModal(i,cur());}); let sx=0,moved=false; scroll.addEventListener("pointerdown",function(e){sx=e.clientX;moved=false;}); scroll.addEventListener("pointermove",function(e){if(Math.abs(e.clientX-sx)>6)moved=true;}); scroll.addEventListener("click",function(){if(!moved)openModal(i,cur());}); requestAnimationFrame(update); coverFrames(scroll,c,frames,update); } else { const wrap=document.createElement("div"); wrap.className="ba-img-wrap"; const img=document.createElement("img"); img.className="ba-img"; img.loading="lazy"; img.src=BASE+c.img; img.alt=c.title; const zoom=document.createElement("div"); zoom.className="ba-zoom"; zoom.innerHTML=ZOOM_SVG; wrap.appendChild(img); wrap.appendChild(zoom); card.appendChild(wrap); card.addEventListener("click",function(){openModal(i,0);}); } const meta=document.createElement("div"); meta.className="ba-meta"; meta.innerHTML=''+c.cat+''+c.title+''+c.detail+''; card.appendChild(meta); col.appendChild(card); grid.appendChild(col); }); } /* ── MODAL ── */ let modalFrames=null, modalFrameIdx=0, modalNat=null; function openModal(i,frameIdx){ modalIdx=i; const c=filtered[i]; const stage=document.getElementById("ba-modal-stage"); document.getElementById("ba-modal-title").textContent=c.title; document.getElementById("ba-modal-detail").textContent=c.detail; stage.innerHTML=""; modalFrames=framesFor(c); modalFrameIdx=frameIdx||0; modalNat=null; if(modalFrames){ if(modalFrameIdx>=modalFrames.length)modalFrameIdx=modalFrames.length-1; const box=document.createElement("div"); box.className="ba-mframe"; box.style.backgroundImage="url('"+BASE+c.img+"')"; box.style.visibility="hidden"; /* hide until sized to avoid a mis-scaled flash */ stage.appendChild(box); const im=new Image(); im.onload=function(){ modalNat={w:im.naturalWidth,h:im.naturalHeight}; showModalFrame(); }; im.src=BASE+c.img; if(im.complete && im.naturalWidth){ modalNat={w:im.naturalWidth,h:im.naturalHeight}; } showModalFrame(); } else { const img=document.createElement("img"); img.className="ba-modal-img"; img.src=BASE+c.img; img.alt=c.title; stage.appendChild(img); document.getElementById("ba-modal-fcount").textContent=""; } document.getElementById("ba-modal").classList.add("open"); document.body.style.overflow="hidden"; } function showModalFrame(){ if(!modalFrames)return; const box=document.querySelector("#ba-modal-stage .ba-mframe"); if(!box)return; const n=modalFrames.length; modalFrameIdx=(modalFrameIdx+n)%n; const f=modalFrames[modalFrameIdx]; document.getElementById("ba-modal-fcount").textContent="View "+(modalFrameIdx+1)+" of "+n; if(!modalNat){ return; } /* wait for load; onload calls this again */ const NW=modalNat.w, NH=modalNat.h; const maxW=window.innerWidth*0.94, maxH=window.innerHeight*0.78; const winW=f.w*NW, winH=f.h*NH; const scale=Math.min(maxW/winW, maxH/winH); /* contain: whole frame fits */ const boxW=scale*winW, boxH=scale*winH; box.style.width=Math.round(boxW)+"px"; box.style.height=Math.round(boxH)+"px"; box.style.backgroundSize=Math.round(scale*NW)+"px "+Math.round(scale*NH)+"px"; box.style.backgroundPosition=Math.round(-scale*f.x*NW)+"px "+Math.round(-scale*f.y*NH)+"px"; box.style.visibility="visible"; } function closeModal(){ document.getElementById("ba-modal").classList.remove("open"); document.body.style.overflow=""; modalFrames=null; modalNat=null; } /* Arrows: step frames within a montage, spilling over to the next/prev case */ function step(dir){ if(modalFrames){ const next=modalFrameIdx+dir; if(next>=0 && next