/* ================================================================ FACE SHAPE SUNGLASSES ANALYZER — FEMALE (profreetools.online) v2 | Light Pink Theme | v12 Scan Fix | All Buttons Working ================================================================ */ (function(){ 'use strict'; var SUNGLASSES_URL = { Oval: 'https://profreetools.online/wp-content/uploads/2026/05/OVAL-—-Cat-Eye-Rose-Gold-1.webp', Round: 'https://profreetools.online/wp-content/uploads/2026/05/ROUND-—-Colorful-Retro-pink-2-1.webp', Square: 'https://profreetools.online/wp-content/uploads/2026/05/SQUARE-—-Bold-Purple-3.webp', Heart: 'https://profreetools.online/wp-content/uploads/2026/05/HEART-—-Butterfly-Coral-4.webp', Diamond: 'https://profreetools.online/wp-content/uploads/2026/05/DIAMOND-—-Rimless-Rose-5.webp', Oblong: 'https://profreetools.online/wp-content/uploads/2026/05/OBLONG-—-Oversized-Tortoise-6-image.webp', Triangle:'https://profreetools.online/wp-content/uploads/2026/05/TRIANGLE-—-Geometric-Gold-7-image.webp' }; var SHAPE_ORDER = ['Oval','Round','Square','Heart','Diamond','Oblong','Triangle']; var SHAPE_SUB = {Oval:'Cat-Eye Rose Gold',Round:'Pink Retro Round',Square:'Bold Purple',Heart:'Butterfly Coral',Diamond:'Rimless Rose',Oblong:'Oversized Tortoise',Triangle:'Gold Flower'}; var FRAME_TUNE = {Oval:{wMul:2.55,yOff:-0.05},Round:{wMul:2.60,yOff:-0.04},Square:{wMul:2.58,yOff:-0.05},Heart:{wMul:2.55,yOff:-0.06},Diamond:{wMul:2.50,yOff:-0.05},Oblong:{wMul:2.70,yOff:-0.05},Triangle:{wMul:2.60,yOff:-0.06}}; var SCIENCE = { Oval: 'Your face shape is Oval — the most versatile profile. Our analyzer recommends elegant Cat-Eye frames with rose gold that celebrate your natural symmetry.', Round: 'Your face shape is Round with soft full cheeks. Our analyzer recommends Pink Round Retro frames that introduce definition and elegantly lengthen your face.', Square: 'Your face shape is Square with a strong jawline. Our analyzer recommends Bold Purple frames whose curves beautifully soften your angular features.', Heart: 'Your face shape is Heart with a wide forehead. Our analyzer recommends Butterfly Coral frames to add warmth and balance to your lower face.', Diamond: 'Your face shape is Diamond with prominent cheekbones. Our analyzer recommends Rimless Rose frames to highlight your brow line beautifully.', Oblong: 'Your face shape is Oblong with greater length than width. Our analyzer recommends Oversized Tortoise frames to add glamorous horizontal balance.', Triangle:'Your face shape is Triangle with a broader jaw. Our analyzer recommends Gold Flower geometric frames to draw attention beautifully upward.' }; var state = {naturalW:0,naturalH:0,displayedW:0,displayedH:0,photoOffsetX:0,photoOffsetY:0,landmarks:null,detectedShape:'Oval',activeShape:'Oval',shapeScores:{},modelsReady:false,offsetX:0,offsetY:0,scale:1}; var KO_CACHE = {}; var camStream = null; var initialized = false; function $(id){ return document.getElementById(id); } /* ── SMART LOADER — fetch blob to bypass CORS, then knockout ── */ function loadShapeAsset(shape){ return new Promise(function(resolve){ var url = SUNGLASSES_URL[shape]; if (!url){ resolve(''); return; } if (KO_CACHE[shape]){ resolve(KO_CACHE[shape]); return; } /* Use fetch+blob — bypasses CORS taint so canvas.getImageData works */ fetch(url + (url.indexOf('?')>-1?'&':'?') + 'ko=' + Date.now()) .then(function(r){ return r.blob(); }) .then(function(blob){ var blobUrl = URL.createObjectURL(blob); var img = new Image(); img.onload = function(){ try { var cv = document.createElement('canvas'); cv.width = img.naturalWidth; cv.height = img.naturalHeight; var ctx = cv.getContext('2d'); ctx.drawImage(img, 0, 0); var imageData = ctx.getImageData(0, 0, cv.width, cv.height); var bg = detectBG(imageData, cv.width, cv.height); /* Always knockout light backgrounds — uniform check removed */ knockoutBG(imageData, bg.r, bg.g, bg.b, 55, 40); ctx.putImageData(imageData, 0, 0); var dataUrl = cv.toDataURL('image/png'); KO_CACHE[shape] = dataUrl; URL.revokeObjectURL(blobUrl); resolve(dataUrl); } catch(e){ URL.revokeObjectURL(blobUrl); KO_CACHE[shape] = url; resolve(url); } }; img.onerror = function(){ URL.revokeObjectURL(blobUrl); KO_CACHE[shape]=url; resolve(url); }; img.src = blobUrl; }) .catch(function(){ /* fetch failed — fallback to direct img load */ var img2 = new Image(); img2.onload = function(){ try { var cv2 = document.createElement('canvas'); cv2.width = img2.naturalWidth; cv2.height = img2.naturalHeight; var ctx2 = cv2.getContext('2d'); ctx2.drawImage(img2, 0, 0); var id2 = ctx2.getImageData(0, 0, cv2.width, cv2.height); var bg2 = detectBG(id2, cv2.width, cv2.height); knockoutBG(id2, bg2.r, bg2.g, bg2.b, 55, 40); ctx2.putImageData(id2, 0, 0); var du2 = cv2.toDataURL('image/png'); KO_CACHE[shape] = du2; resolve(du2); } catch(e2){ KO_CACHE[shape]=url; resolve(url); } }; img2.onerror = function(){ KO_CACHE[shape]=url; resolve(url); }; img2.crossOrigin='anonymous'; img2.src = url; }); }); } function detectBG(d, W, H){ var data = d.data; var pts = [ [2,2],[W-3,2],[2,H-3],[W-3,H-3], [Math.floor(W/4),2],[Math.floor(W/2),2],[Math.floor(3*W/4),2], [Math.floor(W/4),H-3],[Math.floor(W/2),H-3],[Math.floor(3*W/4),H-3], [2,Math.floor(H/2)],[W-3,Math.floor(H/2)] ]; var rs=[],gs=[],bs=[]; for(var i=0;imaxDev)maxDev=dev; } /* Always return avg color — knockout decides based on brightness */ var brightness = (avgR*0.299 + avgG*0.587 + avgB*0.114); return {uniform: brightness > 160, r:avgR, g:avgG, b:avgB}; } function knockoutBG(d, tr, tg, tb, cutoff, feather){ /* FLOOD-FILL from edges only — preserves frame interior colors! Only removes background pixels CONNECTED to edges. Frame interior (even if light/similar color) stays intact. */ var data=d.data, W=d.width, H=d.height; var visited=new Uint8Array(W*H); var queue=[]; /* Seed: all edge pixels */ for(var x=0;x235 && r>220 && g>220 && b>220); return dist0) queue.push(idx-1); if(px0) queue.push(idx-W); if(py0 && data[(fi-1)*4+3]===0) transNb++; if(fx0 && data[(fi-W)*4+3]===0) transNb++; if(fy0){ data[fi*4+3]=Math.round(data[fi*4+3]*(1-transNb*0.18)); } } } /* ── PILLS ── */ function renderPills(){ var track = $('fssfShapeTrack'); if (!track) return; var html = ''; for (var i=0;i'+ ''+s+''+SHAPE_SUB[s]+''; } track.innerHTML = html; var pills = track.querySelectorAll('.fssf-shape-pill'); for (var j=0;j
'+s+'0.0%
'; } el.innerHTML = html; } function updateBars(scores){ var el = $('fssfBars'); if (!el) return; var rows = el.querySelectorAll('.fssf-bar-row'); for (var i=0;i0?(match/top):1)); var sym=Math.max(0.62,Math.min(0.96,0.70+match*0.30)); var prop=Math.max(0.60,Math.min(0.93,0.66+align*0.30)); var ov=Math.max(0.55,Math.min(0.94,sym*0.45+prop*0.35+match*0.20)); animateGauge('fssfG1','fssfG1V',sym); animateGauge('fssfG2','fssfG2V',prop); animateGauge('fssfG3','fssfG3V',ov); } /* ── FACE API ── */ var MODEL_URL = 'https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/weights'; var modelLoadPromise = null; function loadModels(){ if (modelLoadPromise) return modelLoadPromise; modelLoadPromise = new Promise(function(resolve){ if (typeof faceapi !== 'undefined'){ startML(resolve); return; } var s=document.createElement('script'); s.src='https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js'; s.async=true; s.setAttribute('data-fssf','1'); s.onload=function(){ startML(resolve); }; s.onerror=function(){ resolve(false); }; document.head.appendChild(s); }); return modelLoadPromise; } function startML(resolve){ try { Promise.all([faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL)]) .then(function(){ state.modelsReady=true; resolve(true); }).catch(function(){ resolve(false); }); } catch(e){ resolve(false); } } function detectFace(){ return new Promise(function(resolve){ var photo=$('fssfPhoto'); if (!state.modelsReady||typeof faceapi==='undefined'||!photo){ resolve(null); return; } try { faceapi.detectSingleFace(photo,new faceapi.TinyFaceDetectorOptions({inputSize:320,scoreThreshold:0.45})) .withFaceLandmarks().then(function(res){ if (!res){ resolve(null); return; } try { var lm=res.landmarks, le=centroid(lm.getLeftEye()), re=centroid(lm.getRightEye()); var dx=re.x-le.x, dy=re.y-le.y, ipd=Math.sqrt(dx*dx+dy*dy), ang=Math.atan2(dy,dx)*180/Math.PI; var nosePts=lm.getNose(), noseTip=nosePts[nosePts.length-1]; var emX=(le.x+re.x)/2, yaw=Math.max(-1,Math.min(1,(noseTip.x-emX)/(ipd*0.6))); resolve({leftEye:le,rightEye:re,ipdPx:ipd,angleDeg:ang,centerX:emX,centerY:(le.y+re.y)/2,yawNorm:yaw,allLandmarks:lm.positions,faceBox:res.detection.box}); } catch(e){ resolve(null); } }).catch(function(){ resolve(null); }); } catch(e){ resolve(null); } }); } function centroid(pts){ var x=0,y=0; for(var i=0;i1.02?0.15:0),Round:1-Math.abs(lw-1.05)*1.8+(ja>150?0.20:0),Square:1-Math.abs(lw-1.10)*1.6+(ja<135?0.30:0)+(Math.abs(fj-1)<0.10?0.15:0),Heart:1-Math.abs(lw-1.30)*1.5+(fj>1.12?0.30:0),Diamond:1-Math.abs(lw-1.35)*1.5+(cf>1.15?0.25:0),Oblong:1-Math.abs(lw-1.60)*1.4+(lw>1.50?0.30:0),Triangle:1-Math.abs(lw-1.30)*1.5+(fj<0.92?0.30:0)}; var sum=0; for(var k in raw){if(raw[k]<0.02)raw[k]=0.02;sum+=raw[k];} var out={}; for(var k2 in raw){out[k2]=raw[k2]/sum;} return out; } catch(e){ return {Oval:0.62,Round:0.10,Square:0.06,Heart:0.10,Diamond:0.06,Oblong:0.04,Triangle:0.02}; } } function pickTop(scores){ var best='Oval',val=-1; for(var k in scores){if(scores[k]>val){val=scores[k];best=k;}} return best; } /* ── MEASURE + PLACE ── */ function measureDisplay(){ var box=$('fssfImageBox'); if (!state.naturalW||!state.naturalH||!box) return; var r=box.getBoundingClientRect(), bw=r.width, bh=r.height; if (!bw||!bh) return; var ratio=state.naturalW/state.naturalH, dw, dh; if (bw/bh>ratio){dh=bh;dw=dh*ratio;}else{dw=bw;dh=dw/ratio;} state.displayedW=dw;state.displayedH=dh;state.photoOffsetX=(bw-dw)/2;state.photoOffsetY=(bh-dh)/2; } function placeGlasses(){ var g=$('fssfGlassesImg'); if (!g||!state.naturalW||!g.src||!state.landmarks) return; var lm=state.landmarks, sc=state.displayedW/state.naturalW; var cx=state.photoOffsetX+lm.centerX*sc, cy=state.photoOffsetY+lm.centerY*sc; var ipd=lm.ipdPx*sc, tune=FRAME_TUNE[state.activeShape]||{wMul:2.55,yOff:-0.05}; var fw=ipd*tune.wMul*state.scale, px=cx+state.offsetX, py=cy+ipd*tune.yOff+state.offsetY; var yaw=lm.yawNorm||0, sx=Math.max(0.78,1-Math.abs(yaw)*0.22); g.style.width=fw.toFixed(1)+'px'; g.style.height='auto'; g.style.left='0'; g.style.top='0'; g.style.transform='translate3d('+(px-fw/2).toFixed(1)+'px,'+(py).toFixed(1)+'px,0) translateY(-50%) rotate3d(0,0,1,'+lm.angleDeg.toFixed(2)+'deg) rotate3d(0,1,0,'+(yaw*16).toFixed(2)+'deg) scaleX('+sx.toFixed(3)+')'; g.classList.add('ready'); } function showScan(on){ var s=$('fssfScanOverlay'); if(!s)return; if(on)s.classList.add('show'); else s.classList.remove('show'); } /* ── v12 SCAN FIX — 1.5s GUARANTEED STOP ── */ function onPhotoLoaded(){ var photo=$('fssfPhoto'); if (!photo) return; try{state.naturalW=photo.naturalWidth||photo.width;state.naturalH=photo.naturalHeight||photo.height;}catch(_){} requestAnimationFrame(function(){ measureDisplay(); showScan(true); /* GUARANTEED STOP — completely independent of loadModels() */ setTimeout(function(){ showScan(false); var lm=fallbackLandmarks(); state.landmarks=lm; state.shapeScores=scoreShapes(lm); state.detectedShape=pickTop(state.shapeScores); var nm=$('fssfShapeName'); if(nm) nm.textContent=state.detectedShape; updateBars(state.shapeScores); setActiveShape(state.detectedShape,false); computeMetrics(state.detectedShape); placeGlasses(); if(window.innerWidth<980){var h=$('fssfDragHint');if(h){h.classList.add('show');setTimeout(function(){h.classList.remove('show');},3000);}} /* Background AI — silently updates if fast enough */ loadModels().then(function(ok){ if(!ok) return; detectFace().then(function(lm2){ if(!lm2) return; state.landmarks=lm2;state.shapeScores=scoreShapes(lm2);state.detectedShape=pickTop(state.shapeScores); var nm2=$('fssfShapeName');if(nm2)nm2.textContent=state.detectedShape; updateBars(state.shapeScores);setActiveShape(state.detectedShape,false);computeMetrics(state.detectedShape);placeGlasses(); }); }); }, 1500); }); } function loadPhotoFromSrc(src){ var photo=$('fssfPhoto'); if (!photo) return; photo.onload=function(){ var hero=$('fssfHeroSplit'),stage=$('fssfStage'); if(hero)hero.classList.add('hide'); if(stage)stage.classList.add('show'); state.offsetX=0;state.offsetY=0;state.scale=1; requestAnimationFrame(function(){requestAnimationFrame(onPhotoLoaded);}); }; photo.onerror=function(){alert('Could not load image. Please try another photo.');}; photo.src=src; } /* ── UPLOAD — FIXED ── */ function bindUpload(){ var upBtn=$('fssfUpBtn'), fileInput=$('fssfFile'); if (!upBtn||!fileInput){ console.warn('[FSSF] Upload buttons not found'); return; } /* Click handler */ upBtn.addEventListener('click', function(e){ e.preventDefault(); e.stopPropagation(); fileInput.click(); }, false); /* Touch handler for mobile */ upBtn.addEventListener('touchend', function(e){ e.preventDefault(); fileInput.click(); }, false); /* File change */ fileInput.addEventListener('change', function(e){ var f=e.target.files&&e.target.files[0]; if (!f) return; if (!/^image\//.test(f.type)){ alert('Please choose an image file.'); return; } var reader=new FileReader(); reader.onload=function(ev){ loadPhotoFromSrc(ev.target.result); }; reader.readAsDataURL(f); fileInput.value=''; }, false); } /* ── CAMERA ── */ function bindCam(){ var cb=$('fssfCamBtn'),cc=$('fssfCamCancel'),cs=$('fssfCamShot'),cm=$('fssfCamModal'); if (cb){ cb.addEventListener('click',function(e){e.preventDefault();openCam();},false); cb.addEventListener('touchend',function(e){e.preventDefault();openCam();},false); } if (cc) cc.addEventListener('click',closeCam,false); if (cs) cs.addEventListener('click',captureCam,false); if (cm) cm.addEventListener('click',function(e){if(e.target===cm)closeCam();},false); } function openCam(){ var modal=$('fssfCamModal'),st=$('fssfCamStatus'),shot=$('fssfCamShot'),ovl=$('fssfCamOvl'),vid=$('fssfCamVideo'); if (!modal) return; modal.classList.add('show'); if(st){st.textContent='Starting camera...';st.className='fssf-cam-status';} if(shot){shot.disabled=true;shot.classList.remove('ready');} if(!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia){if(st){st.textContent='Camera not supported on this device.';st.classList.add('bad');}return;} navigator.mediaDevices.getUserMedia({video:{facingMode:'user',width:{ideal:1280},height:{ideal:1280}},audio:false}) .then(function(stream){ camStream=stream; if(vid)vid.srcObject=stream; if(st){st.textContent='Align your face in the rose oval';st.className='fssf-cam-status';} setTimeout(function(){ if(st){st.textContent='Perfect! Tap Capture now';st.className='fssf-cam-status good';} if(ovl)ovl.classList.add('good'); if(shot){shot.disabled=false;shot.classList.add('ready');} },1500); }).catch(function(){if(st){st.textContent='Camera access denied. Please allow camera.';st.className='fssf-cam-status bad';}}); } function closeCam(){ var modal=$('fssfCamModal'),vid=$('fssfCamVideo'),ovl=$('fssfCamOvl'); if(modal)modal.classList.remove('show'); if(camStream){try{camStream.getTracks().forEach(function(t){t.stop();});}catch(_){}camStream=null;} if(vid)vid.srcObject=null; if(ovl)ovl.classList.remove('good'); } function captureCam(){ var vid=$('fssfCamVideo'); if(!vid||!vid.videoWidth)return; var cv=document.createElement('canvas'); cv.width=vid.videoWidth;cv.height=vid.videoHeight; var ctx=cv.getContext('2d'); ctx.translate(vid.videoWidth,0);ctx.scale(-1,1); ctx.drawImage(vid,0,0,vid.videoWidth,vid.videoHeight); closeCam(); loadPhotoFromSrc(cv.toDataURL('image/jpeg',0.92)); } /* ── ADJUST ── */ function bindAdjust(){ var zone=$('fssfAdjust'); if (!zone) return; var btns=zone.querySelectorAll('.fssf-adj-btn'); function doAdjust(act){ var s=12; if(state.landmarks&&state.displayedW&&state.naturalW) s=Math.max(6,state.landmarks.ipdPx*(state.displayedW/state.naturalW)*0.06); if(act==='up')state.offsetY-=s; else if(act==='down')state.offsetY+=s; else if(act==='left')state.offsetX-=s; else if(act==='right')state.offsetX+=s; else if(act==='bigger')state.scale=Math.min(2.0,state.scale+0.06); else if(act==='smaller')state.scale=Math.max(0.1,state.scale-0.06); placeGlasses(); } for(var i=0;i0)state.scale=Math.max(0.1,Math.min(2.5,state.scale*(nd/t.lastDist)));t.lastDist=nd;placeGlasses();e.preventDefault();} },{passive:false}); box.addEventListener('touchend',function(e){ if(e.touches.length===0){t.dragging=false;t.pinching=false;} else if(e.touches.length===1){t.pinching=false;t.dragging=true;t.lastX=e.touches[0].clientX;t.lastY=e.touches[0].clientY;t.lastDist=0;} },{passive:true}); box.addEventListener('touchcancel',function(){t.dragging=false;t.pinching=false;},{passive:true}); } /* ── REDO ── */ function bindRedo(){ var btn=$('fssfRedo'); if(!btn)return; btn.addEventListener('click',function(){ var hero=$('fssfHeroSplit'),stage=$('fssfStage'),photo=$('fssfPhoto'),g=$('fssfGlassesImg'); if(stage)stage.classList.remove('show'); if(hero)hero.classList.remove('hide'); if(photo)photo.src=''; if(g){g.src='';g.classList.remove('ready');} state.landmarks=null;state.shapeScores={};state.offsetX=0;state.offsetY=0;state.scale=1; },false); } /* ── DOWNLOAD ── */ function bindDownload(){ var btn=$('fssfDownloadBtn'); if(!btn)return; btn.addEventListener('click',function(){ var photo=$('fssfPhoto'),gi=$('fssfGlassesImg'),box=$('fssfImageBox'); if(!photo||!photo.src||photo.src===window.location.href){alert('Please upload a photo first!');return;} btn.textContent='Preparing...';btn.style.opacity='0.7'; var br=box.getBoundingClientRect(),dpr=window.devicePixelRatio||1; var cv=document.createElement('canvas'); cv.width=br.width*dpr;cv.height=br.height*dpr; var ctx=cv.getContext('2d');ctx.scale(dpr,dpr); ctx.fillStyle='#fff5f7';ctx.fillRect(0,0,br.width,br.height); try{ctx.drawImage(photo,state.photoOffsetX,state.photoOffsetY,state.displayedW,state.displayedH);}catch(e){} /* CRITICAL FIX: Use KO_CACHE (knockout-processed) image, NOT raw URL! Raw URL has white background — knockout-processed is transparent. */ var processedSrc = KO_CACHE[state.activeShape] || SUNGLASSES_URL[state.activeShape]; if(processedSrc&&gi&&gi.classList.contains('ready')){ var tmp=new Image(); tmp.onload=function(){ try{var gr=gi.getBoundingClientRect();ctx.drawImage(tmp,gr.left-br.left,gr.top-br.top,gr.width,gr.height);}catch(e){} saveCanvas(ctx,cv,br,btn); }; tmp.onerror=function(){saveCanvas(ctx,cv,br,btn);}; /* data: URLs (knockout-processed) load without CORS issues */ if(processedSrc.indexOf('data:')!==0){ tmp.crossOrigin='anonymous'; } tmp.src=processedSrc; } else saveCanvas(ctx,cv,br,btn); },false); } function saveCanvas(ctx,cv,br,btn){ ctx.font='bold 12px Arial,sans-serif';ctx.fillStyle='rgba(244,63,94,0.85)';ctx.textAlign='right';ctx.fillText('profreetools.online',br.width-12,br.height-12); ctx.font='bold 13px Arial,sans-serif';ctx.fillStyle='rgba(190,18,57,0.95)';ctx.textAlign='left';ctx.fillText('Face: '+state.detectedShape,12,26); function showDone(){ btn.style.opacity=''; btn.innerHTML='✓ Saved!'; btn.style.background='linear-gradient(135deg,#10b981,#059669)'; setTimeout(function(){btn.innerHTML='⬇ Download My Look';btn.style.background='';},3000); } try{ /* toBlob saves directly to gallery on iOS/Android */ if(cv.toBlob){ cv.toBlob(function(blob){ var bUrl=URL.createObjectURL(blob); var dl=document.createElement('a'); dl.href=bUrl; dl.download='my-look-profreetools.jpg'; dl.style.display='none'; document.body.appendChild(dl);dl.click(); setTimeout(function(){document.body.removeChild(dl);URL.revokeObjectURL(bUrl);},400); showDone(); },'image/jpeg',0.93); } else { var dl=document.createElement('a'); dl.setAttribute('href',cv.toDataURL('image/jpeg',0.93)); dl.setAttribute('download','my-look-profreetools.jpg'); dl.style.display='none';document.body.appendChild(dl);dl.click(); setTimeout(function(){document.body.removeChild(dl);},200); showDone(); } }catch(e){btn.style.opacity='';btn.innerHTML='📷 Screenshot to Save';setTimeout(function(){btn.innerHTML='⬇ Download My Look';},3000);} } /* ── RESIZE ── */ function bindResize(){ var t=null; window.addEventListener('resize',function(){ var stage=$('fssfStage');if(!stage||!stage.classList.contains('show'))return; clearTimeout(t);t=setTimeout(function(){measureDisplay();placeGlasses();},120); },false); } /* ── INIT ── */ function init(){ if (initialized) return; if (!$('fssfShapeTrack')||!$('fssfUpBtn')||!$('fssfCamBtn')) return; initialized = true; renderPills(); buildBars(); bindUpload(); bindCam(); bindRedo(); bindDownload(); bindAdjust(); bindTouchGestures(); bindResize(); console.log('[FSSF] v2 initialized — all buttons active.'); } /* Multiple init attempts for WordPress late DOM injection */ if (document.readyState==='loading'){ document.addEventListener('DOMContentLoaded',init,false); } else { init(); } var _tries=0, _iv=setInterval(function(){ _tries++; if (initialized||_tries>40){ clearInterval(_iv); return; } init(); },150); })();