@swan-admin/swan-web-component 1.0.72 → 1.0.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Header-Bf7ClSfT.js +32 -0
- package/dist/Header-Bf7ClSfT.js.map +1 -0
- package/dist/Header-C65iSMsF.js +32 -0
- package/dist/Header-C65iSMsF.js.map +1 -0
- package/dist/LoadingScreen-C9vDF8N6.js +12 -0
- package/dist/LoadingScreen-C9vDF8N6.js.map +1 -0
- package/dist/LoadingScreen-CDhtnDGT.js +12 -0
- package/dist/LoadingScreen-CDhtnDGT.js.map +1 -0
- package/dist/LoadingScreen-DFKUWqQS.js +12 -0
- package/dist/LoadingScreen-DFKUWqQS.js.map +1 -0
- package/dist/LoadingScreen-DGyU-r8n.js +12 -0
- package/dist/LoadingScreen-DGyU-r8n.js.map +1 -0
- package/dist/bodyScan.d.ts +2 -2
- package/dist/bodyScan.js +2 -2
- package/dist/bodyScan.js.map +1 -1
- package/dist/bodyScan.mjs +2 -2
- package/dist/bodyScan.mjs.map +1 -1
- package/dist/createSvgIcon-CSumJvXf.js +2 -0
- package/dist/createSvgIcon-CSumJvXf.js.map +1 -0
- package/dist/createSvgIcon-KYNE9Cmr.js +2 -0
- package/dist/createSvgIcon-KYNE9Cmr.js.map +1 -0
- package/dist/faceScan.d.ts +2 -2
- package/dist/faceScan.js +1 -1
- package/dist/faceScan.js.map +1 -1
- package/dist/faceScan.mjs +1 -1
- package/dist/faceScan.mjs.map +1 -1
- package/dist/index-B23Mlqqj.js +5068 -0
- package/dist/index-B23Mlqqj.js.map +1 -0
- package/dist/index-CenJi_ia.js +4686 -0
- package/dist/index-CenJi_ia.js.map +1 -0
- package/dist/index-D0NFHRX9.js +5068 -0
- package/dist/index-D0NFHRX9.js.map +1 -0
- package/dist/index-slG-fgu8.js +4684 -0
- package/dist/index-slG-fgu8.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/interfaces-C9CQpzhP.d.ts +142 -0
- package/package.json +1 -1
package/dist/faceScan.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{jsxs as e,jsx as t,Fragment as a}from"react/jsx-runtime";import{useRef as n,useState as o,useMemo as r,useCallback as c,useEffect as l}from"react";import{ab as s,av as i,aw as d,a6 as u,ax as h,ay as m,S as f,az as g,aA as p,aB as y,aC as S,a7 as b,aD as w,aE as v,aF as x,aG as F,aH as N}from"./Header-CGdVhD4h.js";import{A as _}from"./arrow-right-DXQbS3wL.js";import{s as I,L as k,D as M,a as D}from"./LoadingScreen-CoRs0lHa.js";import*as z from"@tensorflow-models/pose-detection";import{setBackend as C,ready as P}from"@tensorflow/tfjs";import"react-dom";function T({stage:a,videoLoading:n,setVideoLoading:o,videoError:r,setVideoError:c,children:l,gender:f,config:g}){return-1===a?e("div",{className:"text-center p-[16px] w-full h-full",style:{background:g?.style?.base?.backgroundColor},children:[e("div",{className:"w-full",children:[t(s,{noTitle:!0,resolvedConfig:g}),e("h2",{style:{fontFamily:g?.style?.heading?.headingFontFamily||"SeriouslyNostalgic Fn",fontSize:g?.style?.heading?.headingFontSize||"32px",color:g?.style?.heading?.headingColor||"#000",fontWeight:g?.style?.heading?.headingFontWeight||"normal"},children:["Make sure your face is fully visible"," "]}),n&&t("div",{className:"mb-4 flex items-center justify-center",style:{fontFamily:g?.style?.subheading?.subheadingFontFamily||"'Inter', sans-serif",fontSize:g?.style?.subheading?.subheadingFontSize||"14px",color:g?.style?.subheading?.subheadingColor||"#4b5563",fontWeight:g?.style?.subheading?.subheadingFontWeight||"normal"},children:"Loading video..."}),r&&t("div",{className:"mb-4 text-red-600",style:{fontFamily:g?.style?.subheading?.subheadingFontFamily||"'Inter', sans-serif",fontSize:g?.style?.subheading?.subheadingFontSize||"14px",color:"#ff0000",fontWeight:g?.style?.subheading?.subheadingFontWeight||"normal"},children:"Video failed to load. Please refresh the page or check your connection."}),!r&&t("div",{className:" w-[100px] mx-auto",children:t("video",{src:f===u.Male?i:d,autoPlay:!0,loop:!0,controls:!1,muted:!0,playsInline:!0,className:"h-full w-full object-contain border-none",onCanPlay:()=>o(!1),onLoadStart:()=>o(!0),onError:()=>c(!0),"aria-label":"Instructional video: Please remove your glasses before starting the scan."})})]}),l]}):t("div",{className:"text-center p-[16px] w-full h-full",style:{background:g?.style?.base?.backgroundColor},children:e("div",{className:"w-full",children:[t(s,{noTitle:!0,resolvedConfig:g}),t("h3",{className:"mb-[0.5rem]",style:{fontFamily:g?.style?.heading?.headingFontFamily||"SeriouslyNostalgic Fn",fontSize:g?.style?.heading?.headingFontSize||"32px",color:g?.style?.heading?.headingColor||"#000",fontWeight:g?.style?.heading?.headingFontWeight||"normal"},children:h[a]}),e("div",{className:"max-w-[400px] justify-center gap-1 mx-auto flex items-center",children:[t("div",{className:` w-[120px] overflow-hidden ${0===a?"opacity-100":"opacity-0 hidden"} `,children:t("video",{className:"h-full w-full object-contain border-none",muted:!0,loop:!0,autoPlay:!0,playsInline:!0,children:t("source",{src:f===u.Male?m.male.forward:m.female.forward,type:"video/mp4"})})}),t("div",{className:` w-[120px] overflow-hidden ${1===a?"opacity-100":"opacity-0 hidden"} `,children:t("video",{className:"h-full w-full object-contain border-none",muted:!0,loop:!0,autoPlay:!0,playsInline:!0,children:t("source",{src:f===u.Male?m.male.left:m.female.left,type:"video/mp4"})})}),t("div",{className:` w-[120px] overflow-hidden ${2===a?"opacity-100":"opacity-0 hidden"} `,children:t("video",{className:"h-full w-full object-contain border-none",muted:!0,loop:!0,autoPlay:!0,playsInline:!0,children:t("source",{src:f===u.Male?m.male.right:m.female.right,type:"video/mp4"})})}),t("div",{className:` w-[120px] overflow-hidden ${3===a?"opacity-100":"opacity-0 hidden"} `,children:t("video",{className:"h-full w-full object-contain border-none",muted:!0,loop:!0,autoPlay:!0,playsInline:!0,children:t("source",{src:f===u.Male?m.male.smile:m.female.smile,type:"video/mp4"})})})]})]})})}function $({resetScanState:a,loading:n,config:o}){return t("div",{className:"fixed top-[0] left-[0] z-[999] flex justify-center items-center w-full h-full",style:{background:o?.style?.base?.backgroundColor},children:e("div",{className:"flex flex-col w-full items-center p-[1rem] rounded-lg ",children:[t(s,{noTitle:!0,resolvedConfig:o}),e("div",{className:"flex flex-col items-center w-full",children:[t("h2",{className:"text-xl font-semibold text-gray-800 ",style:{fontFamily:o?.style?.heading?.headingFontFamily||"SeriouslyNostalgic Fn",fontSize:o?.style?.heading?.headingFontSize||"32px",color:o?.style?.heading?.headingColor||"#000",fontWeight:o?.style?.heading?.headingFontWeight||"normal"},children:"Your Scan Failed"}),t("p",{className:"mb-[1.5rem]",style:{fontFamily:o?.style?.subheading?.subheadingFontFamily||"'Inter', sans-serif",fontSize:o?.style?.subheading?.subheadingFontSize||"14px",color:o?.style?.subheading?.subheadingColor||"#4b5563",fontWeight:o?.style?.subheading?.subheadingFontWeight||"normal"},children:"Please click below to reset your scan."}),t(f,{disabled:n,className:"w-full h-[45px] shadow-[0px_1px_2px_0px_#00000040] text-[16px]",buttonText:"Reset Scan",postfixIcon:t(_,{}),buttonFunc:()=>a(),resolvedConfig:o})]})]})})}let j=null;function E({faceScanId:e,onValidPose:t,onScanComplete:a,onModelReady:s,onStartRecording:i,shopDomain:d}){const u=n(null),[h,m]=o(0),[f,S]=o(0),[b,w]=o(!1),[v,x]=o(!1),F=n(null),N=n(h),_=n(f),k=n([]),M=n(""),D=n(0),T=n(!1),$=n(0),E=/iPad|iPhone|iPod/.test(navigator.userAgent)||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1,R=r(()=>["Face front","Turn head fully left","Turn head fully right","Smile facing front"],[]),L=r(()=>function(e,t){let a;return(...n)=>{a&&clearTimeout(a),a=setTimeout(()=>e(...n),t)}}(e=>{g.capture(`${d}/face_scan_pose_detection`,e)},4e3),[d]),O=c((t,a,n,o,r,c)=>{const l=Date.now();if(!(l-$.current<4e3)){$.current=l;try{const l=t[0]||[0,0,0],s=t[2]||[0,0,0],i=t[5]||[0,0,0],d=t[9]||[0,0,0],u=t[10]||[0,0,0],h=t.map(e=>e[2]).filter(e=>e>0),m=h.length>0?h.reduce((e,t)=>e+t,0)/h.length:0,f=a.map(e=>e[2]).filter(e=>e>0),g=f.length>0?f.reduce((e,t)=>e+t,0)/f.length:0,p=Math.abs(s[0]-i[0]),y=l[0]-i[0],S=s[0]-l[0];let b="frontal";1===n?b="left":2===n?b="right":3===n&&(b="frontal_smile");const w={faceScanId:e,stage:n,timestamp:(new Date).toISOString(),data:JSON.stringify({headValid:o,bodyInvalid:r,consecutiveValid:c,avgFaceScore:parseFloat(m.toFixed(3)),avgBodyScore:parseFloat(g.toFixed(3)),faceKeypointsDetected:h.length,bodyKeypointsDetected:f.length,eyeDistance:parseFloat(p.toFixed(2)),noseToRightRatio:parseFloat((y/p).toFixed(3)),noseToLeftRatio:parseFloat((S/p).toFixed(3)),noseScore:parseFloat(l[2].toFixed(3)),leftEyeScore:parseFloat(s[2].toFixed(3)),rightEyeScore:parseFloat(i[2].toFixed(3)),leftMouthScore:parseFloat(d[2].toFixed(3)),rightMouthScore:parseFloat(u[2].toFixed(3)),expectedPosition:b})};L(w)}catch(e){console.warn("Failed to track pose detection:",e)}}},[e,L]),W=()=>{g.capture(`${d}/face_scan_detection_started`,{faceScanId:e,stage:N.current,timestamp:(new Date).toISOString(),stageName:R[N.current]}),setTimeout(()=>{x(!0)},1500)},V=async()=>{try{console.log("Initializing TensorFlow..."),await C("webgl"),await P(),console.log("TensorFlow initialized, loading models...");const e=await(async()=>{if(j)return j;const e=await z.createDetector(z.SupportedModels.BlazePose,{runtime:"tfjs",modelType:"full"}),t=document.createElement("canvas");t.width=y.width,t.height=y.height;const a=t.getContext("2d");a&&(a.fillStyle="black",a.fillRect(0,0,t.width,t.height));try{await e.estimatePoses(t)}catch(e){console.log(e,"at time of preload")}return j=e,e})();console.log(e,"detector"),u.current=e,console.log("Pose detection model loaded successfully"),w(!0),s?.()}catch(e){console.error("Error initializing face scan:",e)}};return l(()=>(V(),()=>{(async()=>{try{w(!1),u.current&&(await u.current.dispose(),u.current=null)}catch(e){console.error("Error disposing resources:",e)}finally{m(0),S(0),N.current=0,_.current=0,k.current=[]}})()}),[]),l(()=>{N.current=h},[h]),l(()=>{_.current=f},[f]),{faceScanDetector:async(n,o)=>{if(u.current&&n.current&&b&&v)try{const r=await u.current.estimatePoses(n.current);if(r.length>0){const n=[],c=[];if(r[0].keypoints.forEach((e,t)=>{const a=e.score??0,o=[e.x??-1,e.y??-1,a];t<=10?n.push(o):c.push(o)}),o?.current){const e=o.current.getContext("2d"),{width:t,height:a}=o.current;if(e){e.clearRect(0,0,t,a);const n=Math.min(_.current/2,1),o=.35*a;e.beginPath(),e.strokeStyle="#00ff00",e.lineWidth=6,e.arc(t/2,a/2,o+10,-Math.PI/2,-Math.PI/2+2*n*Math.PI),e.stroke()}}const l=((e,t)=>{if(t[0][2]<.2||t[2][2]<.2||t[5][2]<.2)return!1;const a=.5*(t[10][1]+t[9][1])-.5*(t[5][1]+t[2][1]),n=(.5*(t[10][1]+t[9][1])-t[0][1])/a,o=n>.7||n<.3,r=Math.abs(t[2][1]-t[5][1])<.5*a&&Math.abs(t[9][1]-t[10][1])<.5*a,c=t[2][0]-t[5][0],l=t[0][0]-t[5][0],s=l>.4*c&&l<.6*c,i=l>.85*c,d=t[2][0]-t[0][0]>.85*c;switch(e){case 0:case 3:return!o&&r&&s;case 1:return!o&&r&&i;case 2:return!o&&r&&d;default:return!1}})(N.current,n),s=c.slice(2).filter(e=>e[2]>.7).length<=2;k.current.push(l&&s),k.current.length>10&&k.current.shift();if(k.current.filter(Boolean).length>=2){const e=_.current+1;S(e)}else S(Math.max(0,_.current-1));if(_.current>=2){t?.();const n=N.current+1;S(0),k.current=[],m(n),x(!1),(async()=>{if(await I.playAudio(`${p}face-scan-vos/Sound-effect.mp3`),g.capture(`${d}/face_scan_detection_stage_completed`,{faceScanId:e,completedStage:N.current,nextStage:n,timestamp:(new Date).toISOString(),totalStages:4,stageName:R[N.current]}),1===n&&i&&i(),n>=4)a?.();else{let e=null;switch(n){case 1:e="Left.mp3";break;case 2:e="Right.mp3";break;case 3:e="Smile.mp3";break;default:e=null}e&&await I.playAudio(`${p}face-scan-vos/${e}`),W()}})()}O(n,c,N.current,l,s,_.current)}}catch(e){console.error("Pose detection error:",e)}},scanStage:h,setScanStage:m,consecutiveValid:f,isModelLoaded:b,resetScan:()=>{g.capture(`${d}/face_scan_reset`,{faceScanId:e,stage:N.current,timestamp:(new Date).toISOString(),stageName:R[N.current],consecutiveValid:_.current}),m(0),S(0),x(!1),N.current=0,_.current=0,k.current=[],M.current="",D.current=0,T.current=!1,F.current&&(speechSynthesis.cancel(),F.current=null)},enableVoiceCommands:()=>{if(T.current=!0,E&&"speechSynthesis"in window){const e=new SpeechSynthesisUtterance("");e.volume=0,speechSynthesis.speak(e)}},startScanSequence:W,isScanningActive:v}}const R=({userDetails:s,onComplete:i,onScanError:d,onRetry:u,config:h})=>{const m=n(null),z=n(null),C=n(null),P=n([]),j=n(null),[R,L]=o(!1),[O,W]=o(!1),[V,A]=o(!1),[U,B]=o(!0),[J,q]=o(!1),[H,K]=o(!1),[X,G]=o(!1),[Y,Q]=o(S()),Z=b(h),{email:ee,gender:te,deviceFocalLength:ae,shopDomain:ne,callbackUrl:oe}=s;console.log(P.current,"recoridng");const re=r(()=>w.filter(e=>MediaRecorder.isTypeSupported(e)),[]),ce=c(()=>{console.log("Stopping recording..."),C.current&&"recording"===C.current.state&&C.current.stop(),C.current=null},[]),le=async()=>{if(L(!0),console.log("upload final video"),P.current){if(0===P.current.length)return console.error("No video data recorded"),G(!0),void L(!1);const e=new File(P.current,`${Y}.webm`,{type:"video/webm"}),t=e.size,a=(t/1048576).toFixed(2),n=Math.round(t/1e4),o={video_size_mb:parseFloat(a),video_size_bytes:t,blob_count:P.current.length,estimated_duration_seconds:n},r=[{gender:te},{face_scan_id:Y},{focal_length:`${ae}`},{customer_store_url:ne},{scan_type:"face_scan"},{callback_url:oe}];console.log(r,"metadata"),console.log(o),F({eventName:`${ne}/face_scan_meta_data`,faceScanId:Y,email:ee,data:JSON.stringify({metaData:r,videoData:o})});const c=Date.now();F({eventName:`${ne}/face_scan_upload_start`,faceScanId:Y,email:ee,startTime:c});try{const t=await D.fileUpload.faceScanFileUploader({file:e,objectKey:Y,email:ee,arrayMetaData:r,contentType:e.type}),a=Date.now();F({eventName:`${ne}/face_scan_upload_complete`,faceScanId:Y,email:ee,completionTime:a,uploadDuration:a-c}),console.log("✅ Upload successful",t),D.measurement.handlFaceScaneSocket({faceScanId:Y,onOpen:()=>{N({eventName:`${ne}/webSocket`,faceScanID:Y,connection:"open",type:"faceScan_recommendation",email:ee}),console.log("websocket connect open")},onError:e=>{N({eventName:`${ne}/webSocket`,faceScanID:Y,connection:"error",type:"faceScan_recommendation",email:ee}),ye(e)},onSuccess:e=>{N({eventName:`${ne}/webSocket`,faceScanID:Y,connection:"success",type:"faceScan_recommendation",email:ee}),Se(e)},onClose:()=>{console.log("websocket connect close"),N({eventName:`${ne}/webSocket`,faceScanID:Y,connection:"close",type:"faceScan_recommendation",email:ee})}})}catch(e){ye(e)}}},se=c(()=>{console.log("Starting recording for stages 1-3...");const e=m.current?.srcObject;if(!e)return;const t=e.getVideoTracks()[0].getSettings(),{width:a=y.width,height:n=y.height}=t,o=document.createElement("canvas"),r=o.getContext("2d");a>n?(o.width=n,o.height=a):(o.width=a,o.height=n);const c=document.createElement("video");c.srcObject=e,c.muted=!0,c.playsInline=!0,c.play();const l=()=>{a>n?(r?.save(),r?.translate(o.width,0),r?.rotate(Math.PI/2),r?.drawImage(c,0,0,n,a),r?.restore()):r?.drawImage(c,0,0,a,n),requestAnimationFrame(l)};l();const s=o.captureStream(30),i=re.length>0?{mimeType:re[0]}:{};let d;try{d=new MediaRecorder(s,i)}catch(e){return console.error("MediaRecorder init failed:",e),G(!0),void L(!1)}P.current=[],d.ondataavailable=e=>{e?.data&&e.data.size>0&&P.current&&P.current.push(e.data)},d.onstop=()=>{console.log("Recording stopped, total blobs:",P?.current?.length),le()},d.start(100),C.current=d,console.log("Recording started successfully (portrait normalized)")},[re,le]),{faceScanDetector:ie,scanStage:de,setScanStage:ue,resetScan:he,isModelLoaded:me,startScanSequence:fe}=E({faceScanId:Y,shopDomain:ne,onScanComplete:()=>{ce()},onModelReady:()=>{W(!0)},onStartRecording:()=>{console.log("Stage 0 completed - starting recording for stages 1-3"),se()}}),ge=c(()=>{me&&(ue(0),I.playAudio(`${p}face-scan-vos/Face-forward.mp3`),setTimeout(()=>{A(!0),setTimeout(()=>{fe()},200)},200))},[ue,A,fe,me]),pe=c(()=>{A(!1),B(!0),ce(),he(),u?.(),P.current=[],C.current&&(C.current=null),G(!1),ue(-1),Q(S()),m.current&&j.current&&(m.current.srcObject=j.current,console.log("Camera stream restored after reset"))},[ue,A,B,ce,he,Q]),ye=e=>{console.log(e,"ws error"),d?.(e),G(!0),L(!1),B(!1),F({eventName:`${ne}/faceScan`,faceScanId:Y,status:"failed",email:ee,data:JSON.stringify(e)})},Se=e=>{console.log(e,"ws success"),e&&"intermediate"===e?.resultType&&F({eventName:`${ne}/faceScan_success/intermediate`,faceScanId:Y,status:"success",email:ee,data:JSON.stringify(e)}),i?.(e)};l(()=>(navigator.mediaDevices.getUserMedia&&navigator.mediaDevices.getUserMedia({video:y}).then(e=>{j.current=e,m.current&&(m.current.srcObject=e,console.log("Webcam initialized"))}).catch(e=>console.error("Error accessing webcam:",e)),()=>{j.current&&j.current.getTracks().forEach(e=>e.stop())}),[]),l(()=>{if(!O||!V)return;const e=setInterval(()=>{ie(m,z)},500);return()=>clearInterval(e)},[ie,O,V]);return console.log("Model ready:",O,"Has error:",X,"Is scanning:",V,"showLoader",R),l(()=>{ue(-1),g.init(v,{api_host:x}),g.capture("$pageview")},[]),e(a,{children:[t("audio",{id:"audioElement",crossOrigin:"anonymous",preload:"auto",style:{position:"absolute",zIndex:-99999},src:""}),R&&!X&&t("div",{className:"fixed z-[9] w-full h-full",style:{background:Z?.style?.base?.primaryColor},children:t(k,{})}),t("div",{className:"h-full flex-col relative w-full flex justify-center items-center text-center rounded-t-[20px]",style:{background:Z?.style?.base?.backgroundColor},children:t("div",{className:"flex-1 w-full max-w-md overflow-hidden",children:e("div",{className:"w-full h-full",children:[t("video",{ref:m,autoPlay:!0,playsInline:!0,muted:!0,width:y.width,height:y.height,className:"w-full h-full object-cover fixed left-0 top-0 z-0",style:{transform:"scaleX(-1)"}}),t("canvas",{ref:z,width:y.width,height:y.height,style:{transform:"scaleX(-1)",opacity:"0"}})]})})}),!R&&X&&t($,{loading:R,resetScanState:pe,config:Z}),U&&!X&&!R&&t(M,{open:!0,className:"face-scan-small camera-draw",anchor:"bottom",onClose:(e,t)=>{},hideBackdrop:!0,children:t(T,{stage:de,videoLoading:J,setVideoLoading:q,videoError:H,setVideoError:K,gender:te,config:Z,children:t("div",{className:"flex justify-center w-full p-[1rem]",children:t(f,{disabled:R||!me,className:"!w-[60px] !h-[35px] !py-0 !px-0",buttonText:V?"Reset":me?"Start":"Loading...",postfixIcon:V?t(_,{}):"",buttonFunc:V?pe:ge,resolvedConfig:Z})})})})]})};export{R as FaceScan};
|
|
1
|
+
import{jsxs as e,jsx as t,Fragment as a}from"react/jsx-runtime";import{useRef as n,useState as o,useMemo as r,useCallback as c,useEffect as l}from"react";import{ab as s,av as i,aw as d,a6 as u,ax as h,ay as f,S as m,az as g,aA as p,aB as y,aC as S,a7 as b,aD as w,aE as v,aF as x,aG as F,aH as N}from"./Header-C65iSMsF.js";import{A as _}from"./arrow-right-DXQbS3wL.js";import{s as I,L as k,D as M,a as D}from"./LoadingScreen-DGyU-r8n.js";import*as z from"@tensorflow-models/pose-detection";import{setBackend as C,ready as P}from"@tensorflow/tfjs";import"react-dom";function T({stage:a,videoLoading:n,setVideoLoading:o,videoError:r,setVideoError:c,children:l,gender:m,config:g}){return-1===a?e("div",{className:"text-center p-[16px] w-full h-full",style:{background:g?.style?.base?.backgroundColor},children:[e("div",{className:"w-full",children:[t(s,{noTitle:!0,resolvedConfig:g}),e("h2",{style:{fontFamily:g?.style?.heading?.headingFontFamily||"SeriouslyNostalgic Fn",fontSize:g?.style?.heading?.headingFontSize||"32px",color:g?.style?.heading?.headingColor||"#000",fontWeight:g?.style?.heading?.headingFontWeight||"normal"},children:["Make sure your face is fully visible"," "]}),n&&t("div",{className:"mb-4 flex items-center justify-center",style:{fontFamily:g?.style?.subheading?.subheadingFontFamily||"'Inter', sans-serif",fontSize:g?.style?.subheading?.subheadingFontSize||"14px",color:g?.style?.subheading?.subheadingColor||"#4b5563",fontWeight:g?.style?.subheading?.subheadingFontWeight||"normal"},children:"Loading video..."}),r&&t("div",{className:"mb-4 text-red-600",style:{fontFamily:g?.style?.subheading?.subheadingFontFamily||"'Inter', sans-serif",fontSize:g?.style?.subheading?.subheadingFontSize||"14px",color:"#ff0000",fontWeight:g?.style?.subheading?.subheadingFontWeight||"normal"},children:"Video failed to load. Please refresh the page or check your connection."}),!r&&t("div",{className:" w-[100px] mx-auto",children:t("video",{src:m===u.Male?i:d,autoPlay:!0,loop:!0,controls:!1,muted:!0,playsInline:!0,className:"h-full w-full object-contain border-none",onCanPlay:()=>o(!1),onLoadStart:()=>o(!0),onError:()=>c(!0),"aria-label":"Instructional video: Please remove your glasses before starting the scan."})})]}),l]}):t("div",{className:"text-center p-[16px] w-full h-full",style:{background:g?.style?.base?.backgroundColor},children:e("div",{className:"w-full",children:[t(s,{noTitle:!0,resolvedConfig:g}),t("h3",{className:"mb-[0.5rem]",style:{fontFamily:g?.style?.heading?.headingFontFamily||"SeriouslyNostalgic Fn",fontSize:g?.style?.heading?.headingFontSize||"32px",color:g?.style?.heading?.headingColor||"#000",fontWeight:g?.style?.heading?.headingFontWeight||"normal"},children:h[a]}),e("div",{className:"max-w-[400px] justify-center gap-1 mx-auto flex items-center",children:[t("div",{className:` w-[120px] overflow-hidden ${0===a?"opacity-100":"opacity-0 hidden"} `,children:t("video",{className:"h-full w-full object-contain border-none",muted:!0,loop:!0,autoPlay:!0,playsInline:!0,children:t("source",{src:m===u.Male?f.male.forward:f.female.forward,type:"video/mp4"})})}),t("div",{className:` w-[120px] overflow-hidden ${1===a?"opacity-100":"opacity-0 hidden"} `,children:t("video",{className:"h-full w-full object-contain border-none",muted:!0,loop:!0,autoPlay:!0,playsInline:!0,children:t("source",{src:m===u.Male?f.male.left:f.female.left,type:"video/mp4"})})}),t("div",{className:` w-[120px] overflow-hidden ${2===a?"opacity-100":"opacity-0 hidden"} `,children:t("video",{className:"h-full w-full object-contain border-none",muted:!0,loop:!0,autoPlay:!0,playsInline:!0,children:t("source",{src:m===u.Male?f.male.right:f.female.right,type:"video/mp4"})})}),t("div",{className:` w-[120px] overflow-hidden ${3===a?"opacity-100":"opacity-0 hidden"} `,children:t("video",{className:"h-full w-full object-contain border-none",muted:!0,loop:!0,autoPlay:!0,playsInline:!0,children:t("source",{src:m===u.Male?f.male.smile:f.female.smile,type:"video/mp4"})})})]})]})})}function $({resetScanState:a,loading:n,config:o}){return t("div",{className:"fixed top-[0] left-[0] z-[999] flex justify-center items-center w-full h-full",style:{background:o?.style?.base?.backgroundColor},children:e("div",{className:"flex flex-col w-full items-center p-[1rem] rounded-lg ",children:[t(s,{noTitle:!0,resolvedConfig:o}),e("div",{className:"flex flex-col items-center w-full",children:[t("h2",{className:"text-xl font-semibold text-gray-800 ",style:{fontFamily:o?.style?.heading?.headingFontFamily||"SeriouslyNostalgic Fn",fontSize:o?.style?.heading?.headingFontSize||"32px",color:o?.style?.heading?.headingColor||"#000",fontWeight:o?.style?.heading?.headingFontWeight||"normal"},children:"Your Scan Failed"}),t("p",{className:"mb-[1.5rem]",style:{fontFamily:o?.style?.subheading?.subheadingFontFamily||"'Inter', sans-serif",fontSize:o?.style?.subheading?.subheadingFontSize||"14px",color:o?.style?.subheading?.subheadingColor||"#4b5563",fontWeight:o?.style?.subheading?.subheadingFontWeight||"normal"},children:"Please click below to reset your scan."}),t(m,{disabled:n,className:"w-full h-[45px] shadow-[0px_1px_2px_0px_#00000040] text-[16px]",buttonText:"Reset Scan",postfixIcon:t(_,{}),buttonFunc:()=>a?.(),resolvedConfig:o})]})]})})}let j=null;function E({faceScanId:e,onValidPose:t,onScanComplete:a,onModelReady:s,onStartRecording:i,shopDomain:d}){const u=n(null),[h,f]=o(0),[m,S]=o(0),[b,w]=o(!1),[v,x]=o(!1),F=n(null),N=n(h),_=n(m),k=n([]),M=n(""),D=n(0),T=n(!1),$=n(0),E=/iPad|iPhone|iPod/.test(navigator.userAgent)||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1,R=r(()=>["Face front","Turn head fully left","Turn head fully right","Smile facing front"],[]),L=r(()=>function(e,t){let a;return(...n)=>{a&&clearTimeout(a),a=setTimeout(()=>e(...n),t)}}(e=>{g.capture(`${d}/face_scan_pose_detection`,e)},4e3),[d]),O=c((t,a,n,o,r,c)=>{const l=Date.now();if(!(l-$.current<4e3)){$.current=l;try{const l=t[0]||[0,0,0],s=t[2]||[0,0,0],i=t[5]||[0,0,0],d=t[9]||[0,0,0],u=t[10]||[0,0,0],h=t.map(e=>e[2]).filter(e=>e>0),f=h.length>0?h.reduce((e,t)=>e+t,0)/h.length:0,m=a.map(e=>e[2]).filter(e=>e>0),g=m.length>0?m.reduce((e,t)=>e+t,0)/m.length:0,p=Math.abs(s[0]-i[0]),y=l[0]-i[0],S=s[0]-l[0];let b="frontal";1===n?b="left":2===n?b="right":3===n&&(b="frontal_smile");const w={faceScanId:e,stage:n,timestamp:(new Date).toISOString(),data:JSON.stringify({headValid:o,bodyInvalid:r,consecutiveValid:c,avgFaceScore:parseFloat(f.toFixed(3)),avgBodyScore:parseFloat(g.toFixed(3)),faceKeypointsDetected:h.length,bodyKeypointsDetected:m.length,eyeDistance:parseFloat(p.toFixed(2)),noseToRightRatio:parseFloat((y/p).toFixed(3)),noseToLeftRatio:parseFloat((S/p).toFixed(3)),noseScore:parseFloat(l[2].toFixed(3)),leftEyeScore:parseFloat(s[2].toFixed(3)),rightEyeScore:parseFloat(i[2].toFixed(3)),leftMouthScore:parseFloat(d[2].toFixed(3)),rightMouthScore:parseFloat(u[2].toFixed(3)),expectedPosition:b})};L(w)}catch(e){console.warn("Failed to track pose detection:",e)}}},[e,L]),W=()=>{g.capture(`${d}/face_scan_detection_started`,{faceScanId:e,stage:N.current,timestamp:(new Date).toISOString(),stageName:R[N.current]}),setTimeout(()=>{x(!0)},1500)},V=async()=>{try{console.log("Initializing TensorFlow..."),await C("webgl"),await P(),console.log("TensorFlow initialized, loading models...");const e=await(async()=>{if(j)return j;const e=await z.createDetector(z.SupportedModels.BlazePose,{runtime:"tfjs",modelType:"full"}),t=document.createElement("canvas");t.width=y.width,t.height=y.height;const a=t.getContext("2d");a&&(a.fillStyle="black",a.fillRect(0,0,t.width,t.height));try{await e.estimatePoses(t)}catch(e){console.log(e,"at time of preload")}return j=e,e})();console.log(e,"detector"),u.current=e,console.log("Pose detection model loaded successfully"),w(!0),s?.()}catch(e){console.error("Error initializing face scan:",e)}};return l(()=>(V(),()=>{(async()=>{try{w(!1),u.current&&(await u.current.dispose(),u.current=null)}catch(e){console.error("Error disposing resources:",e)}finally{f(0),S(0),N.current=0,_.current=0,k.current=[]}})()}),[]),l(()=>{N.current=h},[h]),l(()=>{_.current=m},[m]),{faceScanDetector:async(n,o)=>{if(u.current&&n.current&&b&&v)try{const r=await u.current.estimatePoses(n.current);if(r.length>0){const n=[],c=[];if(r[0].keypoints.forEach((e,t)=>{const a=e.score??0,o=[e.x??-1,e.y??-1,a];t<=10?n.push(o):c.push(o)}),o?.current){const e=o.current.getContext("2d"),{width:t,height:a}=o.current;if(e){e.clearRect(0,0,t,a);const n=Math.min(_.current/2,1),o=.35*a;e.beginPath(),e.strokeStyle="#00ff00",e.lineWidth=6,e.arc(t/2,a/2,o+10,-Math.PI/2,-Math.PI/2+2*n*Math.PI),e.stroke()}}const l=((e,t)=>{if(t[0][2]<.2||t[2][2]<.2||t[5][2]<.2)return!1;const a=.5*(t[10][1]+t[9][1])-.5*(t[5][1]+t[2][1]),n=(.5*(t[10][1]+t[9][1])-t[0][1])/a,o=n>.7||n<.3,r=Math.abs(t[2][1]-t[5][1])<.5*a&&Math.abs(t[9][1]-t[10][1])<.5*a,c=t[2][0]-t[5][0],l=t[0][0]-t[5][0],s=l>.4*c&&l<.6*c,i=l>.85*c,d=t[2][0]-t[0][0]>.85*c;switch(e){case 0:case 3:return!o&&r&&s;case 1:return!o&&r&&i;case 2:return!o&&r&&d;default:return!1}})(N.current,n),s=c.slice(2).filter(e=>e[2]>.7).length<=2;k.current.push(l&&s),k.current.length>10&&k.current.shift();if(k.current.filter(Boolean).length>=2){const e=_.current+1;S(e)}else S(Math.max(0,_.current-1));if(_.current>=2){t?.();const n=N.current+1;S(0),k.current=[],f(n),x(!1),(async()=>{if(await I.playAudio(`${p}face-scan-vos/Sound-effect.mp3`),g.capture(`${d}/face_scan_detection_stage_completed`,{faceScanId:e,completedStage:N.current,nextStage:n,timestamp:(new Date).toISOString(),totalStages:4,stageName:R[N.current]}),1===n&&i&&i(),n>=4)a?.();else{let e=null;switch(n){case 1:e="Left.mp3";break;case 2:e="Right.mp3";break;case 3:e="Smile.mp3";break;default:e=null}e&&await I.playAudio(`${p}face-scan-vos/${e}`),W()}})()}O(n,c,N.current,l,s,_.current)}}catch(e){console.error("Pose detection error:",e)}},scanStage:h,setScanStage:f,consecutiveValid:m,isModelLoaded:b,resetScan:()=>{g.capture(`${d}/face_scan_reset`,{faceScanId:e,stage:N.current,timestamp:(new Date).toISOString(),stageName:R[N.current],consecutiveValid:_.current}),f(0),S(0),x(!1),N.current=0,_.current=0,k.current=[],M.current="",D.current=0,T.current=!1,F.current&&(speechSynthesis.cancel(),F.current=null)},enableVoiceCommands:()=>{if(T.current=!0,E&&"speechSynthesis"in window){const e=new SpeechSynthesisUtterance("");e.volume=0,speechSynthesis.speak(e)}},startScanSequence:W,isScanningActive:v}}const R=({userDetails:s,onComplete:i,onScanError:d,onRetry:u,config:h,isError:f,isSuccess:z})=>{const C=n(null),P=n(null),j=n(null),R=n([]),L=n(null),[O,W]=o(!1),[V,A]=o(!1),[U,B]=o(!1),[J,q]=o(!0),[H,K]=o(!1),[X,G]=o(!1),[Y,Q]=o(!1),[Z,ee]=o(S()),te=b(h),{email:ae,gender:ne,deviceFocalLength:oe,shopDomain:re,callbackUrl:ce}=s,le=r(()=>w.filter(e=>MediaRecorder.isTypeSupported(e)),[]),se=c(()=>{console.log("Stopping recording..."),j.current&&"recording"===j.current.state&&j.current.stop(),j.current=null},[]),ie=async()=>{if(W(!0),console.log("upload final video"),R.current){if(0===R.current.length)return console.error("No video data recorded"),Q(!0),void W(!1);const e=new File(R.current,`${Z}.webm`,{type:"video/webm"}),t=e.size,a=(t/1048576).toFixed(2),n=Math.round(t/1e4),o={video_size_mb:parseFloat(a),video_size_bytes:t,blob_count:R.current.length,estimated_duration_seconds:n},r=[{gender:ne},{face_scan_id:Z},{focal_length:`${oe}`},{customer_store_url:re},{scan_type:"face_scan"},{callback_url:ce}];F({eventName:`${re}/face_scan_meta_data`,faceScanId:Z,email:ae,data:JSON.stringify({metaData:r,videoData:o})});const c=Date.now();F({eventName:`${re}/face_scan_upload_start`,faceScanId:Z,email:ae,startTime:c});try{const t=await D.fileUpload.faceScanFileUploader({file:e,objectKey:Z,email:ae,arrayMetaData:r,contentType:e.type}),a=Date.now();F({eventName:`${re}/face_scan_upload_complete`,faceScanId:Z,email:ae,completionTime:a,uploadDuration:a-c}),console.log("✅ Upload successful",t),D.measurement.handlFaceScaneSocket({faceScanId:Z,onOpen:()=>{N({eventName:`${re}/webSocket`,faceScanID:Z,connection:"open",type:"faceScan_recommendation",email:ae}),console.log("websocket connect open")},onError:e=>{N({eventName:`${re}/webSocket`,faceScanID:Z,connection:"error",type:"faceScan_recommendation",email:ae}),be(e)},onSuccess:e=>{N({eventName:`${re}/webSocket`,faceScanID:Z,connection:"success",type:"faceScan_recommendation",email:ae}),we(e)},onClose:()=>{console.log("websocket connect close"),N({eventName:`${re}/webSocket`,faceScanID:Z,connection:"close",type:"faceScan_recommendation",email:ae})}})}catch(e){be(e)}}},de=c(()=>{console.log("Starting recording for stages 1-3...");const e=C.current?.srcObject;if(!e)return;const t=e.getVideoTracks()[0].getSettings(),{width:a=y.width,height:n=y.height}=t,o=document.createElement("canvas"),r=o.getContext("2d");a>n?(o.width=n,o.height=a):(o.width=a,o.height=n);const c=document.createElement("video");c.srcObject=e,c.muted=!0,c.playsInline=!0,c.play();const l=()=>{a>n?(r?.save(),r?.translate(o.width,0),r?.rotate(Math.PI/2),r?.drawImage(c,0,0,n,a),r?.restore()):r?.drawImage(c,0,0,a,n),requestAnimationFrame(l)};l();const s=o.captureStream(30),i=le.length>0?{mimeType:le[0]}:{};let d;try{d=new MediaRecorder(s,i)}catch(e){return console.error("MediaRecorder init failed:",e),Q(!0),void W(!1)}R.current=[],d.ondataavailable=e=>{e?.data&&e.data.size>0&&R.current&&R.current.push(e.data)},d.onstop=()=>{console.log("Recording stopped, total blobs:",R?.current?.length),ie()},d.start(100),j.current=d,console.log("Recording started successfully (portrait normalized)")},[le,ie]),{faceScanDetector:ue,scanStage:he,setScanStage:fe,resetScan:me,isModelLoaded:ge,startScanSequence:pe}=E({faceScanId:Z,shopDomain:re,onScanComplete:()=>{se()},onModelReady:()=>{A(!0)},onStartRecording:()=>{console.log("Stage 0 completed - starting recording for stages 1-3"),de()}}),ye=c(()=>{ge&&(fe(0),I.playAudio(`${p}face-scan-vos/Face-forward.mp3`),setTimeout(()=>{B(!0),setTimeout(()=>{pe()},200)},200))},[fe,B,pe,ge]),Se=c(()=>{B(!1),q(!0),se(),me(),u?.(),R.current=[],j.current&&(j.current=null),Q(!1),fe(-1),ee(S()),C.current&&L.current&&(C.current.srcObject=L.current,console.log("Camera stream restored after reset"))},[fe,B,q,se,me,ee]),be=e=>{console.log(e,"ws error"),d?.(e),Q(!0),W(!1),q(!1),F({eventName:`${re}/faceScan`,faceScanId:Z,status:"failed",email:ae,data:JSON.stringify(e)})},we=e=>{console.log(e,"ws success"),e&&"intermediate"===e?.resultType&&F({eventName:`${re}/faceScan_success/intermediate`,faceScanId:Z,status:"success",email:ae,data:JSON.stringify(e)}),i?.(e)};l(()=>{if(!f&&!z)return navigator.mediaDevices.getUserMedia&&navigator.mediaDevices.getUserMedia({video:y}).then(e=>{L.current=e,C.current&&(C.current.srcObject=e,console.log("Webcam initialized"))}).catch(e=>console.error("Error accessing webcam:",e)),()=>{L.current&&L.current.getTracks().forEach(e=>e.stop())}},[f,z]),l(()=>{if(!V||!U)return;const e=setInterval(()=>{ue(C,P)},500);return()=>clearInterval(e)},[ue,V,U]);return console.log("Model ready:",V,"Has error:",Y,"Is scanning:",U,"showLoader",O),l(()=>{fe(-1),f||z||(g.init(v,{api_host:x}),g.capture("$pageview"))},[f,z]),f?t($,{config:te}):z?t("div",{className:"fixed z-[9] w-full h-full",style:{background:te?.style?.base?.primaryColor},children:t(k,{url:te?.loader})}):e(a,{children:[t("audio",{id:"audioElement",crossOrigin:"anonymous",preload:"auto",style:{position:"absolute",zIndex:-99999},src:""}),O&&!Y&&t("div",{className:"fixed z-[9] w-full h-full",style:{background:te?.style?.base?.primaryColor},children:t(k,{url:te?.loader})}),t("div",{className:"h-full flex-col relative w-full flex justify-center items-center text-center rounded-t-[20px]",style:{background:te?.style?.base?.backgroundColor},children:t("div",{className:"flex-1 w-full max-w-md overflow-hidden",children:e("div",{className:"w-full h-full",children:[t("video",{ref:C,autoPlay:!0,playsInline:!0,muted:!0,width:y.width,height:y.height,className:"w-full h-full object-cover fixed left-0 top-0 z-0",style:{transform:"scaleX(-1)"}}),t("canvas",{ref:P,width:y.width,height:y.height,style:{transform:"scaleX(-1)",opacity:"0"}})]})})}),!O&&Y&&t($,{loading:O,resetScanState:Se,config:te}),J&&!Y&&!O&&t(M,{open:!0,className:"face-scan-small camera-draw",anchor:"bottom",onClose:(e,t)=>{},hideBackdrop:!0,children:t(T,{stage:he,videoLoading:H,setVideoLoading:K,videoError:X,setVideoError:G,gender:ne,config:te,children:t("div",{className:"flex justify-center w-full p-[1rem]",children:t(m,{disabled:O||!ge,className:"!w-[60px] !h-[35px] !py-0 !px-0",buttonText:U?"Reset":ge?"Start":"Loading...",postfixIcon:U?t(_,{}):"",buttonFunc:U?Se:ye,resolvedConfig:te})})})})]})};export{R as FaceScan};
|
|
2
2
|
//# sourceMappingURL=faceScan.mjs.map
|
package/dist/faceScan.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"faceScan.mjs","sources":["../src/components/faceScan/FaceScanGuide.tsx","../src/components/faceScan/FaceScanErrorScreen.tsx","../src/customHooks/useFaceScan.ts","../src/components/faceScan/FaceScan.tsx"],"sourcesContent":["\nimport { FaceScanGuideProps } from \"../../types/interfaces\";\nimport Header from \"../Header\";\nimport { GenderType } from \"../../utils/enums\";\nimport { directionMessages, FACE_SCAN_HEADSHOT, glassesOffVideo, maleGlassesOffVideo } from \"../../utils/constants\";\n\n\nfunction FaceScanGuide({\n\tstage,\n\tvideoLoading,\n\tsetVideoLoading,\n\tvideoError,\n\tsetVideoError,\n\tchildren,\n\tgender,\n\tconfig,\n}: FaceScanGuideProps) {\n\tif (stage === -1) {\n\t\treturn (\n\t\t\t<div className=\"text-center p-[16px] w-full h-full\" style={{ background: config?.style?.base?.backgroundColor }}>\n\t\t\t\t<div className=\"w-full\">\n\t\t\t\t\t<Header noTitle resolvedConfig={config} />\n\t\t\t\t\t<h2\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tfontFamily: config?.style?.heading?.headingFontFamily || \"SeriouslyNostalgic Fn\",\n\t\t\t\t\t\t\tfontSize: config?.style?.heading?.headingFontSize || \"32px\",\n\t\t\t\t\t\t\tcolor: config?.style?.heading?.headingColor || \"#000\",\n\t\t\t\t\t\t\tfontWeight: config?.style?.heading?.headingFontWeight || \"normal\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tMake sure your face is fully visible{\" \"}\n\t\t\t\t\t</h2>\n\t\t\t\t\t{videoLoading && (\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"mb-4 flex items-center justify-center\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\tfontFamily: config?.style?.subheading?.subheadingFontFamily || \"'Inter', sans-serif\",\n\t\t\t\t\t\t\t\tfontSize: config?.style?.subheading?.subheadingFontSize || \"14px\",\n\t\t\t\t\t\t\t\tcolor: config?.style?.subheading?.subheadingColor || \"#4b5563\",\n\t\t\t\t\t\t\t\tfontWeight: config?.style?.subheading?.subheadingFontWeight || \"normal\",\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tLoading video...\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{videoError && (\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"mb-4 text-red-600\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\tfontFamily: config?.style?.subheading?.subheadingFontFamily || \"'Inter', sans-serif\",\n\t\t\t\t\t\t\t\tfontSize: config?.style?.subheading?.subheadingFontSize || \"14px\",\n\t\t\t\t\t\t\t\tcolor: \"#ff0000\",\n\t\t\t\t\t\t\t\tfontWeight: config?.style?.subheading?.subheadingFontWeight || \"normal\",\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tVideo failed to load. Please refresh the page or check your connection.\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{!videoError && (\n\t\t\t\t\t\t<div className={` w-[100px] mx-auto`}>\n\t\t\t\t\t\t\t<video\n\t\t\t\t\t\t\t\tsrc={gender === GenderType.Male ? maleGlassesOffVideo : glassesOffVideo}\n\t\t\t\t\t\t\t\tautoPlay\n\t\t\t\t\t\t\t\tloop\n\t\t\t\t\t\t\t\tcontrols={false}\n\t\t\t\t\t\t\t\tmuted\n\t\t\t\t\t\t\t\tplaysInline\n\t\t\t\t\t\t\t\tclassName=\"h-full w-full object-contain border-none\"\n\t\t\t\t\t\t\t\tonCanPlay={() => setVideoLoading(false)}\n\t\t\t\t\t\t\t\tonLoadStart={() => setVideoLoading(true)}\n\t\t\t\t\t\t\t\tonError={() => setVideoError(true)}\n\t\t\t\t\t\t\t\taria-label=\"Instructional video: Please remove your glasses before starting the scan.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className=\"text-center p-[16px] w-full h-full\" style={{ background: config?.style?.base?.backgroundColor }}>\n\t\t\t<div className=\"w-full\">\n\t\t\t\t<Header noTitle resolvedConfig={config} />\n\t\t\t\t<h3\n\t\t\t\t\tclassName=\"mb-[0.5rem]\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tfontFamily: config?.style?.heading?.headingFontFamily || \"SeriouslyNostalgic Fn\",\n\t\t\t\t\t\tfontSize: config?.style?.heading?.headingFontSize || \"32px\",\n\t\t\t\t\t\tcolor: config?.style?.heading?.headingColor || \"#000\",\n\t\t\t\t\t\tfontWeight: config?.style?.heading?.headingFontWeight || \"normal\",\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{directionMessages[stage]}\n\t\t\t\t</h3>\n\t\t\t\t{/* <p>We'll guide you to take 6 selfies </p> */}\n\t\t\t\t<div className=\"max-w-[400px] justify-center gap-1 mx-auto flex items-center\">\n\t\t\t\t\t<div className={` w-[120px] overflow-hidden ${stage === 0 ? \"opacity-100\" : \"opacity-0 hidden\"} `}>\n\t\t\t\t\t\t<video className=\"h-full w-full object-contain border-none\" muted loop autoPlay playsInline>\n\t\t\t\t\t\t\t<source src={gender === GenderType.Male ? FACE_SCAN_HEADSHOT.male.forward : FACE_SCAN_HEADSHOT.female.forward} type=\"video/mp4\" />\n\t\t\t\t\t\t</video>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className={` w-[120px] overflow-hidden ${stage === 1 ? \"opacity-100\" : \"opacity-0 hidden\"} `}>\n\t\t\t\t\t\t<video className=\"h-full w-full object-contain border-none\" muted loop autoPlay playsInline>\n\t\t\t\t\t\t\t<source src={gender === GenderType.Male ? FACE_SCAN_HEADSHOT.male.left : FACE_SCAN_HEADSHOT.female.left} type=\"video/mp4\" />\n\t\t\t\t\t\t</video>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className={` w-[120px] overflow-hidden ${stage === 2 ? \"opacity-100\" : \"opacity-0 hidden\"} `}>\n\t\t\t\t\t\t<video className=\"h-full w-full object-contain border-none\" muted loop autoPlay playsInline>\n\t\t\t\t\t\t\t<source src={gender === GenderType.Male ? FACE_SCAN_HEADSHOT.male.right : FACE_SCAN_HEADSHOT.female.right} type=\"video/mp4\" />\n\t\t\t\t\t\t</video>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className={` w-[120px] overflow-hidden ${stage === 3 ? \"opacity-100\" : \"opacity-0 hidden\"} `}>\n\t\t\t\t\t\t<video className=\"h-full w-full object-contain border-none\" muted loop autoPlay playsInline>\n\t\t\t\t\t\t\t<source src={gender === GenderType.Male ? FACE_SCAN_HEADSHOT.male.smile : FACE_SCAN_HEADSHOT.female.smile} type=\"video/mp4\" />\n\t\t\t\t\t\t</video>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport default FaceScanGuide;\n","import { ArrowRight } from \"lucide-react\";\nimport Header from \"../Header\";\nimport SpecificButton from \"../../atoms/specificButton/SpecificButton\";\n\nfunction FaceScanErrorScreen({ resetScanState, loading, config }: { resetScanState: () => void; loading: boolean; config?: any }) {\n\treturn (\n\t\t<div className=\"fixed top-[0] left-[0] z-[999] flex justify-center items-center w-full h-full\" style={{ background: config?.style?.base?.backgroundColor }}>\n\t\t\t<div className=\"flex flex-col w-full items-center p-[1rem] rounded-lg \">\n\t\t\t\t<Header noTitle resolvedConfig={config} />\n\t\t\t\t<div className=\"flex flex-col items-center w-full\">\n\t\t\t\t\t<h2\n\t\t\t\t\t\tclassName=\"text-xl font-semibold text-gray-800 \"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tfontFamily: config?.style?.heading?.headingFontFamily || \"SeriouslyNostalgic Fn\",\n\t\t\t\t\t\t\tfontSize: config?.style?.heading?.headingFontSize || \"32px\",\n\t\t\t\t\t\t\tcolor: config?.style?.heading?.headingColor || \"#000\",\n\t\t\t\t\t\t\tfontWeight: config?.style?.heading?.headingFontWeight || \"normal\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tYour Scan Failed\n\t\t\t\t\t</h2>\n\t\t\t\t\t<p\n\t\t\t\t\t\tclassName=\"mb-[1.5rem]\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tfontFamily: config?.style?.subheading?.subheadingFontFamily || \"'Inter', sans-serif\",\n\t\t\t\t\t\t\tfontSize: config?.style?.subheading?.subheadingFontSize || \"14px\",\n\t\t\t\t\t\t\tcolor: config?.style?.subheading?.subheadingColor || \"#4b5563\",\n\t\t\t\t\t\t\tfontWeight: config?.style?.subheading?.subheadingFontWeight || \"normal\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tPlease click below to reset your scan.\n\t\t\t\t\t</p>\n\t\t\t\t\t<SpecificButton\n\t\t\t\t\t\tdisabled={loading}\n\t\t\t\t\t\tclassName=\"w-full h-[45px] shadow-[0px_1px_2px_0px_#00000040] text-[16px]\"\n\t\t\t\t\t\tbuttonText=\"Reset Scan\"\n\t\t\t\t\t\tpostfixIcon={<ArrowRight />}\n\t\t\t\t\t\tbuttonFunc={() => resetScanState()}\n\t\t\t\t\t\tresolvedConfig={config}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport default FaceScanErrorScreen;\n","import {\n useState,\n useRef,\n useEffect,\n useCallback,\n useMemo,\n RefObject,\n} from \"react\";\nimport * as poseDetection from \"@tensorflow-models/pose-detection\";\nimport \"@tensorflow/tfjs\";\nimport { posthog } from \"posthog-js\";\nimport { ready, setBackend } from \"@tensorflow/tfjs\";\nimport speechService from \"../utils/service/speechService\";\nimport { videoConstraints, voiceOverAssetsPath } from \"../utils/constants\";\n\n/**\n * useFaceScan Hook with PostHog Analytics\n *\n * Analytics Events Tracked:\n * 1. face_scan_pose_detection - Every 4 seconds during scanning\n * - Keypoint scores and positions\n * - Face/body detection metrics\n * - Stage-specific positioning data\n * - Time spent in current stage\n *\n * 2. face_scan_stage_completed - When user successfully completes a stage\n * - Stage transition data\n * - Completion time\n *\n * 3. face_scan_started - When scan sequence begins\n * - Stage information\n * - Start timestamp\n *\n * 4. face_scan_reset - When user resets the scan\n * - Reset reason analysis\n * - Stage where reset occurred\n *\n * This helps identify why users take longer to get detected in pose detection.\n */\n\n// Debounce utility\nfunction debounce(fn: (...args: any) => void, delay: number) {\n let timer: number | NodeJS.Timeout;\n return (...args: any) => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => fn(...args), delay);\n };\n}\n\nlet preloadedDetector: poseDetection.PoseDetector | null = null;\n\nexport const preloadDetector = async () => {\n if (preloadedDetector) return preloadedDetector;\n\n const detector = await poseDetection.createDetector(\n poseDetection.SupportedModels.BlazePose,\n {\n runtime: \"tfjs\",\n modelType: \"full\",\n }\n );\n\n // Dummy video warmup\n const dummyCanvas = document.createElement(\"canvas\");\n dummyCanvas.width = videoConstraints.width;\n dummyCanvas.height = videoConstraints.height;\n const ctx = dummyCanvas.getContext(\"2d\");\n if (ctx) {\n ctx.fillStyle = \"black\";\n ctx.fillRect(0, 0, dummyCanvas.width, dummyCanvas.height);\n }\n try {\n await detector.estimatePoses(dummyCanvas);\n } catch (e) {\n // Expected: empty video stream\n console.log(e, \"at time of preload\");\n }\n\n preloadedDetector = detector;\n return detector;\n};\n\ntype Point = [number, number, number];\n\nfunction useFaceScan({\n faceScanId,\n onValidPose,\n onScanComplete,\n onModelReady,\n onStartRecording,\n shopDomain,\n}: {\n faceScanId: string;\n onValidPose?: () => void;\n onScanComplete?: () => void;\n onModelReady?: () => void;\n onStartRecording: () => void;\n shopDomain: string;\n}) {\n const detectorRef = useRef<poseDetection.PoseDetector | null>(null);\n const [scanStage, setScanStage] = useState(0);\n const [consecutiveValid, setConsecutiveValid] = useState(0);\n const [isModelLoaded, setIsModelLoaded] = useState(false);\n const [isScanningActive, setIsScanningActive] = useState(false);\n const utteranceRef = useRef(null);\n const scanStageRef = useRef(scanStage);\n const consecutiveValidRef = useRef(consecutiveValid);\n const validityBufferRef = useRef<boolean[]>([]);\n const lastSpokenMessageRef = useRef(\"\");\n const lastSpokenTimeRef = useRef(0);\n const voiceEnabledRef = useRef(false);\n const VALIDITY_BUFFER_LENGTH = 10;\n const CONSECUTIVE_VALID_LENGTH = 2;\n const TOTAL_STAGES = 4;\n const POSE_TRACKING_INTERVAL = 4000; // 4 seconds between pose tracking events\n const lastPoseTrackingTimeRef = useRef(0);\n\n // iOS detection\n const isIOS =\n /iPad|iPhone|iPod/.test(navigator.userAgent) ||\n (navigator.platform === \"MacIntel\" && navigator.maxTouchPoints > 1);\n\n const directionMessages = useMemo(\n () => [\n \"Face front\",\n \"Turn head fully left\",\n \"Turn head fully right\",\n \"Smile facing front\",\n ],\n []\n );\n\n // Debounced analytics event\n const debouncedTrackPoseDetection = useMemo(\n () =>\n debounce((payload) => {\n posthog.capture(`${shopDomain}/face_scan_pose_detection`, payload);\n }, 4000),\n [shopDomain]\n );\n\n // Track pose detection performance for analytics\n const trackPoseDetection = useCallback(\n (\n faceKeypoints: Point[],\n bodyKeypoints: Point[],\n stage: number,\n headValid: boolean,\n bodyInvalid: boolean,\n consecutiveValidCount: number\n ) => {\n const now = Date.now();\n\n // Only track every 4 seconds to avoid spam\n if (now - lastPoseTrackingTimeRef.current < POSE_TRACKING_INTERVAL) {\n return;\n }\n\n lastPoseTrackingTimeRef.current = now;\n\n try {\n // Extract key face keypoints for analysis\n const nose = faceKeypoints[0] || [0, 0, 0];\n const leftEye = faceKeypoints[2] || [0, 0, 0];\n const rightEye = faceKeypoints[5] || [0, 0, 0];\n const leftMouth = faceKeypoints[9] || [0, 0, 0];\n const rightMouth = faceKeypoints[10] || [0, 0, 0];\n\n // Calculate key metrics\n const faceKeypointScores = faceKeypoints\n .map((kp: Point) => kp[2])\n .filter((score: number) => score > 0);\n const avgFaceScore =\n faceKeypointScores.length > 0\n ? faceKeypointScores.reduce((a, b) => a + b, 0) /\n faceKeypointScores.length\n : 0;\n\n const bodyKeypointScores = bodyKeypoints\n .map((kp: Point) => kp[2])\n .filter((score: number) => score > 0);\n const avgBodyScore =\n bodyKeypointScores.length > 0\n ? bodyKeypointScores.reduce((a: number, b: number) => a + b, 0) /\n bodyKeypointScores.length\n : 0;\n\n // Calculate face positioning metrics\n const eyeDistance = Math.abs(leftEye[0] - rightEye[0]);\n const noseToRight = nose[0] - rightEye[0];\n const noseToLeft = leftEye[0] - nose[0];\n\n // Determine expected position based on stage\n let expectedPosition = \"frontal\";\n if (stage === 1) expectedPosition = \"left\";\n else if (stage === 2) expectedPosition = \"right\";\n else if (stage === 3) expectedPosition = \"frontal_smile\";\n\n const payload = {\n faceScanId,\n stage,\n timestamp: new Date().toISOString(),\n data: JSON.stringify({\n headValid,\n bodyInvalid,\n consecutiveValid: consecutiveValidCount,\n avgFaceScore: parseFloat(avgFaceScore.toFixed(3)),\n avgBodyScore: parseFloat(avgBodyScore.toFixed(3)),\n faceKeypointsDetected: faceKeypointScores.length,\n bodyKeypointsDetected: bodyKeypointScores.length,\n eyeDistance: parseFloat(eyeDistance.toFixed(2)),\n noseToRightRatio: parseFloat(\n (noseToRight / eyeDistance).toFixed(3)\n ),\n noseToLeftRatio: parseFloat((noseToLeft / eyeDistance).toFixed(3)),\n noseScore: parseFloat(nose[2].toFixed(3)),\n leftEyeScore: parseFloat(leftEye[2].toFixed(3)),\n rightEyeScore: parseFloat(rightEye[2].toFixed(3)),\n leftMouthScore: parseFloat(leftMouth[2].toFixed(3)),\n rightMouthScore: parseFloat(rightMouth[2].toFixed(3)),\n expectedPosition,\n // timeInStage: Math.floor((now - lastPoseTrackingTimeRef.current) / 1000),\n }),\n };\n debouncedTrackPoseDetection(payload);\n } catch (error) {\n console.warn(\"Failed to track pose detection:\", error);\n }\n },\n [faceScanId, debouncedTrackPoseDetection]\n );\n\n // Enable voice commands from user interaction context\n const enableVoiceCommands = () => {\n voiceEnabledRef.current = true; // Set ref immediately\n\n // iOS Safari requires an initial speech synthesis call to enable audio context\n if (isIOS && \"speechSynthesis\" in window) {\n // Create a silent utterance to initialize speech synthesis\n const silentUtterance = new SpeechSynthesisUtterance(\"\");\n silentUtterance.volume = 0; // Set volume to 0 to make it silent\n speechSynthesis.speak(silentUtterance);\n }\n };\n\n // Start scan sequence: first speak direction, then start scanning\n const startScanSequence = () => {\n // Track scan start\n posthog.capture(`${shopDomain}/face_scan_detection_started`, {\n faceScanId,\n stage: scanStageRef.current,\n timestamp: new Date().toISOString(),\n stageName: directionMessages[scanStageRef.current],\n });\n\n // Then start scanning after the voice command finishes\n setTimeout(() => {\n setIsScanningActive(true);\n }, 1500); // Wait for voice command to complete\n };\n\n const isHeadInPosition = (stage: number, keypoints: Point[]) => {\n const nose = 0;\n const leftEye = 2;\n const rightEye = 5;\n const leftMouth = 9;\n const rightMouth = 10;\n\n const minScore = 0.2;\n if (\n keypoints[nose][2] < minScore ||\n keypoints[leftEye][2] < minScore ||\n keypoints[rightEye][2] < minScore\n ) {\n return false;\n }\n\n const eyesToMouthVerticalDistance =\n 0.5 * (keypoints[rightMouth][1] + keypoints[leftMouth][1]) -\n 0.5 * (keypoints[rightEye][1] + keypoints[leftEye][1]);\n\n const faceTiltedness =\n (0.5 * (keypoints[rightMouth][1] + keypoints[leftMouth][1]) -\n keypoints[nose][1]) /\n eyesToMouthVerticalDistance;\n\n const faceIsTilted = faceTiltedness > 0.7 || faceTiltedness < 0.3;\n\n const faceIsVertical =\n Math.abs(keypoints[leftEye][1] - keypoints[rightEye][1]) <\n 0.5 * eyesToMouthVerticalDistance &&\n Math.abs(keypoints[leftMouth][1] - keypoints[rightMouth][1]) <\n 0.5 * eyesToMouthVerticalDistance;\n\n const eyeDistance = keypoints[leftEye][0] - keypoints[rightEye][0];\n const noseToRight = keypoints[nose][0] - keypoints[rightEye][0];\n const noseToLeft = keypoints[leftEye][0] - keypoints[nose][0];\n\n const frontalFace =\n noseToRight > 0.4 * eyeDistance && noseToRight < 0.6 * eyeDistance;\n const fullLeft = noseToRight > 0.85 * eyeDistance;\n const fullRight = noseToLeft > 0.85 * eyeDistance;\n\n switch (stage) {\n case 0:\n return !faceIsTilted && faceIsVertical && frontalFace;\n case 1:\n return !faceIsTilted && faceIsVertical && fullLeft;\n case 2:\n return !faceIsTilted && faceIsVertical && fullRight;\n case 3:\n return !faceIsTilted && faceIsVertical && frontalFace;\n default:\n return false;\n }\n };\n\n const faceScanDetector = async (\n webcamRef: RefObject<HTMLVideoElement | null>,\n canvasRef: RefObject<HTMLCanvasElement | null>\n ) => {\n if (\n !detectorRef.current ||\n !webcamRef.current ||\n !isModelLoaded ||\n !isScanningActive\n )\n return;\n\n try {\n const poses = await detectorRef.current.estimatePoses(webcamRef.current);\n if (poses.length > 0) {\n const faceKeypoints: Point[] = [];\n const bodyKeypoints: Point[] = [];\n poses[0].keypoints.forEach((landmark, idx) => {\n const score = landmark.score ?? 0;\n const point: Point = [landmark.x ?? -1, landmark.y ?? -1, score];\n if (idx <= 10) faceKeypoints.push(point);\n else bodyKeypoints.push(point);\n });\n\n // Optional drawing (invisible canvas)\n if (canvasRef?.current) {\n const ctx = canvasRef.current.getContext(\"2d\");\n const { width, height } = canvasRef.current;\n if (ctx) {\n ctx.clearRect(0, 0, width, height);\n\n const progress = Math.min(\n consecutiveValidRef.current / CONSECUTIVE_VALID_LENGTH,\n 1\n );\n const radius = height * 0.35;\n ctx.beginPath();\n ctx.strokeStyle = \"#00ff00\";\n ctx.lineWidth = 6;\n ctx.arc(\n width / 2,\n height / 2,\n radius + 10,\n -Math.PI / 2,\n -Math.PI / 2 + progress * 2 * Math.PI\n );\n ctx.stroke();\n }\n }\n\n const headValid = isHeadInPosition(scanStageRef.current, faceKeypoints);\n const bodyInvalid =\n bodyKeypoints.slice(2).filter((p) => p[2] > 0.7).length <= 2;\n\n validityBufferRef.current.push(headValid && bodyInvalid);\n if (validityBufferRef.current.length > VALIDITY_BUFFER_LENGTH) {\n validityBufferRef.current.shift();\n }\n\n const validCount = validityBufferRef.current.filter(Boolean).length;\n if (validCount >= 2) {\n const nextValid = consecutiveValidRef.current + 1;\n setConsecutiveValid(nextValid);\n } else {\n setConsecutiveValid(Math.max(0, consecutiveValidRef.current - 1));\n }\n\n // ✅ Pose confirmed\n if (consecutiveValidRef.current >= CONSECUTIVE_VALID_LENGTH) {\n onValidPose?.();\n\n const nextStage = scanStageRef.current + 1;\n setConsecutiveValid(0);\n validityBufferRef.current = [];\n setScanStage(nextStage);\n setIsScanningActive(false); // Stop scanning for next stage\n\n // Play sound effect for stage completion, then next stage instruction (if not final stage)\n (async () => {\n await speechService.playAudio(\n `${voiceOverAssetsPath}face-scan-vos/Sound-effect.mp3`\n );\n // Track stage completion\n posthog.capture(\n `${shopDomain}/face_scan_detection_stage_completed`,\n {\n faceScanId,\n completedStage: scanStageRef.current,\n nextStage,\n timestamp: new Date().toISOString(),\n totalStages: TOTAL_STAGES,\n stageName: directionMessages[scanStageRef.current],\n }\n );\n // Start recording after stage 0 is completed\n if (nextStage === 1 && onStartRecording) {\n onStartRecording();\n }\n if (nextStage >= TOTAL_STAGES) {\n onScanComplete?.();\n } else {\n // Play next stage instruction sound, then start scan sequence\n let nextSound = null;\n switch (nextStage) {\n case 1:\n nextSound = \"Left.mp3\";\n break;\n case 2:\n nextSound = \"Right.mp3\";\n break;\n case 3:\n nextSound = \"Smile.mp3\";\n break;\n default:\n nextSound = null;\n }\n if (nextSound) {\n await speechService.playAudio(\n `${voiceOverAssetsPath}face-scan-vos/${nextSound}`\n );\n }\n startScanSequence();\n }\n })();\n }\n\n // Track pose detection performance for analytics\n trackPoseDetection(\n faceKeypoints,\n bodyKeypoints,\n scanStageRef.current,\n headValid,\n bodyInvalid,\n consecutiveValidRef.current\n );\n }\n } catch (err) {\n console.error(\"Pose detection error:\", err);\n }\n };\n\n const initializeModels = async () => {\n try {\n console.log(\"Initializing TensorFlow...\");\n await setBackend(\"webgl\");\n await ready();\n console.log(\"TensorFlow initialized, loading models...\");\n const detector = await preloadDetector(); // <-- now it waits for preload\n console.log(detector, \"detector\");\n\n detectorRef.current = detector;\n console.log(\"Pose detection model loaded successfully\");\n setIsModelLoaded(true);\n onModelReady?.();\n } catch (err) {\n console.error(\"Error initializing face scan:\", err);\n }\n };\n\n const disposeModelAndTf = async () => {\n try {\n setIsModelLoaded(false);\n if (detectorRef.current) {\n await detectorRef.current.dispose();\n detectorRef.current = null;\n }\n } catch (err) {\n console.error(\"Error disposing resources:\", err);\n } finally {\n setScanStage(0);\n setConsecutiveValid(0);\n scanStageRef.current = 0;\n consecutiveValidRef.current = 0;\n validityBufferRef.current = [];\n }\n };\n\n const resetScan = () => {\n // Track scan reset\n posthog.capture(`${shopDomain}/face_scan_reset`, {\n faceScanId,\n stage: scanStageRef.current,\n timestamp: new Date().toISOString(),\n stageName: directionMessages[scanStageRef.current],\n consecutiveValid: consecutiveValidRef.current,\n });\n\n setScanStage(0);\n setConsecutiveValid(0);\n setIsScanningActive(false);\n scanStageRef.current = 0;\n consecutiveValidRef.current = 0;\n validityBufferRef.current = [];\n lastSpokenMessageRef.current = \"\";\n lastSpokenTimeRef.current = 0;\n voiceEnabledRef.current = false;\n if (utteranceRef.current) {\n speechSynthesis.cancel();\n utteranceRef.current = null;\n }\n };\n\n useEffect(() => {\n // disposeModelAndTf();\n initializeModels();\n return () => {\n disposeModelAndTf();\n };\n }, []);\n\n useEffect(() => {\n scanStageRef.current = scanStage;\n }, [scanStage]);\n\n useEffect(() => {\n consecutiveValidRef.current = consecutiveValid;\n }, [consecutiveValid]);\n\n return {\n faceScanDetector,\n scanStage,\n setScanStage,\n consecutiveValid,\n isModelLoaded,\n resetScan,\n enableVoiceCommands,\n startScanSequence,\n isScanningActive,\n };\n}\n\nexport default useFaceScan;\n","\"use client\"\nimport { useEffect, useRef, useState, useCallback, useMemo } from \"react\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Drawer } from \"@mui/material\";\nimport FaceScanGuide from \"./FaceScanGuide\";\nimport FaceScanErrorScreen from \"./FaceScanErrorScreen\";\nimport posthog from \"posthog-js\";\nimport { FaceScanProps } from \"../../types/interfaces\";\nimport {\n generateUuid,\n handleScanTimeCapture,\n handleWebSocketCapture,\n} from \"../../utils/utils\";\nimport { useLocalConfig } from \"../../config/useLocalConfig\";\nimport {\n posthogPublicHost,\n posthogPublicKey,\n videoConstraints,\n videoTypes,\n voiceOverAssetsPath,\n} from \"../../utils/constants\";\nimport swan from \"../../utils/service/swanService\";\nimport speechService from \"../../utils/service/speechService\";\nimport useFaceScan from \"../../customHooks/useFaceScan\";\nimport SpecificButton from \"../../atoms/specificButton/SpecificButton\";\nimport LoadingScreen from \"../LoadingScreen\";\n\nexport const FaceScan: React.FC<FaceScanProps> = ({\n userDetails,\n onComplete,\n onScanError,\n onRetry,\n config,\n}) => {\n const webcamRef = useRef<HTMLVideoElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const mediaRecorderRef = useRef<MediaRecorder | null>(null);\n const recordedBlobsRef = useRef<Blob[] | null>([]);\n const streamRef = useRef<MediaStream | null>(null);\n const [showLoader, setShowLoader] = useState(false);\n const [modelReady, setModelReady] = useState(false);\n const [isScanning, setIsScanning] = useState(false);\n const [showGuideCard, setShowGuideCard] = useState(true);\n const [videoLoading, setVideoLoading] = useState(false);\n const [videoError, setVideoError] = useState(false);\n const [hasError, setHasError] = useState(false);\n const [faceScanId, setFaceScanId] = useState(generateUuid());\n const resolvedConfig = useLocalConfig(config);\n const { email, gender, deviceFocalLength, shopDomain, callbackUrl } =\n userDetails;\n\n console.log(recordedBlobsRef.current, \"recoridng\");\n const supportedTypes = useMemo(\n () => videoTypes.filter((type) => MediaRecorder.isTypeSupported(type)),\n []\n );\n\n const stopRecording = useCallback(() => {\n console.log(\"Stopping recording...\");\n if (\n mediaRecorderRef.current &&\n mediaRecorderRef.current.state === \"recording\"\n ) {\n mediaRecorderRef.current.stop();\n }\n mediaRecorderRef.current = null;\n }, []);\n\n const uploadFinalVideo = async () => {\n setShowLoader(true);\n console.log(\"upload final video\");\n if (recordedBlobsRef.current) {\n if (recordedBlobsRef.current.length === 0) {\n console.error(\"No video data recorded\");\n setHasError(true);\n setShowLoader(false);\n\n return;\n }\n\n const videoFile = new File(\n recordedBlobsRef.current,\n `${faceScanId}.webm`,\n {\n type: \"video/webm\",\n }\n );\n const fileSize = videoFile.size;\n const fileSizeMB = (fileSize / (1024 * 1024)).toFixed(2);\n const estimatedDuration = Math.round(fileSize / 10000);\n const videoData = {\n video_size_mb: parseFloat(fileSizeMB),\n video_size_bytes: fileSize,\n blob_count: recordedBlobsRef.current.length,\n estimated_duration_seconds: estimatedDuration,\n };\n\n const metaData = [\n { gender: gender },\n { face_scan_id: faceScanId },\n {\n focal_length: `${deviceFocalLength}`,\n },\n { customer_store_url: shopDomain },\n { scan_type: \"face_scan\" },\n { callback_url: callbackUrl },\n ];\n console.log(metaData, \"metadata\");\n console.log(videoData);\n\n handleScanTimeCapture({\n eventName: `${shopDomain}/face_scan_meta_data`,\n faceScanId,\n email,\n data: JSON.stringify({ metaData, videoData }),\n });\n\n const uploadStartTime = Date.now();\n handleScanTimeCapture({\n eventName: `${shopDomain}/face_scan_upload_start`,\n faceScanId,\n email,\n startTime: uploadStartTime,\n });\n\n try {\n const res = await swan.fileUpload.faceScanFileUploader({\n file: videoFile,\n objectKey: faceScanId,\n email,\n arrayMetaData: metaData,\n contentType: videoFile.type,\n });\n\n const uploadEndTime = Date.now();\n const uploadDuration = uploadEndTime - uploadStartTime;\n\n handleScanTimeCapture({\n eventName: `${shopDomain}/face_scan_upload_complete`,\n faceScanId,\n email,\n completionTime: uploadEndTime,\n uploadDuration,\n });\n\n console.log(\"✅ Upload successful\", res);\n swan.measurement.handlFaceScaneSocket({\n faceScanId,\n onOpen: () => {\n handleWebSocketCapture({\n eventName: `${shopDomain}/webSocket`,\n faceScanID: faceScanId,\n connection: \"open\",\n type: \"faceScan_recommendation\",\n email,\n });\n console.log(\"websocket connect open\");\n },\n onError: (err) => {\n handleWebSocketCapture({\n eventName: `${shopDomain}/webSocket`,\n faceScanID: faceScanId,\n connection: \"error\",\n type: \"faceScan_recommendation\",\n email,\n });\n onError(err);\n },\n onSuccess: (data) => {\n handleWebSocketCapture({\n eventName: `${shopDomain}/webSocket`,\n faceScanID: faceScanId,\n connection: \"success\",\n type: \"faceScan_recommendation\",\n email,\n });\n onSuccess(data);\n },\n onClose: () => {\n console.log(\"websocket connect close\");\n handleWebSocketCapture({\n eventName: `${shopDomain}/webSocket`,\n faceScanID: faceScanId,\n connection: \"close\",\n type: \"faceScan_recommendation\",\n email,\n });\n },\n });\n } catch (error) {\n onError(error);\n }\n }\n };\n\n const startRecording = useCallback(() => {\n console.log(\"Starting recording for stages 1-3...\");\n const stream = webcamRef.current?.srcObject as MediaStream | null;\n if (!stream) return;\n\n // Create a canvas to normalize orientation\n const videoTrack = stream.getVideoTracks()[0];\n const settings = videoTrack.getSettings();\n const { width = videoConstraints.width, height = videoConstraints.height } =\n settings;\n\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n\n // Always force portrait (swap width/height if landscape)\n if (width > height) {\n canvas.width = height;\n canvas.height = width;\n } else {\n canvas.width = width;\n canvas.height = height;\n }\n\n const videoEl = document.createElement(\"video\");\n videoEl.srcObject = stream;\n videoEl.muted = true;\n videoEl.playsInline = true;\n videoEl.play();\n\n // Draw video frames onto canvas\n const drawFrame = () => {\n // Rotate if camera gives landscape frames\n if (width > height) {\n ctx?.save();\n ctx?.translate(canvas.width, 0);\n ctx?.rotate(Math.PI / 2);\n ctx?.drawImage(videoEl, 0, 0, height, width);\n ctx?.restore();\n } else {\n ctx?.drawImage(videoEl, 0, 0, width, height);\n }\n requestAnimationFrame(drawFrame);\n };\n drawFrame();\n\n // Capture portrait-normalized stream\n const canvasStream = canvas.captureStream(30); // 30fps\n const mediaRecorderOptions =\n supportedTypes.length > 0 ? { mimeType: supportedTypes[0] } : {};\n let mediaRecorder;\n\n try {\n mediaRecorder = new MediaRecorder(canvasStream, mediaRecorderOptions);\n } catch (e) {\n console.error(\"MediaRecorder init failed:\", e);\n setHasError(true);\n setShowLoader(false);\n return;\n }\n\n recordedBlobsRef.current = [];\n mediaRecorder.ondataavailable = (event) => {\n if (event?.data && event.data.size > 0 && recordedBlobsRef.current) {\n recordedBlobsRef.current.push(event.data);\n }\n };\n mediaRecorder.onstop = () => {\n console.log(\n \"Recording stopped, total blobs:\",\n recordedBlobsRef?.current?.length\n );\n uploadFinalVideo();\n };\n\n mediaRecorder.start(100); // 100ms chunks\n mediaRecorderRef.current = mediaRecorder;\n console.log(\"Recording started successfully (portrait normalized)\");\n }, [supportedTypes, uploadFinalVideo]);\n\n const {\n faceScanDetector,\n scanStage,\n setScanStage,\n resetScan,\n isModelLoaded,\n startScanSequence,\n } = useFaceScan({\n faceScanId,\n shopDomain,\n onScanComplete: () => {\n stopRecording();\n },\n onModelReady: () => {\n setModelReady(true);\n },\n onStartRecording: () => {\n console.log(\"Stage 0 completed - starting recording for stages 1-3\");\n startRecording();\n },\n });\n\n const startScan = useCallback(() => {\n if (!isModelLoaded) return;\n setScanStage(0);\n speechService.playAudio(\n `${voiceOverAssetsPath}face-scan-vos/Face-forward.mp3`\n );\n setTimeout(() => {\n setIsScanning(true);\n setTimeout(() => {\n startScanSequence();\n }, 200);\n }, 200);\n }, [setScanStage, setIsScanning, startScanSequence, isModelLoaded]);\n\n const resetScanState = useCallback(() => {\n setIsScanning(false);\n setShowGuideCard(true);\n stopRecording();\n resetScan();\n onRetry?.();\n recordedBlobsRef.current = [];\n if (mediaRecorderRef.current) {\n mediaRecorderRef.current = null;\n }\n setHasError(false);\n setScanStage(-1);\n setFaceScanId(generateUuid());\n if (webcamRef.current && streamRef.current) {\n webcamRef.current.srcObject = streamRef.current;\n console.log(\"Camera stream restored after reset\");\n }\n }, [\n setScanStage,\n setIsScanning,\n setShowGuideCard,\n stopRecording,\n resetScan,\n setFaceScanId,\n ]);\n\n const onError = (data: any) => {\n console.log(data, \"ws error\");\n onScanError?.(data);\n setHasError(true);\n setShowLoader(false);\n setShowGuideCard(false);\n handleScanTimeCapture({\n eventName: `${shopDomain}/faceScan`,\n faceScanId,\n status: \"failed\",\n email,\n data: JSON.stringify(data),\n });\n };\n\n const onSuccess = (data: any) => {\n console.log(data, \"ws success\");\n if (data && data?.resultType === \"intermediate\") {\n handleScanTimeCapture({\n eventName: `${shopDomain}/faceScan_success/intermediate`,\n faceScanId,\n status: \"success\",\n email,\n data: JSON.stringify(data),\n });\n }\n onComplete?.(data);\n };\n\n // Initialize webcam\n useEffect(() => {\n if (navigator.mediaDevices.getUserMedia) {\n navigator.mediaDevices\n .getUserMedia({ video: videoConstraints })\n .then((stream) => {\n streamRef.current = stream;\n if (webcamRef.current) {\n webcamRef.current.srcObject = stream;\n console.log(\"Webcam initialized\");\n }\n })\n .catch((err) => console.error(\"Error accessing webcam:\", err));\n }\n\n // Cleanup function\n return () => {\n if (streamRef.current) {\n streamRef.current.getTracks().forEach((track) => track.stop());\n }\n };\n }, []);\n\n // Face detection interval - only run when scanning is active\n useEffect(() => {\n if (!modelReady || !isScanning) return;\n\n const intervalId = setInterval(() => {\n faceScanDetector(webcamRef, canvasRef);\n }, 500);\n\n // eslint-disable-next-line consistent-return\n return () => clearInterval(intervalId);\n }, [faceScanDetector, modelReady, isScanning]);\n\n const getButtonText = () => {\n if (isScanning) return \"Reset\";\n if (!isModelLoaded) return \"Loading...\";\n return \"Start\";\n };\n\n console.log(\n \"Model ready:\",\n modelReady,\n \"Has error:\",\n hasError,\n \"Is scanning:\",\n isScanning,\n \"showLoader\",\n showLoader\n );\n\n useEffect(() => {\n setScanStage(-1);\n posthog.init(posthogPublicKey, { api_host: posthogPublicHost });\n posthog.capture(\"$pageview\");\n }, []);\n\n return (\n <>\n <audio\n id=\"audioElement\"\n crossOrigin=\"anonymous\"\n preload=\"auto\"\n style={{ position: \"absolute\", zIndex: -99999 }}\n src=\"\"\n />\n\n {showLoader && !hasError && (\n <div\n className=\"fixed z-[9] w-full h-full\"\n style={{ background: resolvedConfig?.style?.base?.primaryColor }}\n >\n {/* <Asset genderType={gender} /> */}\n <LoadingScreen />\n </div>\n )}\n\n {/* Always show camera view */}\n <div\n className=\"h-full flex-col relative w-full flex justify-center items-center text-center rounded-t-[20px]\"\n style={{ background: resolvedConfig?.style?.base?.backgroundColor }}\n >\n <div className=\"flex-1 w-full max-w-md overflow-hidden\">\n <div className=\"w-full h-full\">\n <video\n ref={webcamRef}\n autoPlay\n playsInline\n muted\n width={videoConstraints.width}\n height={videoConstraints.height}\n className=\"w-full h-full object-cover fixed left-0 top-0 z-0\"\n style={{ transform: \"scaleX(-1)\" }}\n />\n <canvas\n ref={canvasRef}\n width={videoConstraints.width}\n height={videoConstraints.height}\n style={{ transform: \"scaleX(-1)\", opacity: \"0\" }}\n />\n </div>\n </div>\n </div>\n\n {/* Error overlay */}\n {!showLoader && hasError && (\n <FaceScanErrorScreen\n loading={showLoader}\n resetScanState={resetScanState}\n config={resolvedConfig}\n />\n )}\n\n {/* Scan guide drawer - only show when scanning */}\n {showGuideCard && !hasError && !showLoader && (\n <Drawer\n open\n className=\"face-scan-small camera-draw\"\n anchor=\"bottom\"\n onClose={(event, reason) => {\n if (reason === \"backdropClick\") {\n return;\n }\n }}\n hideBackdrop\n >\n <FaceScanGuide\n stage={scanStage}\n videoLoading={videoLoading}\n setVideoLoading={setVideoLoading}\n videoError={videoError}\n setVideoError={setVideoError}\n gender={gender}\n config={resolvedConfig}\n >\n <div className=\"flex justify-center w-full p-[1rem]\">\n <SpecificButton\n disabled={showLoader || !isModelLoaded}\n className=\"!w-[60px] !h-[35px] !py-0 !px-0\"\n buttonText={getButtonText()}\n postfixIcon={isScanning ? <ArrowRight /> : \"\"}\n buttonFunc={isScanning ? resetScanState : startScan}\n resolvedConfig={resolvedConfig}\n />\n </div>\n </FaceScanGuide>\n </Drawer>\n )}\n </>\n );\n};\n"],"names":["FaceScanGuide","stage","videoLoading","setVideoLoading","videoError","setVideoError","children","gender","config","_jsxs","className","style","background","base","backgroundColor","_jsx","Header","noTitle","resolvedConfig","fontFamily","heading","headingFontFamily","fontSize","headingFontSize","color","headingColor","fontWeight","headingFontWeight","subheading","subheadingFontFamily","subheadingFontSize","subheadingColor","subheadingFontWeight","src","GenderType","Male","maleGlassesOffVideo","glassesOffVideo","autoPlay","loop","controls","muted","playsInline","onCanPlay","onLoadStart","onError","directionMessages","FACE_SCAN_HEADSHOT","male","forward","female","type","left","right","smile","FaceScanErrorScreen","resetScanState","loading","SpecificButton","disabled","buttonText","postfixIcon","ArrowRight","buttonFunc","preloadedDetector","useFaceScan","faceScanId","onValidPose","onScanComplete","onModelReady","onStartRecording","shopDomain","detectorRef","useRef","scanStage","setScanStage","useState","consecutiveValid","setConsecutiveValid","isModelLoaded","setIsModelLoaded","isScanningActive","setIsScanningActive","utteranceRef","scanStageRef","consecutiveValidRef","validityBufferRef","lastSpokenMessageRef","lastSpokenTimeRef","voiceEnabledRef","lastPoseTrackingTimeRef","isIOS","test","navigator","userAgent","platform","maxTouchPoints","useMemo","debouncedTrackPoseDetection","fn","delay","timer","args","clearTimeout","setTimeout","debounce","payload","posthog","capture","trackPoseDetection","useCallback","faceKeypoints","bodyKeypoints","headValid","bodyInvalid","consecutiveValidCount","now","Date","current","nose","leftEye","rightEye","leftMouth","rightMouth","faceKeypointScores","map","kp","filter","score","avgFaceScore","length","reduce","a","b","bodyKeypointScores","avgBodyScore","eyeDistance","Math","abs","noseToRight","noseToLeft","expectedPosition","timestamp","toISOString","data","JSON","stringify","parseFloat","toFixed","faceKeypointsDetected","bodyKeypointsDetected","noseToRightRatio","noseToLeftRatio","noseScore","leftEyeScore","rightEyeScore","leftMouthScore","rightMouthScore","error","console","warn","startScanSequence","stageName","initializeModels","async","log","setBackend","ready","detector","poseDetection","createDetector","SupportedModels","BlazePose","runtime","modelType","dummyCanvas","document","createElement","width","videoConstraints","height","ctx","getContext","fillStyle","fillRect","estimatePoses","e","preloadDetector","err","useEffect","dispose","disposeModelAndTf","faceScanDetector","webcamRef","canvasRef","poses","keypoints","forEach","landmark","idx","point","x","y","push","clearRect","progress","min","radius","beginPath","strokeStyle","lineWidth","arc","PI","stroke","eyesToMouthVerticalDistance","faceTiltedness","faceIsTilted","faceIsVertical","frontalFace","fullLeft","fullRight","isHeadInPosition","slice","p","shift","Boolean","nextValid","max","nextStage","speechService","playAudio","voiceOverAssetsPath","completedStage","totalStages","nextSound","resetScan","speechSynthesis","cancel","enableVoiceCommands","window","silentUtterance","SpeechSynthesisUtterance","volume","speak","FaceScan","userDetails","onComplete","onScanError","onRetry","mediaRecorderRef","recordedBlobsRef","streamRef","showLoader","setShowLoader","modelReady","setModelReady","isScanning","setIsScanning","showGuideCard","setShowGuideCard","hasError","setHasError","setFaceScanId","generateUuid","useLocalConfig","email","deviceFocalLength","callbackUrl","supportedTypes","videoTypes","MediaRecorder","isTypeSupported","stopRecording","state","stop","uploadFinalVideo","videoFile","File","fileSize","size","fileSizeMB","estimatedDuration","round","videoData","video_size_mb","video_size_bytes","blob_count","estimated_duration_seconds","metaData","face_scan_id","focal_length","customer_store_url","scan_type","callback_url","handleScanTimeCapture","eventName","uploadStartTime","startTime","res","swan","fileUpload","faceScanFileUploader","file","objectKey","arrayMetaData","contentType","uploadEndTime","completionTime","uploadDuration","measurement","handlFaceScaneSocket","onOpen","handleWebSocketCapture","faceScanID","connection","onSuccess","onClose","startRecording","stream","srcObject","settings","getVideoTracks","getSettings","canvas","videoEl","play","drawFrame","save","translate","rotate","drawImage","restore","requestAnimationFrame","canvasStream","captureStream","mediaRecorderOptions","mimeType","mediaRecorder","ondataavailable","event","onstop","start","startScan","status","resultType","mediaDevices","getUserMedia","video","then","catch","getTracks","track","intervalId","setInterval","clearInterval","init","posthogPublicKey","api_host","posthogPublicHost","id","crossOrigin","preload","position","zIndex","primaryColor","LoadingScreen","ref","transform","opacity","Drawer","open","anchor","reason","hideBackdrop"],"mappings":"qjBAOA,SAASA,GAAcC,MACtBA,EAAKC,aACLA,EAAYC,gBACZA,EAAeC,WACfA,EAAUC,cACVA,EAAaC,SACbA,EAAQC,OACRA,EAAMC,OACNA,IAEA,OAAc,IAAVP,EAEFQ,EAAA,MAAA,CAAKC,UAAU,sCAAsCC,MAAO,CAAEC,WAAYJ,GAAQG,OAAOE,MAAMC,iBAAiBR,SAAA,CAC/GG,SAAKC,UAAU,SAAQJ,SAAA,CACtBS,EAACC,EAAM,CAACC,SAAO,EAACC,eAAgBV,IAChCC,EAAA,KAAA,CACCE,MAAO,CACNQ,WAAYX,GAAQG,OAAOS,SAASC,mBAAqB,wBACzDC,SAAUd,GAAQG,OAAOS,SAASG,iBAAmB,OACrDC,MAAOhB,GAAQG,OAAOS,SAASK,cAAgB,OAC/CC,WAAYlB,GAAQG,OAAOS,SAASO,mBAAqB,UACzDrB,SAAA,CAAA,uCAEoC,OAErCJ,GACAa,EAAA,MAAA,CACCL,UAAU,wCACVC,MAAO,CACNQ,WAAYX,GAAQG,OAAOiB,YAAYC,sBAAwB,sBAC/DP,SAAUd,GAAQG,OAAOiB,YAAYE,oBAAsB,OAC3DN,MAAOhB,GAAQG,OAAOiB,YAAYG,iBAAmB,UACrDL,WAAYlB,GAAQG,OAAOiB,YAAYI,sBAAwB,UAC/D1B,SAAA,qBAKFF,GACAW,EAAA,MAAA,CACCL,UAAU,oBACVC,MAAO,CACNQ,WAAYX,GAAQG,OAAOiB,YAAYC,sBAAwB,sBAC/DP,SAAUd,GAAQG,OAAOiB,YAAYE,oBAAsB,OAC3DN,MAAO,UACPE,WAAYlB,GAAQG,OAAOiB,YAAYI,sBAAwB,UAC/D1B,SAAA,6EAKDF,GACDW,EAAA,MAAA,CAAKL,UAAW,8BACfK,EAAA,QAAA,CACCkB,IAAK1B,IAAW2B,EAAWC,KAAOC,EAAsBC,EACxDC,UAAQ,EACRC,MAAI,EACJC,UAAU,EACVC,OAAK,EACLC,aAAW,EACXhC,UAAU,2CACViC,UAAW,IAAMxC,GAAgB,GACjCyC,YAAa,IAAMzC,GAAgB,GACnC0C,QAAS,IAAMxC,GAAc,GAAK,aACvB,mFAKdC,KAMHS,EAAA,MAAA,CAAKL,UAAU,sCAAsCC,MAAO,CAAEC,WAAYJ,GAAQG,OAAOE,MAAMC,iBAAiBR,SAC/GG,EAAA,MAAA,CAAKC,UAAU,SAAQJ,SAAA,CACtBS,EAACC,EAAM,CAACC,SAAO,EAACC,eAAgBV,IAChCO,EAAA,KAAA,CACCL,UAAU,cACVC,MAAO,CACNQ,WAAYX,GAAQG,OAAOS,SAASC,mBAAqB,wBACzDC,SAAUd,GAAQG,OAAOS,SAASG,iBAAmB,OACrDC,MAAOhB,GAAQG,OAAOS,SAASK,cAAgB,OAC/CC,WAAYlB,GAAQG,OAAOS,SAASO,mBAAqB,UACzDrB,SAEAwC,EAAkB7C,KAGpBQ,EAAA,MAAA,CAAKC,UAAU,+DAA8DJ,SAAA,CAC5ES,EAAA,MAAA,CAAKL,UAAW,8BAAwC,IAAVT,EAAc,cAAgB,sBAAqBK,SAChGS,WAAOL,UAAU,2CAA2C+B,OAAK,EAACF,MAAI,EAACD,UAAQ,EAACI,aAAW,EAAApC,SAC1FS,EAAA,SAAA,CAAQkB,IAAK1B,IAAW2B,EAAWC,KAAOY,EAAmBC,KAAKC,QAAUF,EAAmBG,OAAOD,QAASE,KAAK,kBAGtHpC,EAAA,MAAA,CAAKL,UAAW,8BAAwC,IAAVT,EAAc,cAAgB,sBAAqBK,SAChGS,EAAA,QAAA,CAAOL,UAAU,2CAA2C+B,OAAK,EAACF,MAAI,EAACD,UAAQ,EAACI,aAAW,EAAApC,SAC1FS,EAAA,SAAA,CAAQkB,IAAK1B,IAAW2B,EAAWC,KAAOY,EAAmBC,KAAKI,KAAOL,EAAmBG,OAAOE,KAAMD,KAAK,kBAGhHpC,EAAA,MAAA,CAAKL,UAAW,8BAAwC,IAAVT,EAAc,cAAgB,sBAAqBK,SAChGS,EAAA,QAAA,CAAOL,UAAU,2CAA2C+B,OAAK,EAACF,MAAI,EAACD,UAAQ,EAACI,aAAW,EAAApC,SAC1FS,EAAA,SAAA,CAAQkB,IAAK1B,IAAW2B,EAAWC,KAAOY,EAAmBC,KAAKK,MAAQN,EAAmBG,OAAOG,MAAOF,KAAK,kBAGlHpC,EAAA,MAAA,CAAKL,UAAW,8BAAwC,IAAVT,EAAc,cAAgB,sBAAqBK,SAChGS,EAAA,QAAA,CAAOL,UAAU,2CAA2C+B,OAAK,EAACF,MAAI,EAACD,UAAQ,EAACI,aAAW,EAAApC,SAC1FS,YAAQkB,IAAK1B,IAAW2B,EAAWC,KAAOY,EAAmBC,KAAKM,MAAQP,EAAmBG,OAAOI,MAAOH,KAAK,yBAOvH,CCtHA,SAASI,GAAoBC,eAAEA,EAAcC,QAAEA,EAAOjD,OAAEA,IACvD,OACCO,SAAKL,UAAU,kFAAkFC,MAAO,CAAEC,WAAYJ,GAAQG,OAAOE,MAAMC,iBAAiBR,SAC3JG,EAAA,MAAA,CAAKC,UAAU,0DAAyDJ,SAAA,CACvES,EAACC,EAAM,CAACC,WAAQC,eAAgBV,IAChCC,SAAKC,UAAU,oCAAmCJ,SAAA,CACjDS,EAAA,KAAA,CACCL,UAAU,uCACVC,MAAO,CACNQ,WAAYX,GAAQG,OAAOS,SAASC,mBAAqB,wBACzDC,SAAUd,GAAQG,OAAOS,SAASG,iBAAmB,OACrDC,MAAOhB,GAAQG,OAAOS,SAASK,cAAgB,OAC/CC,WAAYlB,GAAQG,OAAOS,SAASO,mBAAqB,UACzDrB,SAAA,qBAIFS,EAAA,IAAA,CACCL,UAAU,cACVC,MAAO,CACNQ,WAAYX,GAAQG,OAAOiB,YAAYC,sBAAwB,sBAC/DP,SAAUd,GAAQG,OAAOiB,YAAYE,oBAAsB,OAC3DN,MAAOhB,GAAQG,OAAOiB,YAAYG,iBAAmB,UACrDL,WAAYlB,GAAQG,OAAOiB,YAAYI,sBAAwB,UAC/D1B,SAAA,2CAIFS,EAAC2C,EAAc,CACdC,SAAUF,EACV/C,UAAU,iEACVkD,WAAW,aACXC,YAAa9C,EAAC+C,EAAU,CAAA,GACxBC,WAAY,IAAMP,IAClBtC,eAAgBV,WAMtB,CCKA,IAAIwD,EAAuD,KAmC3D,SAASC,GAAYC,WACnBA,EAAUC,YACVA,EAAWC,eACXA,EAAcC,aACdA,EAAYC,iBACZA,EAAgBC,WAChBA,IASA,MAAMC,EAAcC,EAA0C,OACvDC,EAAWC,GAAgBC,EAAS,IACpCC,EAAkBC,GAAuBF,EAAS,IAClDG,EAAeC,GAAoBJ,GAAS,IAC5CK,EAAkBC,GAAuBN,GAAS,GACnDO,EAAeV,EAAO,MACtBW,EAAeX,EAAOC,GACtBW,EAAsBZ,EAAOI,GAC7BS,EAAoBb,EAAkB,IACtCc,EAAuBd,EAAO,IAC9Be,EAAoBf,EAAO,GAC3BgB,EAAkBhB,GAAO,GAKzBiB,EAA0BjB,EAAO,GAGjCkB,EACJ,mBAAmBC,KAAKC,UAAUC,YACV,aAAvBD,UAAUE,UAA2BF,UAAUG,eAAiB,EAE7DlD,EAAoBmD,EACxB,IAAM,CACJ,aACA,uBACA,wBACA,sBAEF,IAIIC,EAA8BD,EAClC,IA7FJ,SAAkBE,EAA4BC,GAC5C,IAAIC,EACJ,MAAO,IAAIC,KACLD,GAAOE,aAAaF,GACxBA,EAAQG,WAAW,IAAML,KAAMG,GAAOF,GAE1C,CAwFMK,CAAUC,IACRC,EAAQC,QAAQ,GAAGrC,6BAAuCmC,IACzD,KACL,CAACnC,IAIGsC,EAAqBC,EACzB,CACEC,EACAC,EACA/G,EACAgH,EACAC,EACAC,KAEA,MAAMC,EAAMC,KAAKD,MAGjB,KAAIA,EAAM1B,EAAwB4B,QAxCP,KAwC3B,CAIA5B,EAAwB4B,QAAUF,EAElC,IAEE,MAAMG,EAAOR,EAAc,IAAM,CAAC,EAAG,EAAG,GAClCS,EAAUT,EAAc,IAAM,CAAC,EAAG,EAAG,GACrCU,EAAWV,EAAc,IAAM,CAAC,EAAG,EAAG,GACtCW,EAAYX,EAAc,IAAM,CAAC,EAAG,EAAG,GACvCY,EAAaZ,EAAc,KAAO,CAAC,EAAG,EAAG,GAGzCa,EAAqBb,EACxBc,IAAKC,GAAcA,EAAG,IACtBC,OAAQC,GAAkBA,EAAQ,GAC/BC,EACJL,EAAmBM,OAAS,EACxBN,EAAmBO,OAAO,CAACC,EAAGC,IAAMD,EAAIC,EAAG,GAC3CT,EAAmBM,OACnB,EAEAI,EAAqBtB,EACxBa,IAAKC,GAAcA,EAAG,IACtBC,OAAQC,GAAkBA,EAAQ,GAC/BO,EACJD,EAAmBJ,OAAS,EACxBI,EAAmBH,OAAO,CAACC,EAAWC,IAAcD,EAAIC,EAAG,GAC3DC,EAAmBJ,OACnB,EAGAM,EAAcC,KAAKC,IAAIlB,EAAQ,GAAKC,EAAS,IAC7CkB,EAAcpB,EAAK,GAAKE,EAAS,GACjCmB,EAAapB,EAAQ,GAAKD,EAAK,GAGrC,IAAIsB,EAAmB,UACT,IAAV5I,EAAa4I,EAAmB,OACjB,IAAV5I,EAAa4I,EAAmB,QACtB,IAAV5I,IAAa4I,EAAmB,iBAEzC,MAAMnC,EAAU,CACdxC,aACAjE,QACA6I,WAAW,IAAIzB,MAAO0B,cACtBC,KAAMC,KAAKC,UAAU,CACnBjC,YACAC,cACArC,iBAAkBsC,EAClBc,aAAckB,WAAWlB,EAAamB,QAAQ,IAC9Cb,aAAcY,WAAWZ,EAAaa,QAAQ,IAC9CC,sBAAuBzB,EAAmBM,OAC1CoB,sBAAuBhB,EAAmBJ,OAC1CM,YAAaW,WAAWX,EAAYY,QAAQ,IAC5CG,iBAAkBJ,YACfR,EAAcH,GAAaY,QAAQ,IAEtCI,gBAAiBL,YAAYP,EAAaJ,GAAaY,QAAQ,IAC/DK,UAAWN,WAAW5B,EAAK,GAAG6B,QAAQ,IACtCM,aAAcP,WAAW3B,EAAQ,GAAG4B,QAAQ,IAC5CO,cAAeR,WAAW1B,EAAS,GAAG2B,QAAQ,IAC9CQ,eAAgBT,WAAWzB,EAAU,GAAG0B,QAAQ,IAChDS,gBAAiBV,WAAWxB,EAAW,GAAGyB,QAAQ,IAClDP,sBAIJ3C,EAA4BQ,EAC9B,CAAE,MAAOoD,GACPC,QAAQC,KAAK,kCAAmCF,EAClD,CAvEA,GAyEF,CAAC5F,EAAYgC,IAiBT+D,EAAoB,KAExBtD,EAAQC,QAAQ,GAAGrC,gCAA0C,CAC3DL,aACAjE,MAAOmF,EAAakC,QACpBwB,WAAW,IAAIzB,MAAO0B,cACtBmB,UAAWpH,EAAkBsC,EAAakC,WAI5Cd,WAAW,KACTtB,GAAoB,IACnB,OAwMCiF,EAAmBC,UACvB,IACEL,QAAQM,IAAI,oCACNC,EAAW,eACXC,IACNR,QAAQM,IAAI,6CACZ,MAAMG,OA7ZmBJ,WAC7B,GAAIpG,EAAmB,OAAOA,EAE9B,MAAMwG,QAAiBC,EAAcC,eACnCD,EAAcE,gBAAgBC,UAC9B,CACEC,QAAS,OACTC,UAAW,SAKTC,EAAcC,SAASC,cAAc,UAC3CF,EAAYG,MAAQC,EAAiBD,MACrCH,EAAYK,OAASD,EAAiBC,OACtC,MAAMC,EAAMN,EAAYO,WAAW,MAC/BD,IACFA,EAAIE,UAAY,QAChBF,EAAIG,SAAS,EAAG,EAAGT,EAAYG,MAAOH,EAAYK,SAEpD,UACQZ,EAASiB,cAAcV,EAC/B,CAAE,MAAOW,GAEP3B,QAAQM,IAAIqB,EAAG,qBACjB,CAGA,OADA1H,EAAoBwG,EACbA,GAiYoBmB,GACvB5B,QAAQM,IAAIG,EAAU,YAEtBhG,EAAY8C,QAAUkD,EACtBT,QAAQM,IAAI,4CACZrF,GAAiB,GACjBX,KACF,CAAE,MAAOuH,GACP7B,QAAQD,MAAM,gCAAiC8B,EACjD,GA8DF,OAhBAC,EAAU,KAER1B,IACO,KA9CiBC,WACxB,IACEpF,GAAiB,GACbR,EAAY8C,gBACR9C,EAAY8C,QAAQwE,UAC1BtH,EAAY8C,QAAU,KAE1B,CAAE,MAAOsE,GACP7B,QAAQD,MAAM,6BAA8B8B,EAC9C,SACEjH,EAAa,GACbG,EAAoB,GACpBM,EAAakC,QAAU,EACvBjC,EAAoBiC,QAAU,EAC9BhC,EAAkBgC,QAAU,EAC9B,GAgCEyE,KAED,IAEHF,EAAU,KACRzG,EAAakC,QAAU5C,GACtB,CAACA,IAEJmH,EAAU,KACRxG,EAAoBiC,QAAUzC,GAC7B,CAACA,IAEG,CACLmH,iBA3NuB5B,MACvB6B,EACAC,KAEA,GACG1H,EAAY8C,SACZ2E,EAAU3E,SACVvC,GACAE,EAIH,IACE,MAAMkH,QAAc3H,EAAY8C,QAAQmE,cAAcQ,EAAU3E,SAChE,GAAI6E,EAAMjE,OAAS,EAAG,CACpB,MAAMnB,EAAyB,GACzBC,EAAyB,GAS/B,GARAmF,EAAM,GAAGC,UAAUC,QAAQ,CAACC,EAAUC,KACpC,MAAMvE,EAAQsE,EAAStE,OAAS,EAC1BwE,EAAe,CAACF,EAASG,IAAM,EAAGH,EAASI,IAAM,EAAG1E,GACtDuE,GAAO,GAAIxF,EAAc4F,KAAKH,GAC7BxF,EAAc2F,KAAKH,KAItBN,GAAW5E,QAAS,CACtB,MAAM+D,EAAMa,EAAU5E,QAAQgE,WAAW,OACnCJ,MAAEA,EAAKE,OAAEA,GAAWc,EAAU5E,QACpC,GAAI+D,EAAK,CACPA,EAAIuB,UAAU,EAAG,EAAG1B,EAAOE,GAE3B,MAAMyB,EAAWpE,KAAKqE,IACpBzH,EAAoBiC,QA7OC,EA8OrB,GAEIyF,EAAkB,IAAT3B,EACfC,EAAI2B,YACJ3B,EAAI4B,YAAc,UAClB5B,EAAI6B,UAAY,EAChB7B,EAAI8B,IACFjC,EAAQ,EACRE,EAAS,EACT2B,EAAS,IACRtE,KAAK2E,GAAK,GACV3E,KAAK2E,GAAK,EAAe,EAAXP,EAAepE,KAAK2E,IAErC/B,EAAIgC,QACN,CACF,CAEA,MAAMpG,EA1Ga,EAAChH,EAAemM,KAQvC,GACEA,EARW,GAQK,GAFD,IAGfA,EARc,GAQK,GAHJ,IAIfA,EARe,GAQK,GAJL,GAMf,OAAO,EAGT,MAAMkB,EACJ,IAAOlB,EAZU,IAYY,GAAKA,EAblB,GAauC,IACvD,IAAOA,EAfQ,GAeY,GAAKA,EAhBlB,GAgBqC,IAE/CmB,GACH,IAAOnB,EAhBS,IAgBa,GAAKA,EAjBnB,GAiBwC,IACtDA,EArBS,GAqBO,IAClBkB,EAEIE,EAAeD,EAAiB,IAAOA,EAAiB,GAExDE,EACJhF,KAAKC,IAAI0D,EA1BK,GA0Bc,GAAKA,EAzBlB,GAyBsC,IACnD,GAAMkB,GACR7E,KAAKC,IAAI0D,EA1BO,GA0Bc,GAAKA,EAzBlB,IAyBwC,IACvD,GAAMkB,EAEJ9E,EAAc4D,EA/BJ,GA+BuB,GAAKA,EA9B3B,GA8B+C,GAC1DzD,EAAcyD,EAjCP,GAiCuB,GAAKA,EA/BxB,GA+B4C,GAGvDsB,EACJ/E,EAAc,GAAMH,GAAeG,EAAc,GAAMH,EACnDmF,EAAWhF,EAAc,IAAOH,EAChCoF,EALaxB,EAjCH,GAiCsB,GAAKA,EAlC9B,GAkC8C,GAK5B,IAAO5D,EAEtC,OAAQvI,GACN,KAAK,EAML,KAAK,EACH,OAAQuN,GAAgBC,GAAkBC,EAL5C,KAAK,EACH,OAAQF,GAAgBC,GAAkBE,EAC5C,KAAK,EACH,OAAQH,GAAgBC,GAAkBG,EAG5C,QACE,OAAO,IAsDWC,CAAiBzI,EAAakC,QAASP,GACnDG,EACJF,EAAc8G,MAAM,GAAG/F,OAAQgG,GAAMA,EAAE,GAAK,IAAK7F,QAAU,EAE7D5C,EAAkBgC,QAAQqF,KAAK1F,GAAaC,GACxC5B,EAAkBgC,QAAQY,OArQL,IAsQvB5C,EAAkBgC,QAAQ0G,QAI5B,GADmB1I,EAAkBgC,QAAQS,OAAOkG,SAAS/F,QAC3C,EAAG,CACnB,MAAMgG,EAAY7I,EAAoBiC,QAAU,EAChDxC,EAAoBoJ,EACtB,MACEpJ,EAAoB2D,KAAK0F,IAAI,EAAG9I,EAAoBiC,QAAU,IAIhE,GAAIjC,EAAoBiC,SAjRG,EAiRkC,CAC3DnD,MAEA,MAAMiK,EAAYhJ,EAAakC,QAAU,EACzCxC,EAAoB,GACpBQ,EAAkBgC,QAAU,GAC5B3C,EAAayJ,GACblJ,GAAoB,GAGpB,WAoBE,SAnBMmJ,EAAcC,UAClB,GAAGC,mCAGL5H,EAAQC,QACN,GAAGrC,wCACH,CACEL,aACAsK,eAAgBpJ,EAAakC,QAC7B8G,YACAtF,WAAW,IAAIzB,MAAO0B,cACtB0F,YAtSO,EAuSPvE,UAAWpH,EAAkBsC,EAAakC,WAI5B,IAAd8G,GAAmB9J,GACrBA,IAEE8J,GA9SO,EA+SThK,UACK,CAEL,IAAIsK,EAAY,KAChB,OAAQN,GACN,KAAK,EACHM,EAAY,WACZ,MACF,KAAK,EACHA,EAAY,YACZ,MACF,KAAK,EACHA,EAAY,YACZ,MACF,QACEA,EAAY,KAEZA,SACIL,EAAcC,UAClB,GAAGC,kBAAoCG,KAG3CzE,GACF,CACD,EA7CD,EA8CF,CAGApD,EACEE,EACAC,EACA5B,EAAakC,QACbL,EACAC,EACA7B,EAAoBiC,QAExB,CACF,CAAE,MAAOsE,GACP7B,QAAQD,MAAM,wBAAyB8B,EACzC,GAkFAlH,YACAC,eACAE,mBACAE,gBACA4J,UA/CgB,KAEhBhI,EAAQC,QAAQ,GAAGrC,oBAA8B,CAC/CL,aACAjE,MAAOmF,EAAakC,QACpBwB,WAAW,IAAIzB,MAAO0B,cACtBmB,UAAWpH,EAAkBsC,EAAakC,SAC1CzC,iBAAkBQ,EAAoBiC,UAGxC3C,EAAa,GACbG,EAAoB,GACpBI,GAAoB,GACpBE,EAAakC,QAAU,EACvBjC,EAAoBiC,QAAU,EAC9BhC,EAAkBgC,QAAU,GAC5B/B,EAAqB+B,QAAU,GAC/B9B,EAAkB8B,QAAU,EAC5B7B,EAAgB6B,SAAU,EACtBnC,EAAamC,UACfsH,gBAAgBC,SAChB1J,EAAamC,QAAU,OA2BzBwH,oBArT0B,KAI1B,GAHArJ,EAAgB6B,SAAU,EAGtB3B,GAAS,oBAAqBoJ,OAAQ,CAExC,MAAMC,EAAkB,IAAIC,yBAAyB,IACrDD,EAAgBE,OAAS,EACzBN,gBAAgBO,MAAMH,EACxB,GA6SA/E,oBACAhF,mBAEJ,CCvgBO,MAAMmK,EAAoC,EAC/CC,cACAC,aACAC,cACAC,UACAhP,aAEA,MAAMyL,EAAYxH,EAAgC,MAC5CyH,EAAYzH,EAAiC,MAC7CgL,EAAmBhL,EAA6B,MAChDiL,EAAmBjL,EAAsB,IACzCkL,EAAYlL,EAA2B,OACtCmL,EAAYC,GAAiBjL,GAAS,IACtCkL,EAAYC,GAAiBnL,GAAS,IACtCoL,EAAYC,GAAiBrL,GAAS,IACtCsL,EAAeC,GAAoBvL,GAAS,IAC5C1E,EAAcC,GAAmByE,GAAS,IAC1CxE,EAAYC,GAAiBuE,GAAS,IACtCwL,EAAUC,GAAezL,GAAS,IAClCV,EAAYoM,GAAiB1L,EAAS2L,KACvCrP,EAAiBsP,EAAehQ,IAChCiQ,MAAEA,GAAKlQ,OAAEA,GAAMmQ,kBAAEA,GAAiBnM,WAAEA,GAAUoM,YAAEA,IACpDtB,EAEFtF,QAAQM,IAAIqF,EAAiBpI,QAAS,aACtC,MAAMsJ,GAAiB3K,EACrB,IAAM4K,EAAW9I,OAAQ5E,GAAS2N,cAAcC,gBAAgB5N,IAChE,IAGI6N,GAAgBlK,EAAY,KAChCiD,QAAQM,IAAI,yBAEVoF,EAAiBnI,SACkB,cAAnCmI,EAAiBnI,QAAQ2J,OAEzBxB,EAAiBnI,QAAQ4J,OAE3BzB,EAAiBnI,QAAU,MAC1B,IAEG6J,GAAmB/G,UAGvB,GAFAyF,GAAc,GACd9F,QAAQM,IAAI,sBACRqF,EAAiBpI,QAAS,CAC5B,GAAwC,IAApCoI,EAAiBpI,QAAQY,OAK3B,OAJA6B,QAAQD,MAAM,0BACduG,GAAY,QACZR,GAAc,GAKhB,MAAMuB,EAAY,IAAIC,KACpB3B,EAAiBpI,QACjB,GAAGpD,SACH,CACEf,KAAM,eAGJmO,EAAWF,EAAUG,KACrBC,GAAcF,EAAQ,SAAkBlI,QAAQ,GAChDqI,EAAoBhJ,KAAKiJ,MAAMJ,EAAW,KAC1CK,EAAY,CAChBC,cAAezI,WAAWqI,GAC1BK,iBAAkBP,EAClBQ,WAAYpC,EAAiBpI,QAAQY,OACrC6J,2BAA4BN,GAGxBO,EAAW,CACf,CAAEzR,OAAQA,IACV,CAAE0R,aAAc/N,GAChB,CACEgO,aAAc,GAAGxB,MAEnB,CAAEyB,mBAAoB5N,IACtB,CAAE6N,UAAW,aACb,CAAEC,aAAc1B,KAElB5G,QAAQM,IAAI2H,EAAU,YACtBjI,QAAQM,IAAIsH,GAEZW,EAAsB,CACpBC,UAAW,GAAGhO,yBACdL,aACAuM,SACAzH,KAAMC,KAAKC,UAAU,CAAE8I,WAAUL,gBAGnC,MAAMa,EAAkBnL,KAAKD,MAC7BkL,EAAsB,CACpBC,UAAW,GAAGhO,4BACdL,aACAuM,SACAgC,UAAWD,IAGb,IACE,MAAME,QAAYC,EAAKC,WAAWC,qBAAqB,CACrDC,KAAM1B,EACN2B,UAAW7O,EACXuM,SACAuC,cAAehB,EACfiB,YAAa7B,EAAUjO,OAGnB+P,EAAgB7L,KAAKD,MAG3BkL,EAAsB,CACpBC,UAAW,GAAGhO,+BACdL,aACAuM,SACA0C,eAAgBD,EAChBE,eAPqBF,EAAgBV,IAUvCzI,QAAQM,IAAI,sBAAuBqI,GACnCC,EAAKU,YAAYC,qBAAqB,CACpCpP,aACAqP,OAAQ,KACNC,EAAuB,CACrBjB,UAAW,GAAGhO,eACdkP,WAAYvP,EACZwP,WAAY,OACZvQ,KAAM,0BACNsN,WAEF1G,QAAQM,IAAI,2BAEdxH,QAAU+I,IACR4H,EAAuB,CACrBjB,UAAW,GAAGhO,eACdkP,WAAYvP,EACZwP,WAAY,QACZvQ,KAAM,0BACNsN,WAEF5N,GAAQ+I,IAEV+H,UAAY3K,IACVwK,EAAuB,CACrBjB,UAAW,GAAGhO,eACdkP,WAAYvP,EACZwP,WAAY,UACZvQ,KAAM,0BACNsN,WAEFkD,GAAU3K,IAEZ4K,QAAS,KACP7J,QAAQM,IAAI,2BACZmJ,EAAuB,CACrBjB,UAAW,GAAGhO,eACdkP,WAAYvP,EACZwP,WAAY,QACZvQ,KAAM,0BACNsN,aAIR,CAAE,MAAO3G,GACPjH,GAAQiH,EACV,CACF,GAGI+J,GAAiB/M,EAAY,KACjCiD,QAAQM,IAAI,wCACZ,MAAMyJ,EAAS7H,EAAU3E,SAASyM,UAClC,IAAKD,EAAQ,OAGb,MACME,EADaF,EAAOG,iBAAiB,GACfC,eACtBhJ,MAAEA,EAAQC,EAAiBD,MAAKE,OAAEA,EAASD,EAAiBC,QAChE4I,EAEIG,EAASnJ,SAASC,cAAc,UAChCI,EAAM8I,EAAO7I,WAAW,MAG1BJ,EAAQE,GACV+I,EAAOjJ,MAAQE,EACf+I,EAAO/I,OAASF,IAEhBiJ,EAAOjJ,MAAQA,EACfiJ,EAAO/I,OAASA,GAGlB,MAAMgJ,EAAUpJ,SAASC,cAAc,SACvCmJ,EAAQL,UAAYD,EACpBM,EAAQ3R,OAAQ,EAChB2R,EAAQ1R,aAAc,EACtB0R,EAAQC,OAGR,MAAMC,EAAY,KAEZpJ,EAAQE,GACVC,GAAKkJ,OACLlJ,GAAKmJ,UAAUL,EAAOjJ,MAAO,GAC7BG,GAAKoJ,OAAOhM,KAAK2E,GAAK,GACtB/B,GAAKqJ,UAAUN,EAAS,EAAG,EAAGhJ,EAAQF,GACtCG,GAAKsJ,WAELtJ,GAAKqJ,UAAUN,EAAS,EAAG,EAAGlJ,EAAOE,GAEvCwJ,sBAAsBN,IAExBA,IAGA,MAAMO,EAAeV,EAAOW,cAAc,IACpCC,EACJnE,GAAe1I,OAAS,EAAI,CAAE8M,SAAUpE,GAAe,IAAO,CAAA,EAChE,IAAIqE,EAEJ,IACEA,EAAgB,IAAInE,cAAc+D,EAAcE,EAClD,CAAE,MAAOrJ,GAIP,OAHA3B,QAAQD,MAAM,6BAA8B4B,GAC5C2E,GAAY,QACZR,GAAc,EAEhB,CAEAH,EAAiBpI,QAAU,GAC3B2N,EAAcC,gBAAmBC,IAC3BA,GAAOnM,MAAQmM,EAAMnM,KAAKuI,KAAO,GAAK7B,EAAiBpI,SACzDoI,EAAiBpI,QAAQqF,KAAKwI,EAAMnM,OAGxCiM,EAAcG,OAAS,KACrBrL,QAAQM,IACN,kCACAqF,GAAkBpI,SAASY,QAE7BiJ,MAGF8D,EAAcI,MAAM,KACpB5F,EAAiBnI,QAAU2N,EAC3BlL,QAAQM,IAAI,yDACX,CAACuG,GAAgBO,MAEdnF,iBACJA,GAAgBtH,UAChBA,GAASC,aACTA,GAAYgK,UACZA,GAAS5J,cACTA,GAAakF,kBACbA,IACEhG,EAAY,CACdC,aACAK,cACAH,eAAgB,KACd4M,MAEF3M,aAAc,KACZ0L,GAAc,IAEhBzL,iBAAkB,KAChByF,QAAQM,IAAI,yDACZwJ,QAIEyB,GAAYxO,EAAY,KACvB/B,KACLJ,GAAa,GACb0J,EAAcC,UACZ,GAAGC,mCAEL/H,WAAW,KACTyJ,GAAc,GACdzJ,WAAW,KACTyD,MACC,MACF,OACF,CAACtF,GAAcsL,EAAehG,GAAmBlF,KAE9CvB,GAAiBsD,EAAY,KACjCmJ,GAAc,GACdE,GAAiB,GACjBa,KACArC,KACAa,MACAE,EAAiBpI,QAAU,GACvBmI,EAAiBnI,UACnBmI,EAAiBnI,QAAU,MAE7B+I,GAAY,GACZ1L,IAAa,GACb2L,EAAcC,KACVtE,EAAU3E,SAAWqI,EAAUrI,UACjC2E,EAAU3E,QAAQyM,UAAYpE,EAAUrI,QACxCyC,QAAQM,IAAI,wCAEb,CACD1F,GACAsL,EACAE,EACAa,GACArC,GACA2B,IAGIzN,GAAWmG,IACfe,QAAQM,IAAIrB,EAAM,YAClBuG,IAAcvG,GACdqH,GAAY,GACZR,GAAc,GACdM,GAAiB,GACjBmC,EAAsB,CACpBC,UAAW,GAAGhO,cACdL,aACAqR,OAAQ,SACR9E,SACAzH,KAAMC,KAAKC,UAAUF,MAInB2K,GAAa3K,IACjBe,QAAQM,IAAIrB,EAAM,cACdA,GAA6B,iBAArBA,GAAMwM,YAChBlD,EAAsB,CACpBC,UAAW,GAAGhO,mCACdL,aACAqR,OAAQ,UACR9E,SACAzH,KAAMC,KAAKC,UAAUF,KAGzBsG,IAAatG,IAIf6C,EAAU,KACJhG,UAAU4P,aAAaC,cACzB7P,UAAU4P,aACPC,aAAa,CAAEC,MAAOxK,IACtByK,KAAM9B,IACLnE,EAAUrI,QAAUwM,EAChB7H,EAAU3E,UACZ2E,EAAU3E,QAAQyM,UAAYD,EAC9B/J,QAAQM,IAAI,yBAGfwL,MAAOjK,GAAQ7B,QAAQD,MAAM,0BAA2B8B,IAItD,KACD+D,EAAUrI,SACZqI,EAAUrI,QAAQwO,YAAYzJ,QAAS0J,GAAUA,EAAM7E,UAG1D,IAGHrF,EAAU,KACR,IAAKiE,IAAeE,EAAY,OAEhC,MAAMgG,EAAaC,YAAY,KAC7BjK,GAAiBC,EAAWC,IAC3B,KAGH,MAAO,IAAMgK,cAAcF,IAC1B,CAAChK,GAAkB8D,EAAYE,IAyBlC,OAjBAjG,QAAQM,IACN,eACAyF,EACA,aACAM,EACA,eACAJ,EACA,aACAJ,GAGF/D,EAAU,KACRlH,IAAa,GACbgC,EAAQwP,KAAKC,EAAkB,CAAEC,SAAUC,IAC3C3P,EAAQC,QAAQ,cACf,IAGDnG,eACEM,EAAA,QAAA,CACEwV,GAAG,eACHC,YAAY,YACZC,QAAQ,OACR9V,MAAO,CAAE+V,SAAU,WAAYC,QAAQ,OACvC1U,IAAI,KAGL2N,IAAeQ,GACdrP,EAAA,MAAA,CACEL,UAAU,6BACVC,MAAO,CAAEC,WAAYM,GAAgBP,OAAOE,MAAM+V,cAActW,SAGhES,EAAC8V,EAAa,CAAA,KAKlB9V,EAAA,MAAA,CACEL,UAAU,iGACVC,MAAO,CAAEC,WAAYM,GAAgBP,OAAOE,MAAMC,iBAAiBR,SAEnES,EAAA,MAAA,CAAKL,UAAU,yCAAwCJ,SACrDG,EAAA,MAAA,CAAKC,UAAU,gBAAeJ,SAAA,CAC5BS,WACE+V,IAAK7K,EACL3J,UAAQ,EACRI,aAAW,EACXD,OAAK,EACLyI,MAAOC,EAAiBD,MACxBE,OAAQD,EAAiBC,OACzB1K,UAAU,oDACVC,MAAO,CAAEoW,UAAW,gBAEtBhW,EAAA,SAAA,CACE+V,IAAK5K,EACLhB,MAAOC,EAAiBD,MACxBE,OAAQD,EAAiBC,OACzBzK,MAAO,CAAEoW,UAAW,aAAcC,QAAS,eAOjDpH,GAAcQ,GACdrP,EAACwC,EAAmB,CAClBE,QAASmM,EACTpM,eAAgBA,GAChBhD,OAAQU,IAKXgP,IAAkBE,IAAaR,GAC9B7O,EAACkW,EAAM,CACLC,MAAI,EACJxW,UAAU,8BACVyW,OAAO,SACPvD,QAAS,CAACuB,EAAOiC,OAKjBC,cAAY,EAAA/W,SAEZS,EAACf,EAAa,CACZC,MAAOyE,GACPxE,aAAcA,EACdC,gBAAiBA,EACjBC,WAAYA,EACZC,cAAeA,EACfE,OAAQA,GACRC,OAAQU,EAAcZ,SAEtBS,SAAKL,UAAU,uCAAsCJ,SACnDS,EAAC2C,EAAc,CACbC,SAAUiM,IAAe7K,GACzBrE,UAAU,kCACVkD,WAxGRoM,EAAmB,QAClBjL,GACE,QADoB,aAwGflB,YAAamM,EAAajP,EAAC+C,EAAU,IAAM,GAC3CC,WAAYiM,EAAaxM,GAAiB8R,GAC1CpU,eAAgBA"}
|
|
1
|
+
{"version":3,"file":"faceScan.mjs","sources":["../src/components/faceScan/FaceScanGuide.tsx","../src/components/faceScan/FaceScanErrorScreen.tsx","../src/customHooks/useFaceScan.ts","../src/components/faceScan/FaceScan.tsx"],"sourcesContent":["\nimport { FaceScanGuideProps } from \"../../types/interfaces\";\nimport Header from \"../Header\";\nimport { GenderType } from \"../../utils/enums\";\nimport { directionMessages, FACE_SCAN_HEADSHOT, glassesOffVideo, maleGlassesOffVideo } from \"../../utils/constants\";\n\n\nfunction FaceScanGuide({\n\tstage,\n\tvideoLoading,\n\tsetVideoLoading,\n\tvideoError,\n\tsetVideoError,\n\tchildren,\n\tgender,\n\tconfig,\n}: FaceScanGuideProps) {\n\tif (stage === -1) {\n\t\treturn (\n\t\t\t<div className=\"text-center p-[16px] w-full h-full\" style={{ background: config?.style?.base?.backgroundColor }}>\n\t\t\t\t<div className=\"w-full\">\n\t\t\t\t\t<Header noTitle resolvedConfig={config} />\n\t\t\t\t\t<h2\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tfontFamily: config?.style?.heading?.headingFontFamily || \"SeriouslyNostalgic Fn\",\n\t\t\t\t\t\t\tfontSize: config?.style?.heading?.headingFontSize || \"32px\",\n\t\t\t\t\t\t\tcolor: config?.style?.heading?.headingColor || \"#000\",\n\t\t\t\t\t\t\tfontWeight: config?.style?.heading?.headingFontWeight || \"normal\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tMake sure your face is fully visible{\" \"}\n\t\t\t\t\t</h2>\n\t\t\t\t\t{videoLoading && (\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"mb-4 flex items-center justify-center\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\tfontFamily: config?.style?.subheading?.subheadingFontFamily || \"'Inter', sans-serif\",\n\t\t\t\t\t\t\t\tfontSize: config?.style?.subheading?.subheadingFontSize || \"14px\",\n\t\t\t\t\t\t\t\tcolor: config?.style?.subheading?.subheadingColor || \"#4b5563\",\n\t\t\t\t\t\t\t\tfontWeight: config?.style?.subheading?.subheadingFontWeight || \"normal\",\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tLoading video...\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{videoError && (\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"mb-4 text-red-600\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\tfontFamily: config?.style?.subheading?.subheadingFontFamily || \"'Inter', sans-serif\",\n\t\t\t\t\t\t\t\tfontSize: config?.style?.subheading?.subheadingFontSize || \"14px\",\n\t\t\t\t\t\t\t\tcolor: \"#ff0000\",\n\t\t\t\t\t\t\t\tfontWeight: config?.style?.subheading?.subheadingFontWeight || \"normal\",\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tVideo failed to load. Please refresh the page or check your connection.\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{!videoError && (\n\t\t\t\t\t\t<div className={` w-[100px] mx-auto`}>\n\t\t\t\t\t\t\t<video\n\t\t\t\t\t\t\t\tsrc={gender === GenderType.Male ? maleGlassesOffVideo : glassesOffVideo}\n\t\t\t\t\t\t\t\tautoPlay\n\t\t\t\t\t\t\t\tloop\n\t\t\t\t\t\t\t\tcontrols={false}\n\t\t\t\t\t\t\t\tmuted\n\t\t\t\t\t\t\t\tplaysInline\n\t\t\t\t\t\t\t\tclassName=\"h-full w-full object-contain border-none\"\n\t\t\t\t\t\t\t\tonCanPlay={() => setVideoLoading(false)}\n\t\t\t\t\t\t\t\tonLoadStart={() => setVideoLoading(true)}\n\t\t\t\t\t\t\t\tonError={() => setVideoError(true)}\n\t\t\t\t\t\t\t\taria-label=\"Instructional video: Please remove your glasses before starting the scan.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className=\"text-center p-[16px] w-full h-full\" style={{ background: config?.style?.base?.backgroundColor }}>\n\t\t\t<div className=\"w-full\">\n\t\t\t\t<Header noTitle resolvedConfig={config} />\n\t\t\t\t<h3\n\t\t\t\t\tclassName=\"mb-[0.5rem]\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tfontFamily: config?.style?.heading?.headingFontFamily || \"SeriouslyNostalgic Fn\",\n\t\t\t\t\t\tfontSize: config?.style?.heading?.headingFontSize || \"32px\",\n\t\t\t\t\t\tcolor: config?.style?.heading?.headingColor || \"#000\",\n\t\t\t\t\t\tfontWeight: config?.style?.heading?.headingFontWeight || \"normal\",\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{directionMessages[stage]}\n\t\t\t\t</h3>\n\t\t\t\t{/* <p>We'll guide you to take 6 selfies </p> */}\n\t\t\t\t<div className=\"max-w-[400px] justify-center gap-1 mx-auto flex items-center\">\n\t\t\t\t\t<div className={` w-[120px] overflow-hidden ${stage === 0 ? \"opacity-100\" : \"opacity-0 hidden\"} `}>\n\t\t\t\t\t\t<video className=\"h-full w-full object-contain border-none\" muted loop autoPlay playsInline>\n\t\t\t\t\t\t\t<source src={gender === GenderType.Male ? FACE_SCAN_HEADSHOT.male.forward : FACE_SCAN_HEADSHOT.female.forward} type=\"video/mp4\" />\n\t\t\t\t\t\t</video>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className={` w-[120px] overflow-hidden ${stage === 1 ? \"opacity-100\" : \"opacity-0 hidden\"} `}>\n\t\t\t\t\t\t<video className=\"h-full w-full object-contain border-none\" muted loop autoPlay playsInline>\n\t\t\t\t\t\t\t<source src={gender === GenderType.Male ? FACE_SCAN_HEADSHOT.male.left : FACE_SCAN_HEADSHOT.female.left} type=\"video/mp4\" />\n\t\t\t\t\t\t</video>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className={` w-[120px] overflow-hidden ${stage === 2 ? \"opacity-100\" : \"opacity-0 hidden\"} `}>\n\t\t\t\t\t\t<video className=\"h-full w-full object-contain border-none\" muted loop autoPlay playsInline>\n\t\t\t\t\t\t\t<source src={gender === GenderType.Male ? FACE_SCAN_HEADSHOT.male.right : FACE_SCAN_HEADSHOT.female.right} type=\"video/mp4\" />\n\t\t\t\t\t\t</video>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className={` w-[120px] overflow-hidden ${stage === 3 ? \"opacity-100\" : \"opacity-0 hidden\"} `}>\n\t\t\t\t\t\t<video className=\"h-full w-full object-contain border-none\" muted loop autoPlay playsInline>\n\t\t\t\t\t\t\t<source src={gender === GenderType.Male ? FACE_SCAN_HEADSHOT.male.smile : FACE_SCAN_HEADSHOT.female.smile} type=\"video/mp4\" />\n\t\t\t\t\t\t</video>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport default FaceScanGuide;\n","import { ArrowRight } from \"lucide-react\";\nimport Header from \"../Header\";\nimport SpecificButton from \"../../atoms/specificButton/SpecificButton\";\n\nfunction FaceScanErrorScreen({ resetScanState, loading, config }: { resetScanState?: () => void; loading?: boolean; config?: any }) {\n\treturn (\n\t\t<div className=\"fixed top-[0] left-[0] z-[999] flex justify-center items-center w-full h-full\" style={{ background: config?.style?.base?.backgroundColor }}>\n\t\t\t<div className=\"flex flex-col w-full items-center p-[1rem] rounded-lg \">\n\t\t\t\t<Header noTitle resolvedConfig={config} />\n\t\t\t\t<div className=\"flex flex-col items-center w-full\">\n\t\t\t\t\t<h2\n\t\t\t\t\t\tclassName=\"text-xl font-semibold text-gray-800 \"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tfontFamily: config?.style?.heading?.headingFontFamily || \"SeriouslyNostalgic Fn\",\n\t\t\t\t\t\t\tfontSize: config?.style?.heading?.headingFontSize || \"32px\",\n\t\t\t\t\t\t\tcolor: config?.style?.heading?.headingColor || \"#000\",\n\t\t\t\t\t\t\tfontWeight: config?.style?.heading?.headingFontWeight || \"normal\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tYour Scan Failed\n\t\t\t\t\t</h2>\n\t\t\t\t\t<p\n\t\t\t\t\t\tclassName=\"mb-[1.5rem]\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tfontFamily: config?.style?.subheading?.subheadingFontFamily || \"'Inter', sans-serif\",\n\t\t\t\t\t\t\tfontSize: config?.style?.subheading?.subheadingFontSize || \"14px\",\n\t\t\t\t\t\t\tcolor: config?.style?.subheading?.subheadingColor || \"#4b5563\",\n\t\t\t\t\t\t\tfontWeight: config?.style?.subheading?.subheadingFontWeight || \"normal\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tPlease click below to reset your scan.\n\t\t\t\t\t</p>\n\t\t\t\t\t<SpecificButton\n\t\t\t\t\t\tdisabled={loading}\n\t\t\t\t\t\tclassName=\"w-full h-[45px] shadow-[0px_1px_2px_0px_#00000040] text-[16px]\"\n\t\t\t\t\t\tbuttonText=\"Reset Scan\"\n\t\t\t\t\t\tpostfixIcon={<ArrowRight />}\n\t\t\t\t\t\tbuttonFunc={() => resetScanState?.()}\n\t\t\t\t\t\tresolvedConfig={config}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport default FaceScanErrorScreen;\n","import {\n useState,\n useRef,\n useEffect,\n useCallback,\n useMemo,\n RefObject,\n} from \"react\";\nimport * as poseDetection from \"@tensorflow-models/pose-detection\";\nimport \"@tensorflow/tfjs\";\nimport { posthog } from \"posthog-js\";\nimport { ready, setBackend } from \"@tensorflow/tfjs\";\nimport speechService from \"../utils/service/speechService\";\nimport { videoConstraints, voiceOverAssetsPath } from \"../utils/constants\";\n\n/**\n * useFaceScan Hook with PostHog Analytics\n *\n * Analytics Events Tracked:\n * 1. face_scan_pose_detection - Every 4 seconds during scanning\n * - Keypoint scores and positions\n * - Face/body detection metrics\n * - Stage-specific positioning data\n * - Time spent in current stage\n *\n * 2. face_scan_stage_completed - When user successfully completes a stage\n * - Stage transition data\n * - Completion time\n *\n * 3. face_scan_started - When scan sequence begins\n * - Stage information\n * - Start timestamp\n *\n * 4. face_scan_reset - When user resets the scan\n * - Reset reason analysis\n * - Stage where reset occurred\n *\n * This helps identify why users take longer to get detected in pose detection.\n */\n\n// Debounce utility\nfunction debounce(fn: (...args: any) => void, delay: number) {\n let timer: number | NodeJS.Timeout;\n return (...args: any) => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => fn(...args), delay);\n };\n}\n\nlet preloadedDetector: poseDetection.PoseDetector | null = null;\n\nexport const preloadDetector = async () => {\n if (preloadedDetector) return preloadedDetector;\n\n const detector = await poseDetection.createDetector(\n poseDetection.SupportedModels.BlazePose,\n {\n runtime: \"tfjs\",\n modelType: \"full\",\n }\n );\n\n // Dummy video warmup\n const dummyCanvas = document.createElement(\"canvas\");\n dummyCanvas.width = videoConstraints.width;\n dummyCanvas.height = videoConstraints.height;\n const ctx = dummyCanvas.getContext(\"2d\");\n if (ctx) {\n ctx.fillStyle = \"black\";\n ctx.fillRect(0, 0, dummyCanvas.width, dummyCanvas.height);\n }\n try {\n await detector.estimatePoses(dummyCanvas);\n } catch (e) {\n // Expected: empty video stream\n console.log(e, \"at time of preload\");\n }\n\n preloadedDetector = detector;\n return detector;\n};\n\ntype Point = [number, number, number];\n\nfunction useFaceScan({\n faceScanId,\n onValidPose,\n onScanComplete,\n onModelReady,\n onStartRecording,\n shopDomain,\n}: {\n faceScanId: string;\n onValidPose?: () => void;\n onScanComplete?: () => void;\n onModelReady?: () => void;\n onStartRecording: () => void;\n shopDomain: string;\n}) {\n const detectorRef = useRef<poseDetection.PoseDetector | null>(null);\n const [scanStage, setScanStage] = useState(0);\n const [consecutiveValid, setConsecutiveValid] = useState(0);\n const [isModelLoaded, setIsModelLoaded] = useState(false);\n const [isScanningActive, setIsScanningActive] = useState(false);\n const utteranceRef = useRef(null);\n const scanStageRef = useRef(scanStage);\n const consecutiveValidRef = useRef(consecutiveValid);\n const validityBufferRef = useRef<boolean[]>([]);\n const lastSpokenMessageRef = useRef(\"\");\n const lastSpokenTimeRef = useRef(0);\n const voiceEnabledRef = useRef(false);\n const VALIDITY_BUFFER_LENGTH = 10;\n const CONSECUTIVE_VALID_LENGTH = 2;\n const TOTAL_STAGES = 4;\n const POSE_TRACKING_INTERVAL = 4000; // 4 seconds between pose tracking events\n const lastPoseTrackingTimeRef = useRef(0);\n\n // iOS detection\n const isIOS =\n /iPad|iPhone|iPod/.test(navigator.userAgent) ||\n (navigator.platform === \"MacIntel\" && navigator.maxTouchPoints > 1);\n\n const directionMessages = useMemo(\n () => [\n \"Face front\",\n \"Turn head fully left\",\n \"Turn head fully right\",\n \"Smile facing front\",\n ],\n []\n );\n\n // Debounced analytics event\n const debouncedTrackPoseDetection = useMemo(\n () =>\n debounce((payload) => {\n posthog.capture(`${shopDomain}/face_scan_pose_detection`, payload);\n }, 4000),\n [shopDomain]\n );\n\n // Track pose detection performance for analytics\n const trackPoseDetection = useCallback(\n (\n faceKeypoints: Point[],\n bodyKeypoints: Point[],\n stage: number,\n headValid: boolean,\n bodyInvalid: boolean,\n consecutiveValidCount: number\n ) => {\n const now = Date.now();\n\n // Only track every 4 seconds to avoid spam\n if (now - lastPoseTrackingTimeRef.current < POSE_TRACKING_INTERVAL) {\n return;\n }\n\n lastPoseTrackingTimeRef.current = now;\n\n try {\n // Extract key face keypoints for analysis\n const nose = faceKeypoints[0] || [0, 0, 0];\n const leftEye = faceKeypoints[2] || [0, 0, 0];\n const rightEye = faceKeypoints[5] || [0, 0, 0];\n const leftMouth = faceKeypoints[9] || [0, 0, 0];\n const rightMouth = faceKeypoints[10] || [0, 0, 0];\n\n // Calculate key metrics\n const faceKeypointScores = faceKeypoints\n .map((kp: Point) => kp[2])\n .filter((score: number) => score > 0);\n const avgFaceScore =\n faceKeypointScores.length > 0\n ? faceKeypointScores.reduce((a, b) => a + b, 0) /\n faceKeypointScores.length\n : 0;\n\n const bodyKeypointScores = bodyKeypoints\n .map((kp: Point) => kp[2])\n .filter((score: number) => score > 0);\n const avgBodyScore =\n bodyKeypointScores.length > 0\n ? bodyKeypointScores.reduce((a: number, b: number) => a + b, 0) /\n bodyKeypointScores.length\n : 0;\n\n // Calculate face positioning metrics\n const eyeDistance = Math.abs(leftEye[0] - rightEye[0]);\n const noseToRight = nose[0] - rightEye[0];\n const noseToLeft = leftEye[0] - nose[0];\n\n // Determine expected position based on stage\n let expectedPosition = \"frontal\";\n if (stage === 1) expectedPosition = \"left\";\n else if (stage === 2) expectedPosition = \"right\";\n else if (stage === 3) expectedPosition = \"frontal_smile\";\n\n const payload = {\n faceScanId,\n stage,\n timestamp: new Date().toISOString(),\n data: JSON.stringify({\n headValid,\n bodyInvalid,\n consecutiveValid: consecutiveValidCount,\n avgFaceScore: parseFloat(avgFaceScore.toFixed(3)),\n avgBodyScore: parseFloat(avgBodyScore.toFixed(3)),\n faceKeypointsDetected: faceKeypointScores.length,\n bodyKeypointsDetected: bodyKeypointScores.length,\n eyeDistance: parseFloat(eyeDistance.toFixed(2)),\n noseToRightRatio: parseFloat(\n (noseToRight / eyeDistance).toFixed(3)\n ),\n noseToLeftRatio: parseFloat((noseToLeft / eyeDistance).toFixed(3)),\n noseScore: parseFloat(nose[2].toFixed(3)),\n leftEyeScore: parseFloat(leftEye[2].toFixed(3)),\n rightEyeScore: parseFloat(rightEye[2].toFixed(3)),\n leftMouthScore: parseFloat(leftMouth[2].toFixed(3)),\n rightMouthScore: parseFloat(rightMouth[2].toFixed(3)),\n expectedPosition,\n // timeInStage: Math.floor((now - lastPoseTrackingTimeRef.current) / 1000),\n }),\n };\n debouncedTrackPoseDetection(payload);\n } catch (error) {\n console.warn(\"Failed to track pose detection:\", error);\n }\n },\n [faceScanId, debouncedTrackPoseDetection]\n );\n\n // Enable voice commands from user interaction context\n const enableVoiceCommands = () => {\n voiceEnabledRef.current = true; // Set ref immediately\n\n // iOS Safari requires an initial speech synthesis call to enable audio context\n if (isIOS && \"speechSynthesis\" in window) {\n // Create a silent utterance to initialize speech synthesis\n const silentUtterance = new SpeechSynthesisUtterance(\"\");\n silentUtterance.volume = 0; // Set volume to 0 to make it silent\n speechSynthesis.speak(silentUtterance);\n }\n };\n\n // Start scan sequence: first speak direction, then start scanning\n const startScanSequence = () => {\n // Track scan start\n posthog.capture(`${shopDomain}/face_scan_detection_started`, {\n faceScanId,\n stage: scanStageRef.current,\n timestamp: new Date().toISOString(),\n stageName: directionMessages[scanStageRef.current],\n });\n\n // Then start scanning after the voice command finishes\n setTimeout(() => {\n setIsScanningActive(true);\n }, 1500); // Wait for voice command to complete\n };\n\n const isHeadInPosition = (stage: number, keypoints: Point[]) => {\n const nose = 0;\n const leftEye = 2;\n const rightEye = 5;\n const leftMouth = 9;\n const rightMouth = 10;\n\n const minScore = 0.2;\n if (\n keypoints[nose][2] < minScore ||\n keypoints[leftEye][2] < minScore ||\n keypoints[rightEye][2] < minScore\n ) {\n return false;\n }\n\n const eyesToMouthVerticalDistance =\n 0.5 * (keypoints[rightMouth][1] + keypoints[leftMouth][1]) -\n 0.5 * (keypoints[rightEye][1] + keypoints[leftEye][1]);\n\n const faceTiltedness =\n (0.5 * (keypoints[rightMouth][1] + keypoints[leftMouth][1]) -\n keypoints[nose][1]) /\n eyesToMouthVerticalDistance;\n\n const faceIsTilted = faceTiltedness > 0.7 || faceTiltedness < 0.3;\n\n const faceIsVertical =\n Math.abs(keypoints[leftEye][1] - keypoints[rightEye][1]) <\n 0.5 * eyesToMouthVerticalDistance &&\n Math.abs(keypoints[leftMouth][1] - keypoints[rightMouth][1]) <\n 0.5 * eyesToMouthVerticalDistance;\n\n const eyeDistance = keypoints[leftEye][0] - keypoints[rightEye][0];\n const noseToRight = keypoints[nose][0] - keypoints[rightEye][0];\n const noseToLeft = keypoints[leftEye][0] - keypoints[nose][0];\n\n const frontalFace =\n noseToRight > 0.4 * eyeDistance && noseToRight < 0.6 * eyeDistance;\n const fullLeft = noseToRight > 0.85 * eyeDistance;\n const fullRight = noseToLeft > 0.85 * eyeDistance;\n\n switch (stage) {\n case 0:\n return !faceIsTilted && faceIsVertical && frontalFace;\n case 1:\n return !faceIsTilted && faceIsVertical && fullLeft;\n case 2:\n return !faceIsTilted && faceIsVertical && fullRight;\n case 3:\n return !faceIsTilted && faceIsVertical && frontalFace;\n default:\n return false;\n }\n };\n\n const faceScanDetector = async (\n webcamRef: RefObject<HTMLVideoElement | null>,\n canvasRef: RefObject<HTMLCanvasElement | null>\n ) => {\n if (\n !detectorRef.current ||\n !webcamRef.current ||\n !isModelLoaded ||\n !isScanningActive\n )\n return;\n\n try {\n const poses = await detectorRef.current.estimatePoses(webcamRef.current);\n if (poses.length > 0) {\n const faceKeypoints: Point[] = [];\n const bodyKeypoints: Point[] = [];\n poses[0].keypoints.forEach((landmark, idx) => {\n const score = landmark.score ?? 0;\n const point: Point = [landmark.x ?? -1, landmark.y ?? -1, score];\n if (idx <= 10) faceKeypoints.push(point);\n else bodyKeypoints.push(point);\n });\n\n // Optional drawing (invisible canvas)\n if (canvasRef?.current) {\n const ctx = canvasRef.current.getContext(\"2d\");\n const { width, height } = canvasRef.current;\n if (ctx) {\n ctx.clearRect(0, 0, width, height);\n\n const progress = Math.min(\n consecutiveValidRef.current / CONSECUTIVE_VALID_LENGTH,\n 1\n );\n const radius = height * 0.35;\n ctx.beginPath();\n ctx.strokeStyle = \"#00ff00\";\n ctx.lineWidth = 6;\n ctx.arc(\n width / 2,\n height / 2,\n radius + 10,\n -Math.PI / 2,\n -Math.PI / 2 + progress * 2 * Math.PI\n );\n ctx.stroke();\n }\n }\n\n const headValid = isHeadInPosition(scanStageRef.current, faceKeypoints);\n const bodyInvalid =\n bodyKeypoints.slice(2).filter((p) => p[2] > 0.7).length <= 2;\n\n validityBufferRef.current.push(headValid && bodyInvalid);\n if (validityBufferRef.current.length > VALIDITY_BUFFER_LENGTH) {\n validityBufferRef.current.shift();\n }\n\n const validCount = validityBufferRef.current.filter(Boolean).length;\n if (validCount >= 2) {\n const nextValid = consecutiveValidRef.current + 1;\n setConsecutiveValid(nextValid);\n } else {\n setConsecutiveValid(Math.max(0, consecutiveValidRef.current - 1));\n }\n\n // ✅ Pose confirmed\n if (consecutiveValidRef.current >= CONSECUTIVE_VALID_LENGTH) {\n onValidPose?.();\n\n const nextStage = scanStageRef.current + 1;\n setConsecutiveValid(0);\n validityBufferRef.current = [];\n setScanStage(nextStage);\n setIsScanningActive(false); // Stop scanning for next stage\n\n // Play sound effect for stage completion, then next stage instruction (if not final stage)\n (async () => {\n await speechService.playAudio(\n `${voiceOverAssetsPath}face-scan-vos/Sound-effect.mp3`\n );\n // Track stage completion\n posthog.capture(\n `${shopDomain}/face_scan_detection_stage_completed`,\n {\n faceScanId,\n completedStage: scanStageRef.current,\n nextStage,\n timestamp: new Date().toISOString(),\n totalStages: TOTAL_STAGES,\n stageName: directionMessages[scanStageRef.current],\n }\n );\n // Start recording after stage 0 is completed\n if (nextStage === 1 && onStartRecording) {\n onStartRecording();\n }\n if (nextStage >= TOTAL_STAGES) {\n onScanComplete?.();\n } else {\n // Play next stage instruction sound, then start scan sequence\n let nextSound = null;\n switch (nextStage) {\n case 1:\n nextSound = \"Left.mp3\";\n break;\n case 2:\n nextSound = \"Right.mp3\";\n break;\n case 3:\n nextSound = \"Smile.mp3\";\n break;\n default:\n nextSound = null;\n }\n if (nextSound) {\n await speechService.playAudio(\n `${voiceOverAssetsPath}face-scan-vos/${nextSound}`\n );\n }\n startScanSequence();\n }\n })();\n }\n\n // Track pose detection performance for analytics\n trackPoseDetection(\n faceKeypoints,\n bodyKeypoints,\n scanStageRef.current,\n headValid,\n bodyInvalid,\n consecutiveValidRef.current\n );\n }\n } catch (err) {\n console.error(\"Pose detection error:\", err);\n }\n };\n\n const initializeModels = async () => {\n try {\n console.log(\"Initializing TensorFlow...\");\n await setBackend(\"webgl\");\n await ready();\n console.log(\"TensorFlow initialized, loading models...\");\n const detector = await preloadDetector(); // <-- now it waits for preload\n console.log(detector, \"detector\");\n\n detectorRef.current = detector;\n console.log(\"Pose detection model loaded successfully\");\n setIsModelLoaded(true);\n onModelReady?.();\n } catch (err) {\n console.error(\"Error initializing face scan:\", err);\n }\n };\n\n const disposeModelAndTf = async () => {\n try {\n setIsModelLoaded(false);\n if (detectorRef.current) {\n await detectorRef.current.dispose();\n detectorRef.current = null;\n }\n } catch (err) {\n console.error(\"Error disposing resources:\", err);\n } finally {\n setScanStage(0);\n setConsecutiveValid(0);\n scanStageRef.current = 0;\n consecutiveValidRef.current = 0;\n validityBufferRef.current = [];\n }\n };\n\n const resetScan = () => {\n // Track scan reset\n posthog.capture(`${shopDomain}/face_scan_reset`, {\n faceScanId,\n stage: scanStageRef.current,\n timestamp: new Date().toISOString(),\n stageName: directionMessages[scanStageRef.current],\n consecutiveValid: consecutiveValidRef.current,\n });\n\n setScanStage(0);\n setConsecutiveValid(0);\n setIsScanningActive(false);\n scanStageRef.current = 0;\n consecutiveValidRef.current = 0;\n validityBufferRef.current = [];\n lastSpokenMessageRef.current = \"\";\n lastSpokenTimeRef.current = 0;\n voiceEnabledRef.current = false;\n if (utteranceRef.current) {\n speechSynthesis.cancel();\n utteranceRef.current = null;\n }\n };\n\n useEffect(() => {\n // disposeModelAndTf();\n initializeModels();\n return () => {\n disposeModelAndTf();\n };\n }, []);\n\n useEffect(() => {\n scanStageRef.current = scanStage;\n }, [scanStage]);\n\n useEffect(() => {\n consecutiveValidRef.current = consecutiveValid;\n }, [consecutiveValid]);\n\n return {\n faceScanDetector,\n scanStage,\n setScanStage,\n consecutiveValid,\n isModelLoaded,\n resetScan,\n enableVoiceCommands,\n startScanSequence,\n isScanningActive,\n };\n}\n\nexport default useFaceScan;\n","\"use client\";\nimport { useEffect, useRef, useState, useCallback, useMemo } from \"react\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Drawer } from \"@mui/material\";\nimport FaceScanGuide from \"./FaceScanGuide\";\nimport FaceScanErrorScreen from \"./FaceScanErrorScreen\";\nimport posthog from \"posthog-js\";\nimport { FaceScanProps } from \"../../types/interfaces\";\nimport { generateUuid, handleScanTimeCapture, handleWebSocketCapture } from \"../../utils/utils\";\nimport { useLocalConfig } from \"../../config/useLocalConfig\";\nimport { posthogPublicHost, posthogPublicKey, videoConstraints, videoTypes, voiceOverAssetsPath } from \"../../utils/constants\";\nimport swan from \"../../utils/service/swanService\";\nimport speechService from \"../../utils/service/speechService\";\nimport useFaceScan from \"../../customHooks/useFaceScan\";\nimport SpecificButton from \"../../atoms/specificButton/SpecificButton\";\nimport LoadingScreen from \"../LoadingScreen\";\n\nexport const FaceScan: React.FC<FaceScanProps> = ({ userDetails, onComplete, onScanError, onRetry, config,isError,isSuccess }) => {\n\tconst webcamRef = useRef<HTMLVideoElement | null>(null);\n\tconst canvasRef = useRef<HTMLCanvasElement | null>(null);\n\tconst mediaRecorderRef = useRef<MediaRecorder | null>(null);\n\tconst recordedBlobsRef = useRef<Blob[] | null>([]);\n\tconst streamRef = useRef<MediaStream | null>(null);\n\tconst [showLoader, setShowLoader] = useState(false);\n\tconst [modelReady, setModelReady] = useState(false);\n\tconst [isScanning, setIsScanning] = useState(false);\n\tconst [showGuideCard, setShowGuideCard] = useState(true);\n\tconst [videoLoading, setVideoLoading] = useState(false);\n\tconst [videoError, setVideoError] = useState(false);\n\tconst [hasError, setHasError] = useState(false);\n\tconst [faceScanId, setFaceScanId] = useState(generateUuid());\n\tconst resolvedConfig = useLocalConfig(config);\n\tconst { email, gender, deviceFocalLength, shopDomain, callbackUrl } = userDetails;\n\tconst supportedTypes = useMemo(() => videoTypes.filter((type) => MediaRecorder.isTypeSupported(type)), []);\n\n\tconst stopRecording = useCallback(() => {\n\t\tconsole.log(\"Stopping recording...\");\n\t\tif (mediaRecorderRef.current && mediaRecorderRef.current.state === \"recording\") {\n\t\t\tmediaRecorderRef.current.stop();\n\t\t}\n\t\tmediaRecorderRef.current = null;\n\t}, []);\n\n\tconst uploadFinalVideo = async () => {\n\t\tsetShowLoader(true);\n\t\tconsole.log(\"upload final video\");\n\t\tif (recordedBlobsRef.current) {\n\t\t\tif (recordedBlobsRef.current.length === 0) {\n\t\t\t\tconsole.error(\"No video data recorded\");\n\t\t\t\tsetHasError(true);\n\t\t\t\tsetShowLoader(false);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst videoFile = new File(recordedBlobsRef.current, `${faceScanId}.webm`, {\n\t\t\t\ttype: \"video/webm\",\n\t\t\t});\n\t\t\tconst fileSize = videoFile.size;\n\t\t\tconst fileSizeMB = (fileSize / (1024 * 1024)).toFixed(2);\n\t\t\tconst estimatedDuration = Math.round(fileSize / 10000);\n\t\t\tconst videoData = {\n\t\t\t\tvideo_size_mb: parseFloat(fileSizeMB),\n\t\t\t\tvideo_size_bytes: fileSize,\n\t\t\t\tblob_count: recordedBlobsRef.current.length,\n\t\t\t\testimated_duration_seconds: estimatedDuration,\n\t\t\t};\n\n\t\t\tconst metaData = [\n\t\t\t\t{ gender: gender },\n\t\t\t\t{ face_scan_id: faceScanId },\n\t\t\t\t{\n\t\t\t\t\tfocal_length: `${deviceFocalLength}`,\n\t\t\t\t},\n\t\t\t\t{ customer_store_url: shopDomain },\n\t\t\t\t{ scan_type: \"face_scan\" },\n\t\t\t\t{ callback_url: callbackUrl },\n\t\t\t];\n\t\t\thandleScanTimeCapture({\n\t\t\t\teventName: `${shopDomain}/face_scan_meta_data`,\n\t\t\t\tfaceScanId,\n\t\t\t\temail,\n\t\t\t\tdata: JSON.stringify({ metaData, videoData }),\n\t\t\t});\n\n\t\t\tconst uploadStartTime = Date.now();\n\t\t\thandleScanTimeCapture({\n\t\t\t\teventName: `${shopDomain}/face_scan_upload_start`,\n\t\t\t\tfaceScanId,\n\t\t\t\temail,\n\t\t\t\tstartTime: uploadStartTime,\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tconst res = await swan.fileUpload.faceScanFileUploader({\n\t\t\t\t\tfile: videoFile,\n\t\t\t\t\tobjectKey: faceScanId,\n\t\t\t\t\temail,\n\t\t\t\t\tarrayMetaData: metaData,\n\t\t\t\t\tcontentType: videoFile.type,\n\t\t\t\t});\n\n\t\t\t\tconst uploadEndTime = Date.now();\n\t\t\t\tconst uploadDuration = uploadEndTime - uploadStartTime;\n\n\t\t\t\thandleScanTimeCapture({\n\t\t\t\t\teventName: `${shopDomain}/face_scan_upload_complete`,\n\t\t\t\t\tfaceScanId,\n\t\t\t\t\temail,\n\t\t\t\t\tcompletionTime: uploadEndTime,\n\t\t\t\t\tuploadDuration,\n\t\t\t\t});\n\n\t\t\t\tconsole.log(\"✅ Upload successful\", res);\n\t\t\t\tswan.measurement.handlFaceScaneSocket({\n\t\t\t\t\tfaceScanId,\n\t\t\t\t\tonOpen: () => {\n\t\t\t\t\t\thandleWebSocketCapture({\n\t\t\t\t\t\t\teventName: `${shopDomain}/webSocket`,\n\t\t\t\t\t\t\tfaceScanID: faceScanId,\n\t\t\t\t\t\t\tconnection: \"open\",\n\t\t\t\t\t\t\ttype: \"faceScan_recommendation\",\n\t\t\t\t\t\t\temail,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconsole.log(\"websocket connect open\");\n\t\t\t\t\t},\n\t\t\t\t\tonError: (err) => {\n\t\t\t\t\t\thandleWebSocketCapture({\n\t\t\t\t\t\t\teventName: `${shopDomain}/webSocket`,\n\t\t\t\t\t\t\tfaceScanID: faceScanId,\n\t\t\t\t\t\t\tconnection: \"error\",\n\t\t\t\t\t\t\ttype: \"faceScan_recommendation\",\n\t\t\t\t\t\t\temail,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tonError(err);\n\t\t\t\t\t},\n\t\t\t\t\tonSuccess: (data) => {\n\t\t\t\t\t\thandleWebSocketCapture({\n\t\t\t\t\t\t\teventName: `${shopDomain}/webSocket`,\n\t\t\t\t\t\t\tfaceScanID: faceScanId,\n\t\t\t\t\t\t\tconnection: \"success\",\n\t\t\t\t\t\t\ttype: \"faceScan_recommendation\",\n\t\t\t\t\t\t\temail,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tonSuccess(data);\n\t\t\t\t\t},\n\t\t\t\t\tonClose: () => {\n\t\t\t\t\t\tconsole.log(\"websocket connect close\");\n\t\t\t\t\t\thandleWebSocketCapture({\n\t\t\t\t\t\t\teventName: `${shopDomain}/webSocket`,\n\t\t\t\t\t\t\tfaceScanID: faceScanId,\n\t\t\t\t\t\t\tconnection: \"close\",\n\t\t\t\t\t\t\ttype: \"faceScan_recommendation\",\n\t\t\t\t\t\t\temail,\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tonError(error);\n\t\t\t}\n\t\t}\n\t};\n\n\tconst startRecording = useCallback(() => {\n\t\tconsole.log(\"Starting recording for stages 1-3...\");\n\t\tconst stream = webcamRef.current?.srcObject as MediaStream | null;\n\t\tif (!stream) return;\n\n\t\t// Create a canvas to normalize orientation\n\t\tconst videoTrack = stream.getVideoTracks()[0];\n\t\tconst settings = videoTrack.getSettings();\n\t\tconst { width = videoConstraints.width, height = videoConstraints.height } = settings;\n\n\t\tconst canvas = document.createElement(\"canvas\");\n\t\tconst ctx = canvas.getContext(\"2d\");\n\n\t\t// Always force portrait (swap width/height if landscape)\n\t\tif (width > height) {\n\t\t\tcanvas.width = height;\n\t\t\tcanvas.height = width;\n\t\t} else {\n\t\t\tcanvas.width = width;\n\t\t\tcanvas.height = height;\n\t\t}\n\n\t\tconst videoEl = document.createElement(\"video\");\n\t\tvideoEl.srcObject = stream;\n\t\tvideoEl.muted = true;\n\t\tvideoEl.playsInline = true;\n\t\tvideoEl.play();\n\n\t\t// Draw video frames onto canvas\n\t\tconst drawFrame = () => {\n\t\t\t// Rotate if camera gives landscape frames\n\t\t\tif (width > height) {\n\t\t\t\tctx?.save();\n\t\t\t\tctx?.translate(canvas.width, 0);\n\t\t\t\tctx?.rotate(Math.PI / 2);\n\t\t\t\tctx?.drawImage(videoEl, 0, 0, height, width);\n\t\t\t\tctx?.restore();\n\t\t\t} else {\n\t\t\t\tctx?.drawImage(videoEl, 0, 0, width, height);\n\t\t\t}\n\t\t\trequestAnimationFrame(drawFrame);\n\t\t};\n\t\tdrawFrame();\n\n\t\t// Capture portrait-normalized stream\n\t\tconst canvasStream = canvas.captureStream(30); // 30fps\n\t\tconst mediaRecorderOptions = supportedTypes.length > 0 ? { mimeType: supportedTypes[0] } : {};\n\t\tlet mediaRecorder;\n\n\t\ttry {\n\t\t\tmediaRecorder = new MediaRecorder(canvasStream, mediaRecorderOptions);\n\t\t} catch (e) {\n\t\t\tconsole.error(\"MediaRecorder init failed:\", e);\n\t\t\tsetHasError(true);\n\t\t\tsetShowLoader(false);\n\t\t\treturn;\n\t\t}\n\n\t\trecordedBlobsRef.current = [];\n\t\tmediaRecorder.ondataavailable = (event) => {\n\t\t\tif (event?.data && event.data.size > 0 && recordedBlobsRef.current) {\n\t\t\t\trecordedBlobsRef.current.push(event.data);\n\t\t\t}\n\t\t};\n\t\tmediaRecorder.onstop = () => {\n\t\t\tconsole.log(\"Recording stopped, total blobs:\", recordedBlobsRef?.current?.length);\n\t\t\tuploadFinalVideo();\n\t\t};\n\n\t\tmediaRecorder.start(100); // 100ms chunks\n\t\tmediaRecorderRef.current = mediaRecorder;\n\t\tconsole.log(\"Recording started successfully (portrait normalized)\");\n\t}, [supportedTypes, uploadFinalVideo]);\n\n\tconst { faceScanDetector, scanStage, setScanStage, resetScan, isModelLoaded, startScanSequence } = useFaceScan({\n\t\tfaceScanId,\n\t\tshopDomain,\n\t\tonScanComplete: () => {\n\t\t\tstopRecording();\n\t\t},\n\t\tonModelReady: () => {\n\t\t\tsetModelReady(true);\n\t\t},\n\t\tonStartRecording: () => {\n\t\t\tconsole.log(\"Stage 0 completed - starting recording for stages 1-3\");\n\t\t\tstartRecording();\n\t\t},\n\t});\n\n\tconst startScan = useCallback(() => {\n\t\tif (!isModelLoaded) return;\n\t\tsetScanStage(0);\n\t\tspeechService.playAudio(`${voiceOverAssetsPath}face-scan-vos/Face-forward.mp3`);\n\t\tsetTimeout(() => {\n\t\t\tsetIsScanning(true);\n\t\t\tsetTimeout(() => {\n\t\t\t\tstartScanSequence();\n\t\t\t}, 200);\n\t\t}, 200);\n\t}, [setScanStage, setIsScanning, startScanSequence, isModelLoaded]);\n\n\tconst resetScanState = useCallback(() => {\n\t\tsetIsScanning(false);\n\t\tsetShowGuideCard(true);\n\t\tstopRecording();\n\t\tresetScan();\n\t\tonRetry?.();\n\t\trecordedBlobsRef.current = [];\n\t\tif (mediaRecorderRef.current) {\n\t\t\tmediaRecorderRef.current = null;\n\t\t}\n\t\tsetHasError(false);\n\t\tsetScanStage(-1);\n\t\tsetFaceScanId(generateUuid());\n\t\tif (webcamRef.current && streamRef.current) {\n\t\t\twebcamRef.current.srcObject = streamRef.current;\n\t\t\tconsole.log(\"Camera stream restored after reset\");\n\t\t}\n\t}, [setScanStage, setIsScanning, setShowGuideCard, stopRecording, resetScan, setFaceScanId]);\n\n\tconst onError = (data: any) => {\n\t\tconsole.log(data, \"ws error\");\n\t\tonScanError?.(data);\n\t\tsetHasError(true);\n\t\tsetShowLoader(false);\n\t\tsetShowGuideCard(false);\n\t\thandleScanTimeCapture({\n\t\t\teventName: `${shopDomain}/faceScan`,\n\t\t\tfaceScanId,\n\t\t\tstatus: \"failed\",\n\t\t\temail,\n\t\t\tdata: JSON.stringify(data),\n\t\t});\n\t};\n\n\tconst onSuccess = (data: any) => {\n\t\tconsole.log(data, \"ws success\");\n\t\tif (data && data?.resultType === \"intermediate\") {\n\t\t\thandleScanTimeCapture({\n\t\t\t\teventName: `${shopDomain}/faceScan_success/intermediate`,\n\t\t\t\tfaceScanId,\n\t\t\t\tstatus: \"success\",\n\t\t\t\temail,\n\t\t\t\tdata: JSON.stringify(data),\n\t\t\t});\n\t\t}\n\t\tonComplete?.(data);\n\t};\n\n\t// Initialize webcam\n\tuseEffect(() => {\n\t\tif (isError || isSuccess) return;\n\t\tif (navigator.mediaDevices.getUserMedia) {\n\t\t\tnavigator.mediaDevices\n\t\t\t\t.getUserMedia({ video: videoConstraints })\n\t\t\t\t.then((stream) => {\n\t\t\t\t\tstreamRef.current = stream;\n\t\t\t\t\tif (webcamRef.current) {\n\t\t\t\t\t\twebcamRef.current.srcObject = stream;\n\t\t\t\t\t\tconsole.log(\"Webcam initialized\");\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch((err) => console.error(\"Error accessing webcam:\", err));\n\t\t}\n\n\t\t// Cleanup function\n\t\treturn () => {\n\t\t\tif (streamRef.current) {\n\t\t\t\tstreamRef.current.getTracks().forEach((track) => track.stop());\n\t\t\t}\n\t\t};\n\t}, [isError, isSuccess]);\n\n\t// Face detection interval - only run when scanning is active\n\tuseEffect(() => {\n\t\tif (!modelReady || !isScanning) return;\n\n\t\tconst intervalId = setInterval(() => {\n\t\t\tfaceScanDetector(webcamRef, canvasRef);\n\t\t}, 500);\n\n\t\t// eslint-disable-next-line consistent-return\n\t\treturn () => clearInterval(intervalId);\n\t}, [faceScanDetector, modelReady, isScanning]);\n\n\tconst getButtonText = () => {\n\t\tif (isScanning) return \"Reset\";\n\t\tif (!isModelLoaded) return \"Loading...\";\n\t\treturn \"Start\";\n\t};\n\n\tconsole.log(\"Model ready:\", modelReady, \"Has error:\", hasError, \"Is scanning:\", isScanning, \"showLoader\", showLoader);\n\n\tuseEffect(() => {\n\t\tsetScanStage(-1);\n\t\tif (isError || isSuccess) return;\n\t\tposthog.init(posthogPublicKey, { api_host: posthogPublicHost });\n\t\tposthog.capture(\"$pageview\");\n\t}, [isError, isSuccess]);\n\n\tif (isError) {\n\t\treturn <FaceScanErrorScreen config={resolvedConfig} />;\n\t}\n\n\tif (isSuccess) {\n\t\treturn (\n\t\t\t<div className=\"fixed z-[9] w-full h-full\" style={{ background: resolvedConfig?.style?.base?.primaryColor }}>\n\t\t\t\t{/* <Asset genderType={gender} /> */}\n\t\t\t\t<LoadingScreen url={resolvedConfig?.loader} />\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<>\n\t\t\t<audio id=\"audioElement\" crossOrigin=\"anonymous\" preload=\"auto\" style={{ position: \"absolute\", zIndex: -99999 }} src=\"\" />\n\n\t\t\t{showLoader && !hasError && (\n\t\t\t\t<div className=\"fixed z-[9] w-full h-full\" style={{ background: resolvedConfig?.style?.base?.primaryColor }}>\n\t\t\t\t\t{/* <Asset genderType={gender} /> */}\n\t\t\t\t\t<LoadingScreen url={resolvedConfig?.loader} />\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Always show camera view */}\n\t\t\t<div className=\"h-full flex-col relative w-full flex justify-center items-center text-center rounded-t-[20px]\" style={{ background: resolvedConfig?.style?.base?.backgroundColor }}>\n\t\t\t\t<div className=\"flex-1 w-full max-w-md overflow-hidden\">\n\t\t\t\t\t<div className=\"w-full h-full\">\n\t\t\t\t\t\t<video\n\t\t\t\t\t\t\tref={webcamRef}\n\t\t\t\t\t\t\tautoPlay\n\t\t\t\t\t\t\tplaysInline\n\t\t\t\t\t\t\tmuted\n\t\t\t\t\t\t\twidth={videoConstraints.width}\n\t\t\t\t\t\t\theight={videoConstraints.height}\n\t\t\t\t\t\t\tclassName=\"w-full h-full object-cover fixed left-0 top-0 z-0\"\n\t\t\t\t\t\t\tstyle={{ transform: \"scaleX(-1)\" }}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<canvas ref={canvasRef} width={videoConstraints.width} height={videoConstraints.height} style={{ transform: \"scaleX(-1)\", opacity: \"0\" }} />\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Error overlay */}\n\t\t\t{!showLoader && hasError && <FaceScanErrorScreen loading={showLoader} resetScanState={resetScanState} config={resolvedConfig} />}\n\n\t\t\t{/* Scan guide drawer - only show when scanning */}\n\t\t\t{showGuideCard && !hasError && !showLoader && (\n\t\t\t\t<Drawer\n\t\t\t\t\topen\n\t\t\t\t\tclassName=\"face-scan-small camera-draw\"\n\t\t\t\t\tanchor=\"bottom\"\n\t\t\t\t\tonClose={(event, reason) => {\n\t\t\t\t\t\tif (reason === \"backdropClick\") {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t\thideBackdrop\n\t\t\t\t>\n\t\t\t\t\t<FaceScanGuide stage={scanStage} videoLoading={videoLoading} setVideoLoading={setVideoLoading} videoError={videoError} setVideoError={setVideoError} gender={gender} config={resolvedConfig}>\n\t\t\t\t\t\t<div className=\"flex justify-center w-full p-[1rem]\">\n\t\t\t\t\t\t\t<SpecificButton\n\t\t\t\t\t\t\t\tdisabled={showLoader || !isModelLoaded}\n\t\t\t\t\t\t\t\tclassName=\"!w-[60px] !h-[35px] !py-0 !px-0\"\n\t\t\t\t\t\t\t\tbuttonText={getButtonText()}\n\t\t\t\t\t\t\t\tpostfixIcon={isScanning ? <ArrowRight /> : \"\"}\n\t\t\t\t\t\t\t\tbuttonFunc={isScanning ? resetScanState : startScan}\n\t\t\t\t\t\t\t\tresolvedConfig={resolvedConfig}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</FaceScanGuide>\n\t\t\t\t</Drawer>\n\t\t\t)}\n\t\t</>\n\t);\n};\n"],"names":["FaceScanGuide","stage","videoLoading","setVideoLoading","videoError","setVideoError","children","gender","config","_jsxs","className","style","background","base","backgroundColor","_jsx","Header","noTitle","resolvedConfig","fontFamily","heading","headingFontFamily","fontSize","headingFontSize","color","headingColor","fontWeight","headingFontWeight","subheading","subheadingFontFamily","subheadingFontSize","subheadingColor","subheadingFontWeight","src","GenderType","Male","maleGlassesOffVideo","glassesOffVideo","autoPlay","loop","controls","muted","playsInline","onCanPlay","onLoadStart","onError","directionMessages","FACE_SCAN_HEADSHOT","male","forward","female","type","left","right","smile","FaceScanErrorScreen","resetScanState","loading","SpecificButton","disabled","buttonText","postfixIcon","ArrowRight","buttonFunc","preloadedDetector","useFaceScan","faceScanId","onValidPose","onScanComplete","onModelReady","onStartRecording","shopDomain","detectorRef","useRef","scanStage","setScanStage","useState","consecutiveValid","setConsecutiveValid","isModelLoaded","setIsModelLoaded","isScanningActive","setIsScanningActive","utteranceRef","scanStageRef","consecutiveValidRef","validityBufferRef","lastSpokenMessageRef","lastSpokenTimeRef","voiceEnabledRef","lastPoseTrackingTimeRef","isIOS","test","navigator","userAgent","platform","maxTouchPoints","useMemo","debouncedTrackPoseDetection","fn","delay","timer","args","clearTimeout","setTimeout","debounce","payload","posthog","capture","trackPoseDetection","useCallback","faceKeypoints","bodyKeypoints","headValid","bodyInvalid","consecutiveValidCount","now","Date","current","nose","leftEye","rightEye","leftMouth","rightMouth","faceKeypointScores","map","kp","filter","score","avgFaceScore","length","reduce","a","b","bodyKeypointScores","avgBodyScore","eyeDistance","Math","abs","noseToRight","noseToLeft","expectedPosition","timestamp","toISOString","data","JSON","stringify","parseFloat","toFixed","faceKeypointsDetected","bodyKeypointsDetected","noseToRightRatio","noseToLeftRatio","noseScore","leftEyeScore","rightEyeScore","leftMouthScore","rightMouthScore","error","console","warn","startScanSequence","stageName","initializeModels","async","log","setBackend","ready","detector","poseDetection","createDetector","SupportedModels","BlazePose","runtime","modelType","dummyCanvas","document","createElement","width","videoConstraints","height","ctx","getContext","fillStyle","fillRect","estimatePoses","e","preloadDetector","err","useEffect","dispose","disposeModelAndTf","faceScanDetector","webcamRef","canvasRef","poses","keypoints","forEach","landmark","idx","point","x","y","push","clearRect","progress","min","radius","beginPath","strokeStyle","lineWidth","arc","PI","stroke","eyesToMouthVerticalDistance","faceTiltedness","faceIsTilted","faceIsVertical","frontalFace","fullLeft","fullRight","isHeadInPosition","slice","p","shift","Boolean","nextValid","max","nextStage","speechService","playAudio","voiceOverAssetsPath","completedStage","totalStages","nextSound","resetScan","speechSynthesis","cancel","enableVoiceCommands","window","silentUtterance","SpeechSynthesisUtterance","volume","speak","FaceScan","userDetails","onComplete","onScanError","onRetry","isError","isSuccess","mediaRecorderRef","recordedBlobsRef","streamRef","showLoader","setShowLoader","modelReady","setModelReady","isScanning","setIsScanning","showGuideCard","setShowGuideCard","hasError","setHasError","setFaceScanId","generateUuid","useLocalConfig","email","deviceFocalLength","callbackUrl","supportedTypes","videoTypes","MediaRecorder","isTypeSupported","stopRecording","state","stop","uploadFinalVideo","videoFile","File","fileSize","size","fileSizeMB","estimatedDuration","round","videoData","video_size_mb","video_size_bytes","blob_count","estimated_duration_seconds","metaData","face_scan_id","focal_length","customer_store_url","scan_type","callback_url","handleScanTimeCapture","eventName","uploadStartTime","startTime","res","swan","fileUpload","faceScanFileUploader","file","objectKey","arrayMetaData","contentType","uploadEndTime","completionTime","uploadDuration","measurement","handlFaceScaneSocket","onOpen","handleWebSocketCapture","faceScanID","connection","onSuccess","onClose","startRecording","stream","srcObject","settings","getVideoTracks","getSettings","canvas","videoEl","play","drawFrame","save","translate","rotate","drawImage","restore","requestAnimationFrame","canvasStream","captureStream","mediaRecorderOptions","mimeType","mediaRecorder","ondataavailable","event","onstop","start","startScan","status","resultType","mediaDevices","getUserMedia","video","then","catch","getTracks","track","intervalId","setInterval","clearInterval","init","posthogPublicKey","api_host","posthogPublicHost","primaryColor","LoadingScreen","url","loader","id","crossOrigin","preload","position","zIndex","ref","transform","opacity","Drawer","open","anchor","reason","hideBackdrop"],"mappings":"qjBAOA,SAASA,GAAcC,MACtBA,EAAKC,aACLA,EAAYC,gBACZA,EAAeC,WACfA,EAAUC,cACVA,EAAaC,SACbA,EAAQC,OACRA,EAAMC,OACNA,IAEA,OAAc,IAAVP,EAEFQ,EAAA,MAAA,CAAKC,UAAU,sCAAsCC,MAAO,CAAEC,WAAYJ,GAAQG,OAAOE,MAAMC,iBAAiBR,SAAA,CAC/GG,SAAKC,UAAU,SAAQJ,SAAA,CACtBS,EAACC,EAAM,CAACC,SAAO,EAACC,eAAgBV,IAChCC,EAAA,KAAA,CACCE,MAAO,CACNQ,WAAYX,GAAQG,OAAOS,SAASC,mBAAqB,wBACzDC,SAAUd,GAAQG,OAAOS,SAASG,iBAAmB,OACrDC,MAAOhB,GAAQG,OAAOS,SAASK,cAAgB,OAC/CC,WAAYlB,GAAQG,OAAOS,SAASO,mBAAqB,UACzDrB,SAAA,CAAA,uCAEoC,OAErCJ,GACAa,EAAA,MAAA,CACCL,UAAU,wCACVC,MAAO,CACNQ,WAAYX,GAAQG,OAAOiB,YAAYC,sBAAwB,sBAC/DP,SAAUd,GAAQG,OAAOiB,YAAYE,oBAAsB,OAC3DN,MAAOhB,GAAQG,OAAOiB,YAAYG,iBAAmB,UACrDL,WAAYlB,GAAQG,OAAOiB,YAAYI,sBAAwB,UAC/D1B,SAAA,qBAKFF,GACAW,EAAA,MAAA,CACCL,UAAU,oBACVC,MAAO,CACNQ,WAAYX,GAAQG,OAAOiB,YAAYC,sBAAwB,sBAC/DP,SAAUd,GAAQG,OAAOiB,YAAYE,oBAAsB,OAC3DN,MAAO,UACPE,WAAYlB,GAAQG,OAAOiB,YAAYI,sBAAwB,UAC/D1B,SAAA,6EAKDF,GACDW,EAAA,MAAA,CAAKL,UAAW,8BACfK,EAAA,QAAA,CACCkB,IAAK1B,IAAW2B,EAAWC,KAAOC,EAAsBC,EACxDC,UAAQ,EACRC,MAAI,EACJC,UAAU,EACVC,OAAK,EACLC,aAAW,EACXhC,UAAU,2CACViC,UAAW,IAAMxC,GAAgB,GACjCyC,YAAa,IAAMzC,GAAgB,GACnC0C,QAAS,IAAMxC,GAAc,GAAK,aACvB,mFAKdC,KAMHS,EAAA,MAAA,CAAKL,UAAU,sCAAsCC,MAAO,CAAEC,WAAYJ,GAAQG,OAAOE,MAAMC,iBAAiBR,SAC/GG,EAAA,MAAA,CAAKC,UAAU,SAAQJ,SAAA,CACtBS,EAACC,EAAM,CAACC,SAAO,EAACC,eAAgBV,IAChCO,EAAA,KAAA,CACCL,UAAU,cACVC,MAAO,CACNQ,WAAYX,GAAQG,OAAOS,SAASC,mBAAqB,wBACzDC,SAAUd,GAAQG,OAAOS,SAASG,iBAAmB,OACrDC,MAAOhB,GAAQG,OAAOS,SAASK,cAAgB,OAC/CC,WAAYlB,GAAQG,OAAOS,SAASO,mBAAqB,UACzDrB,SAEAwC,EAAkB7C,KAGpBQ,EAAA,MAAA,CAAKC,UAAU,+DAA8DJ,SAAA,CAC5ES,EAAA,MAAA,CAAKL,UAAW,8BAAwC,IAAVT,EAAc,cAAgB,sBAAqBK,SAChGS,WAAOL,UAAU,2CAA2C+B,OAAK,EAACF,MAAI,EAACD,UAAQ,EAACI,aAAW,EAAApC,SAC1FS,EAAA,SAAA,CAAQkB,IAAK1B,IAAW2B,EAAWC,KAAOY,EAAmBC,KAAKC,QAAUF,EAAmBG,OAAOD,QAASE,KAAK,kBAGtHpC,EAAA,MAAA,CAAKL,UAAW,8BAAwC,IAAVT,EAAc,cAAgB,sBAAqBK,SAChGS,EAAA,QAAA,CAAOL,UAAU,2CAA2C+B,OAAK,EAACF,MAAI,EAACD,UAAQ,EAACI,aAAW,EAAApC,SAC1FS,EAAA,SAAA,CAAQkB,IAAK1B,IAAW2B,EAAWC,KAAOY,EAAmBC,KAAKI,KAAOL,EAAmBG,OAAOE,KAAMD,KAAK,kBAGhHpC,EAAA,MAAA,CAAKL,UAAW,8BAAwC,IAAVT,EAAc,cAAgB,sBAAqBK,SAChGS,EAAA,QAAA,CAAOL,UAAU,2CAA2C+B,OAAK,EAACF,MAAI,EAACD,UAAQ,EAACI,aAAW,EAAApC,SAC1FS,EAAA,SAAA,CAAQkB,IAAK1B,IAAW2B,EAAWC,KAAOY,EAAmBC,KAAKK,MAAQN,EAAmBG,OAAOG,MAAOF,KAAK,kBAGlHpC,EAAA,MAAA,CAAKL,UAAW,8BAAwC,IAAVT,EAAc,cAAgB,sBAAqBK,SAChGS,EAAA,QAAA,CAAOL,UAAU,2CAA2C+B,OAAK,EAACF,MAAI,EAACD,UAAQ,EAACI,aAAW,EAAApC,SAC1FS,YAAQkB,IAAK1B,IAAW2B,EAAWC,KAAOY,EAAmBC,KAAKM,MAAQP,EAAmBG,OAAOI,MAAOH,KAAK,yBAOvH,CCtHA,SAASI,GAAoBC,eAAEA,EAAcC,QAAEA,EAAOjD,OAAEA,IACvD,OACCO,SAAKL,UAAU,kFAAkFC,MAAO,CAAEC,WAAYJ,GAAQG,OAAOE,MAAMC,iBAAiBR,SAC3JG,EAAA,MAAA,CAAKC,UAAU,0DAAyDJ,SAAA,CACvES,EAACC,EAAM,CAACC,WAAQC,eAAgBV,IAChCC,SAAKC,UAAU,oCAAmCJ,SAAA,CACjDS,EAAA,KAAA,CACCL,UAAU,uCACVC,MAAO,CACNQ,WAAYX,GAAQG,OAAOS,SAASC,mBAAqB,wBACzDC,SAAUd,GAAQG,OAAOS,SAASG,iBAAmB,OACrDC,MAAOhB,GAAQG,OAAOS,SAASK,cAAgB,OAC/CC,WAAYlB,GAAQG,OAAOS,SAASO,mBAAqB,UACzDrB,SAAA,qBAIFS,EAAA,IAAA,CACCL,UAAU,cACVC,MAAO,CACNQ,WAAYX,GAAQG,OAAOiB,YAAYC,sBAAwB,sBAC/DP,SAAUd,GAAQG,OAAOiB,YAAYE,oBAAsB,OAC3DN,MAAOhB,GAAQG,OAAOiB,YAAYG,iBAAmB,UACrDL,WAAYlB,GAAQG,OAAOiB,YAAYI,sBAAwB,UAC/D1B,SAAA,2CAIFS,EAAC2C,EAAc,CACdC,SAAUF,EACV/C,UAAU,iEACVkD,WAAW,aACXC,YAAa9C,EAAC+C,EAAU,CAAA,GACxBC,WAAY,IAAMP,MAClBtC,eAAgBV,WAMtB,CCKA,IAAIwD,EAAuD,KAmC3D,SAASC,GAAYC,WACnBA,EAAUC,YACVA,EAAWC,eACXA,EAAcC,aACdA,EAAYC,iBACZA,EAAgBC,WAChBA,IASA,MAAMC,EAAcC,EAA0C,OACvDC,EAAWC,GAAgBC,EAAS,IACpCC,EAAkBC,GAAuBF,EAAS,IAClDG,EAAeC,GAAoBJ,GAAS,IAC5CK,EAAkBC,GAAuBN,GAAS,GACnDO,EAAeV,EAAO,MACtBW,EAAeX,EAAOC,GACtBW,EAAsBZ,EAAOI,GAC7BS,EAAoBb,EAAkB,IACtCc,EAAuBd,EAAO,IAC9Be,EAAoBf,EAAO,GAC3BgB,EAAkBhB,GAAO,GAKzBiB,EAA0BjB,EAAO,GAGjCkB,EACJ,mBAAmBC,KAAKC,UAAUC,YACV,aAAvBD,UAAUE,UAA2BF,UAAUG,eAAiB,EAE7DlD,EAAoBmD,EACxB,IAAM,CACJ,aACA,uBACA,wBACA,sBAEF,IAIIC,EAA8BD,EAClC,IA7FJ,SAAkBE,EAA4BC,GAC5C,IAAIC,EACJ,MAAO,IAAIC,KACLD,GAAOE,aAAaF,GACxBA,EAAQG,WAAW,IAAML,KAAMG,GAAOF,GAE1C,CAwFMK,CAAUC,IACRC,EAAQC,QAAQ,GAAGrC,6BAAuCmC,IACzD,KACL,CAACnC,IAIGsC,EAAqBC,EACzB,CACEC,EACAC,EACA/G,EACAgH,EACAC,EACAC,KAEA,MAAMC,EAAMC,KAAKD,MAGjB,KAAIA,EAAM1B,EAAwB4B,QAxCP,KAwC3B,CAIA5B,EAAwB4B,QAAUF,EAElC,IAEE,MAAMG,EAAOR,EAAc,IAAM,CAAC,EAAG,EAAG,GAClCS,EAAUT,EAAc,IAAM,CAAC,EAAG,EAAG,GACrCU,EAAWV,EAAc,IAAM,CAAC,EAAG,EAAG,GACtCW,EAAYX,EAAc,IAAM,CAAC,EAAG,EAAG,GACvCY,EAAaZ,EAAc,KAAO,CAAC,EAAG,EAAG,GAGzCa,EAAqBb,EACxBc,IAAKC,GAAcA,EAAG,IACtBC,OAAQC,GAAkBA,EAAQ,GAC/BC,EACJL,EAAmBM,OAAS,EACxBN,EAAmBO,OAAO,CAACC,EAAGC,IAAMD,EAAIC,EAAG,GAC3CT,EAAmBM,OACnB,EAEAI,EAAqBtB,EACxBa,IAAKC,GAAcA,EAAG,IACtBC,OAAQC,GAAkBA,EAAQ,GAC/BO,EACJD,EAAmBJ,OAAS,EACxBI,EAAmBH,OAAO,CAACC,EAAWC,IAAcD,EAAIC,EAAG,GAC3DC,EAAmBJ,OACnB,EAGAM,EAAcC,KAAKC,IAAIlB,EAAQ,GAAKC,EAAS,IAC7CkB,EAAcpB,EAAK,GAAKE,EAAS,GACjCmB,EAAapB,EAAQ,GAAKD,EAAK,GAGrC,IAAIsB,EAAmB,UACT,IAAV5I,EAAa4I,EAAmB,OACjB,IAAV5I,EAAa4I,EAAmB,QACtB,IAAV5I,IAAa4I,EAAmB,iBAEzC,MAAMnC,EAAU,CACdxC,aACAjE,QACA6I,WAAW,IAAIzB,MAAO0B,cACtBC,KAAMC,KAAKC,UAAU,CACnBjC,YACAC,cACArC,iBAAkBsC,EAClBc,aAAckB,WAAWlB,EAAamB,QAAQ,IAC9Cb,aAAcY,WAAWZ,EAAaa,QAAQ,IAC9CC,sBAAuBzB,EAAmBM,OAC1CoB,sBAAuBhB,EAAmBJ,OAC1CM,YAAaW,WAAWX,EAAYY,QAAQ,IAC5CG,iBAAkBJ,YACfR,EAAcH,GAAaY,QAAQ,IAEtCI,gBAAiBL,YAAYP,EAAaJ,GAAaY,QAAQ,IAC/DK,UAAWN,WAAW5B,EAAK,GAAG6B,QAAQ,IACtCM,aAAcP,WAAW3B,EAAQ,GAAG4B,QAAQ,IAC5CO,cAAeR,WAAW1B,EAAS,GAAG2B,QAAQ,IAC9CQ,eAAgBT,WAAWzB,EAAU,GAAG0B,QAAQ,IAChDS,gBAAiBV,WAAWxB,EAAW,GAAGyB,QAAQ,IAClDP,sBAIJ3C,EAA4BQ,EAC9B,CAAE,MAAOoD,GACPC,QAAQC,KAAK,kCAAmCF,EAClD,CAvEA,GAyEF,CAAC5F,EAAYgC,IAiBT+D,EAAoB,KAExBtD,EAAQC,QAAQ,GAAGrC,gCAA0C,CAC3DL,aACAjE,MAAOmF,EAAakC,QACpBwB,WAAW,IAAIzB,MAAO0B,cACtBmB,UAAWpH,EAAkBsC,EAAakC,WAI5Cd,WAAW,KACTtB,GAAoB,IACnB,OAwMCiF,EAAmBC,UACvB,IACEL,QAAQM,IAAI,oCACNC,EAAW,eACXC,IACNR,QAAQM,IAAI,6CACZ,MAAMG,OA7ZmBJ,WAC7B,GAAIpG,EAAmB,OAAOA,EAE9B,MAAMwG,QAAiBC,EAAcC,eACnCD,EAAcE,gBAAgBC,UAC9B,CACEC,QAAS,OACTC,UAAW,SAKTC,EAAcC,SAASC,cAAc,UAC3CF,EAAYG,MAAQC,EAAiBD,MACrCH,EAAYK,OAASD,EAAiBC,OACtC,MAAMC,EAAMN,EAAYO,WAAW,MAC/BD,IACFA,EAAIE,UAAY,QAChBF,EAAIG,SAAS,EAAG,EAAGT,EAAYG,MAAOH,EAAYK,SAEpD,UACQZ,EAASiB,cAAcV,EAC/B,CAAE,MAAOW,GAEP3B,QAAQM,IAAIqB,EAAG,qBACjB,CAGA,OADA1H,EAAoBwG,EACbA,GAiYoBmB,GACvB5B,QAAQM,IAAIG,EAAU,YAEtBhG,EAAY8C,QAAUkD,EACtBT,QAAQM,IAAI,4CACZrF,GAAiB,GACjBX,KACF,CAAE,MAAOuH,GACP7B,QAAQD,MAAM,gCAAiC8B,EACjD,GA8DF,OAhBAC,EAAU,KAER1B,IACO,KA9CiBC,WACxB,IACEpF,GAAiB,GACbR,EAAY8C,gBACR9C,EAAY8C,QAAQwE,UAC1BtH,EAAY8C,QAAU,KAE1B,CAAE,MAAOsE,GACP7B,QAAQD,MAAM,6BAA8B8B,EAC9C,SACEjH,EAAa,GACbG,EAAoB,GACpBM,EAAakC,QAAU,EACvBjC,EAAoBiC,QAAU,EAC9BhC,EAAkBgC,QAAU,EAC9B,GAgCEyE,KAED,IAEHF,EAAU,KACRzG,EAAakC,QAAU5C,GACtB,CAACA,IAEJmH,EAAU,KACRxG,EAAoBiC,QAAUzC,GAC7B,CAACA,IAEG,CACLmH,iBA3NuB5B,MACvB6B,EACAC,KAEA,GACG1H,EAAY8C,SACZ2E,EAAU3E,SACVvC,GACAE,EAIH,IACE,MAAMkH,QAAc3H,EAAY8C,QAAQmE,cAAcQ,EAAU3E,SAChE,GAAI6E,EAAMjE,OAAS,EAAG,CACpB,MAAMnB,EAAyB,GACzBC,EAAyB,GAS/B,GARAmF,EAAM,GAAGC,UAAUC,QAAQ,CAACC,EAAUC,KACpC,MAAMvE,EAAQsE,EAAStE,OAAS,EAC1BwE,EAAe,CAACF,EAASG,IAAM,EAAGH,EAASI,IAAM,EAAG1E,GACtDuE,GAAO,GAAIxF,EAAc4F,KAAKH,GAC7BxF,EAAc2F,KAAKH,KAItBN,GAAW5E,QAAS,CACtB,MAAM+D,EAAMa,EAAU5E,QAAQgE,WAAW,OACnCJ,MAAEA,EAAKE,OAAEA,GAAWc,EAAU5E,QACpC,GAAI+D,EAAK,CACPA,EAAIuB,UAAU,EAAG,EAAG1B,EAAOE,GAE3B,MAAMyB,EAAWpE,KAAKqE,IACpBzH,EAAoBiC,QA7OC,EA8OrB,GAEIyF,EAAkB,IAAT3B,EACfC,EAAI2B,YACJ3B,EAAI4B,YAAc,UAClB5B,EAAI6B,UAAY,EAChB7B,EAAI8B,IACFjC,EAAQ,EACRE,EAAS,EACT2B,EAAS,IACRtE,KAAK2E,GAAK,GACV3E,KAAK2E,GAAK,EAAe,EAAXP,EAAepE,KAAK2E,IAErC/B,EAAIgC,QACN,CACF,CAEA,MAAMpG,EA1Ga,EAAChH,EAAemM,KAQvC,GACEA,EARW,GAQK,GAFD,IAGfA,EARc,GAQK,GAHJ,IAIfA,EARe,GAQK,GAJL,GAMf,OAAO,EAGT,MAAMkB,EACJ,IAAOlB,EAZU,IAYY,GAAKA,EAblB,GAauC,IACvD,IAAOA,EAfQ,GAeY,GAAKA,EAhBlB,GAgBqC,IAE/CmB,GACH,IAAOnB,EAhBS,IAgBa,GAAKA,EAjBnB,GAiBwC,IACtDA,EArBS,GAqBO,IAClBkB,EAEIE,EAAeD,EAAiB,IAAOA,EAAiB,GAExDE,EACJhF,KAAKC,IAAI0D,EA1BK,GA0Bc,GAAKA,EAzBlB,GAyBsC,IACnD,GAAMkB,GACR7E,KAAKC,IAAI0D,EA1BO,GA0Bc,GAAKA,EAzBlB,IAyBwC,IACvD,GAAMkB,EAEJ9E,EAAc4D,EA/BJ,GA+BuB,GAAKA,EA9B3B,GA8B+C,GAC1DzD,EAAcyD,EAjCP,GAiCuB,GAAKA,EA/BxB,GA+B4C,GAGvDsB,EACJ/E,EAAc,GAAMH,GAAeG,EAAc,GAAMH,EACnDmF,EAAWhF,EAAc,IAAOH,EAChCoF,EALaxB,EAjCH,GAiCsB,GAAKA,EAlC9B,GAkC8C,GAK5B,IAAO5D,EAEtC,OAAQvI,GACN,KAAK,EAML,KAAK,EACH,OAAQuN,GAAgBC,GAAkBC,EAL5C,KAAK,EACH,OAAQF,GAAgBC,GAAkBE,EAC5C,KAAK,EACH,OAAQH,GAAgBC,GAAkBG,EAG5C,QACE,OAAO,IAsDWC,CAAiBzI,EAAakC,QAASP,GACnDG,EACJF,EAAc8G,MAAM,GAAG/F,OAAQgG,GAAMA,EAAE,GAAK,IAAK7F,QAAU,EAE7D5C,EAAkBgC,QAAQqF,KAAK1F,GAAaC,GACxC5B,EAAkBgC,QAAQY,OArQL,IAsQvB5C,EAAkBgC,QAAQ0G,QAI5B,GADmB1I,EAAkBgC,QAAQS,OAAOkG,SAAS/F,QAC3C,EAAG,CACnB,MAAMgG,EAAY7I,EAAoBiC,QAAU,EAChDxC,EAAoBoJ,EACtB,MACEpJ,EAAoB2D,KAAK0F,IAAI,EAAG9I,EAAoBiC,QAAU,IAIhE,GAAIjC,EAAoBiC,SAjRG,EAiRkC,CAC3DnD,MAEA,MAAMiK,EAAYhJ,EAAakC,QAAU,EACzCxC,EAAoB,GACpBQ,EAAkBgC,QAAU,GAC5B3C,EAAayJ,GACblJ,GAAoB,GAGpB,WAoBE,SAnBMmJ,EAAcC,UAClB,GAAGC,mCAGL5H,EAAQC,QACN,GAAGrC,wCACH,CACEL,aACAsK,eAAgBpJ,EAAakC,QAC7B8G,YACAtF,WAAW,IAAIzB,MAAO0B,cACtB0F,YAtSO,EAuSPvE,UAAWpH,EAAkBsC,EAAakC,WAI5B,IAAd8G,GAAmB9J,GACrBA,IAEE8J,GA9SO,EA+SThK,UACK,CAEL,IAAIsK,EAAY,KAChB,OAAQN,GACN,KAAK,EACHM,EAAY,WACZ,MACF,KAAK,EACHA,EAAY,YACZ,MACF,KAAK,EACHA,EAAY,YACZ,MACF,QACEA,EAAY,KAEZA,SACIL,EAAcC,UAClB,GAAGC,kBAAoCG,KAG3CzE,GACF,CACD,EA7CD,EA8CF,CAGApD,EACEE,EACAC,EACA5B,EAAakC,QACbL,EACAC,EACA7B,EAAoBiC,QAExB,CACF,CAAE,MAAOsE,GACP7B,QAAQD,MAAM,wBAAyB8B,EACzC,GAkFAlH,YACAC,eACAE,mBACAE,gBACA4J,UA/CgB,KAEhBhI,EAAQC,QAAQ,GAAGrC,oBAA8B,CAC/CL,aACAjE,MAAOmF,EAAakC,QACpBwB,WAAW,IAAIzB,MAAO0B,cACtBmB,UAAWpH,EAAkBsC,EAAakC,SAC1CzC,iBAAkBQ,EAAoBiC,UAGxC3C,EAAa,GACbG,EAAoB,GACpBI,GAAoB,GACpBE,EAAakC,QAAU,EACvBjC,EAAoBiC,QAAU,EAC9BhC,EAAkBgC,QAAU,GAC5B/B,EAAqB+B,QAAU,GAC/B9B,EAAkB8B,QAAU,EAC5B7B,EAAgB6B,SAAU,EACtBnC,EAAamC,UACfsH,gBAAgBC,SAChB1J,EAAamC,QAAU,OA2BzBwH,oBArT0B,KAI1B,GAHArJ,EAAgB6B,SAAU,EAGtB3B,GAAS,oBAAqBoJ,OAAQ,CAExC,MAAMC,EAAkB,IAAIC,yBAAyB,IACrDD,EAAgBE,OAAS,EACzBN,gBAAgBO,MAAMH,EACxB,GA6SA/E,oBACAhF,mBAEJ,OCjhBamK,EAAoC,EAAGC,cAAaC,aAAYC,cAAaC,UAAShP,SAAOiP,UAAQC,gBACjH,MAAMzD,EAAYxH,EAAgC,MAC5CyH,EAAYzH,EAAiC,MAC7CkL,EAAmBlL,EAA6B,MAChDmL,EAAmBnL,EAAsB,IACzCoL,EAAYpL,EAA2B,OACtCqL,EAAYC,GAAiBnL,GAAS,IACtCoL,EAAYC,GAAiBrL,GAAS,IACtCsL,EAAYC,GAAiBvL,GAAS,IACtCwL,EAAeC,GAAoBzL,GAAS,IAC5C1E,EAAcC,GAAmByE,GAAS,IAC1CxE,EAAYC,GAAiBuE,GAAS,IACtC0L,EAAUC,GAAe3L,GAAS,IAClCV,EAAYsM,IAAiB5L,EAAS6L,KACvCvP,GAAiBwP,EAAelQ,IAChCmQ,MAAEA,GAAKpQ,OAAEA,GAAMqQ,kBAAEA,GAAiBrM,WAAEA,GAAUsM,YAAEA,IAAgBxB,EAChEyB,GAAiB7K,EAAQ,IAAM8K,EAAWhJ,OAAQ5E,GAAS6N,cAAcC,gBAAgB9N,IAAQ,IAEjG+N,GAAgBpK,EAAY,KACjCiD,QAAQM,IAAI,yBACRsF,EAAiBrI,SAA8C,cAAnCqI,EAAiBrI,QAAQ6J,OACxDxB,EAAiBrI,QAAQ8J,OAE1BzB,EAAiBrI,QAAU,MACzB,IAEG+J,GAAmBjH,UAGxB,GAFA2F,GAAc,GACdhG,QAAQM,IAAI,sBACRuF,EAAiBtI,QAAS,CAC7B,GAAwC,IAApCsI,EAAiBtI,QAAQY,OAK5B,OAJA6B,QAAQD,MAAM,0BACdyG,GAAY,QACZR,GAAc,GAKf,MAAMuB,EAAY,IAAIC,KAAK3B,EAAiBtI,QAAS,GAAGpD,SAAmB,CAC1Ef,KAAM,eAEDqO,EAAWF,EAAUG,KACrBC,GAAcF,EAAQ,SAAkBpI,QAAQ,GAChDuI,EAAoBlJ,KAAKmJ,MAAMJ,EAAW,KAC1CK,EAAY,CACjBC,cAAe3I,WAAWuI,GAC1BK,iBAAkBP,EAClBQ,WAAYpC,EAAiBtI,QAAQY,OACrC+J,2BAA4BN,GAGvBO,EAAW,CAChB,CAAE3R,OAAQA,IACV,CAAE4R,aAAcjO,GAChB,CACCkO,aAAc,GAAGxB,MAElB,CAAEyB,mBAAoB9N,IACtB,CAAE+N,UAAW,aACb,CAAEC,aAAc1B,KAEjB2B,EAAsB,CACrBC,UAAW,GAAGlO,yBACdL,aACAyM,SACA3H,KAAMC,KAAKC,UAAU,CAAEgJ,WAAUL,gBAGlC,MAAMa,EAAkBrL,KAAKD,MAC7BoL,EAAsB,CACrBC,UAAW,GAAGlO,4BACdL,aACAyM,SACAgC,UAAWD,IAGZ,IACC,MAAME,QAAYC,EAAKC,WAAWC,qBAAqB,CACtDC,KAAM1B,EACN2B,UAAW/O,EACXyM,SACAuC,cAAehB,EACfiB,YAAa7B,EAAUnO,OAGlBiQ,EAAgB/L,KAAKD,MAG3BoL,EAAsB,CACrBC,UAAW,GAAGlO,+BACdL,aACAyM,SACA0C,eAAgBD,EAChBE,eAPsBF,EAAgBV,IAUvC3I,QAAQM,IAAI,sBAAuBuI,GACnCC,EAAKU,YAAYC,qBAAqB,CACrCtP,aACAuP,OAAQ,KACPC,EAAuB,CACtBjB,UAAW,GAAGlO,eACdoP,WAAYzP,EACZ0P,WAAY,OACZzQ,KAAM,0BACNwN,WAED5G,QAAQM,IAAI,2BAEbxH,QAAU+I,IACT8H,EAAuB,CACtBjB,UAAW,GAAGlO,eACdoP,WAAYzP,EACZ0P,WAAY,QACZzQ,KAAM,0BACNwN,WAED9N,GAAQ+I,IAETiI,UAAY7K,IACX0K,EAAuB,CACtBjB,UAAW,GAAGlO,eACdoP,WAAYzP,EACZ0P,WAAY,UACZzQ,KAAM,0BACNwN,WAEDkD,GAAU7K,IAEX8K,QAAS,KACR/J,QAAQM,IAAI,2BACZqJ,EAAuB,CACtBjB,UAAW,GAAGlO,eACdoP,WAAYzP,EACZ0P,WAAY,QACZzQ,KAAM,0BACNwN,aAIJ,CAAE,MAAO7G,GACRjH,GAAQiH,EACT,CACD,GAGKiK,GAAiBjN,EAAY,KAClCiD,QAAQM,IAAI,wCACZ,MAAM2J,EAAS/H,EAAU3E,SAAS2M,UAClC,IAAKD,EAAQ,OAGb,MACME,EADaF,EAAOG,iBAAiB,GACfC,eACtBlJ,MAAEA,EAAQC,EAAiBD,MAAKE,OAAEA,EAASD,EAAiBC,QAAW8I,EAEvEG,EAASrJ,SAASC,cAAc,UAChCI,EAAMgJ,EAAO/I,WAAW,MAG1BJ,EAAQE,GACXiJ,EAAOnJ,MAAQE,EACfiJ,EAAOjJ,OAASF,IAEhBmJ,EAAOnJ,MAAQA,EACfmJ,EAAOjJ,OAASA,GAGjB,MAAMkJ,EAAUtJ,SAASC,cAAc,SACvCqJ,EAAQL,UAAYD,EACpBM,EAAQ7R,OAAQ,EAChB6R,EAAQ5R,aAAc,EACtB4R,EAAQC,OAGR,MAAMC,EAAY,KAEbtJ,EAAQE,GACXC,GAAKoJ,OACLpJ,GAAKqJ,UAAUL,EAAOnJ,MAAO,GAC7BG,GAAKsJ,OAAOlM,KAAK2E,GAAK,GACtB/B,GAAKuJ,UAAUN,EAAS,EAAG,EAAGlJ,EAAQF,GACtCG,GAAKwJ,WAELxJ,GAAKuJ,UAAUN,EAAS,EAAG,EAAGpJ,EAAOE,GAEtC0J,sBAAsBN,IAEvBA,IAGA,MAAMO,EAAeV,EAAOW,cAAc,IACpCC,EAAuBnE,GAAe5I,OAAS,EAAI,CAAEgN,SAAUpE,GAAe,IAAO,CAAA,EAC3F,IAAIqE,EAEJ,IACCA,EAAgB,IAAInE,cAAc+D,EAAcE,EACjD,CAAE,MAAOvJ,GAIR,OAHA3B,QAAQD,MAAM,6BAA8B4B,GAC5C6E,GAAY,QACZR,GAAc,EAEf,CAEAH,EAAiBtI,QAAU,GAC3B6N,EAAcC,gBAAmBC,IAC5BA,GAAOrM,MAAQqM,EAAMrM,KAAKyI,KAAO,GAAK7B,EAAiBtI,SAC1DsI,EAAiBtI,QAAQqF,KAAK0I,EAAMrM,OAGtCmM,EAAcG,OAAS,KACtBvL,QAAQM,IAAI,kCAAmCuF,GAAkBtI,SAASY,QAC1EmJ,MAGD8D,EAAcI,MAAM,KACpB5F,EAAiBrI,QAAU6N,EAC3BpL,QAAQM,IAAI,yDACV,CAACyG,GAAgBO,MAEdrF,iBAAEA,GAAgBtH,UAAEA,GAASC,aAAEA,GAAYgK,UAAEA,GAAS5J,cAAEA,GAAakF,kBAAEA,IAAsBhG,EAAY,CAC9GC,aACAK,cACAH,eAAgB,KACf8M,MAED7M,aAAc,KACb4L,GAAc,IAEf3L,iBAAkB,KACjByF,QAAQM,IAAI,yDACZ0J,QAIIyB,GAAY1O,EAAY,KACxB/B,KACLJ,GAAa,GACb0J,EAAcC,UAAU,GAAGC,mCAC3B/H,WAAW,KACV2J,GAAc,GACd3J,WAAW,KACVyD,MACE,MACD,OACD,CAACtF,GAAcwL,EAAelG,GAAmBlF,KAE9CvB,GAAiBsD,EAAY,KAClCqJ,GAAc,GACdE,GAAiB,GACjBa,KACAvC,KACAa,MACAI,EAAiBtI,QAAU,GACvBqI,EAAiBrI,UACpBqI,EAAiBrI,QAAU,MAE5BiJ,GAAY,GACZ5L,IAAa,GACb6L,GAAcC,KACVxE,EAAU3E,SAAWuI,EAAUvI,UAClC2E,EAAU3E,QAAQ2M,UAAYpE,EAAUvI,QACxCyC,QAAQM,IAAI,wCAEX,CAAC1F,GAAcwL,EAAeE,EAAkBa,GAAevC,GAAW6B,KAEvE3N,GAAWmG,IAChBe,QAAQM,IAAIrB,EAAM,YAClBuG,IAAcvG,GACduH,GAAY,GACZR,GAAc,GACdM,GAAiB,GACjBmC,EAAsB,CACrBC,UAAW,GAAGlO,cACdL,aACAuR,OAAQ,SACR9E,SACA3H,KAAMC,KAAKC,UAAUF,MAIjB6K,GAAa7K,IAClBe,QAAQM,IAAIrB,EAAM,cACdA,GAA6B,iBAArBA,GAAM0M,YACjBlD,EAAsB,CACrBC,UAAW,GAAGlO,mCACdL,aACAuR,OAAQ,UACR9E,SACA3H,KAAMC,KAAKC,UAAUF,KAGvBsG,IAAatG,IAId6C,EAAU,KACT,IAAI4D,IAAWC,EAef,OAdI7J,UAAU8P,aAAaC,cAC1B/P,UAAU8P,aACRC,aAAa,CAAEC,MAAO1K,IACtB2K,KAAM9B,IACNnE,EAAUvI,QAAU0M,EAChB/H,EAAU3E,UACb2E,EAAU3E,QAAQ2M,UAAYD,EAC9BjK,QAAQM,IAAI,yBAGb0L,MAAOnK,GAAQ7B,QAAQD,MAAM,0BAA2B8B,IAIpD,KACFiE,EAAUvI,SACbuI,EAAUvI,QAAQ0O,YAAY3J,QAAS4J,GAAUA,EAAM7E,UAGvD,CAAC3B,EAASC,IAGb7D,EAAU,KACT,IAAKmE,IAAeE,EAAY,OAEhC,MAAMgG,EAAaC,YAAY,KAC9BnK,GAAiBC,EAAWC,IAC1B,KAGH,MAAO,IAAMkK,cAAcF,IACzB,CAAClK,GAAkBgE,EAAYE,IAiBlC,OATAnG,QAAQM,IAAI,eAAgB2F,EAAY,aAAcM,EAAU,eAAgBJ,EAAY,aAAcJ,GAE1GjE,EAAU,KACTlH,IAAa,GACT8K,GAAWC,IACf/I,EAAQ0P,KAAKC,EAAkB,CAAEC,SAAUC,IAC3C7P,EAAQC,QAAQ,eACd,CAAC6I,EAASC,IAETD,EACI1O,EAACwC,EAAmB,CAAC/C,OAAQU,KAGjCwO,EAEF3O,EAAA,MAAA,CAAKL,UAAU,6BAA6BC,MAAO,CAAEC,WAAYM,IAAgBP,OAAOE,MAAM4V,cAAcnW,SAE3GS,EAAC2V,EAAa,CAACC,IAAKzV,IAAgB0V,WAMtCnW,eACCM,EAAA,QAAA,CAAO8V,GAAG,eAAeC,YAAY,YAAYC,QAAQ,OAAOpW,MAAO,CAAEqW,SAAU,WAAYC,QAAQ,OAAUhV,IAAI,KAEpH6N,IAAeQ,GACfvP,SAAKL,UAAU,6BAA6BC,MAAO,CAAEC,WAAYM,IAAgBP,OAAOE,MAAM4V,cAAcnW,SAE3GS,EAAC2V,GAAcC,IAAKzV,IAAgB0V,WAKtC7V,EAAA,MAAA,CAAKL,UAAU,iGAAiGC,MAAO,CAAEC,WAAYM,IAAgBP,OAAOE,MAAMC,0BACjKC,EAAA,MAAA,CAAKL,UAAU,yCAAwCJ,SACtDG,EAAA,MAAA,CAAKC,UAAU,gBAAeJ,SAAA,CAC7BS,EAAA,QAAA,CACCmW,IAAKjL,EACL3J,UAAQ,EACRI,aAAW,EACXD,SACAyI,MAAOC,EAAiBD,MACxBE,OAAQD,EAAiBC,OACzB1K,UAAU,oDACVC,MAAO,CAAEwW,UAAW,gBAErBpW,EAAA,SAAA,CAAQmW,IAAKhL,EAAWhB,MAAOC,EAAiBD,MAAOE,OAAQD,EAAiBC,OAAQzK,MAAO,CAAEwW,UAAW,aAAcC,QAAS,eAMpItH,GAAcQ,GAAYvP,EAACwC,EAAmB,CAACE,QAASqM,EAAYtM,eAAgBA,GAAgBhD,OAAQU,KAG7GkP,IAAkBE,IAAaR,GAC/B/O,EAACsW,EAAM,CACNC,MAAI,EACJ5W,UAAU,8BACV6W,OAAO,SACPzD,QAAS,CAACuB,EAAOmC,OAKjBC,cAAY,EAAAnX,SAEZS,EAACf,EAAa,CAACC,MAAOyE,GAAWxE,aAAcA,EAAcC,gBAAiBA,EAAiBC,WAAYA,EAAYC,cAAeA,EAAeE,OAAQA,GAAQC,OAAQU,GAAcZ,SAC1LS,SAAKL,UAAU,uCAAsCJ,SACpDS,EAAC2C,EAAc,CACdC,SAAUmM,IAAe/K,GACzBrE,UAAU,kCACVkD,WA9EFsM,EAAmB,QAClBnL,GACE,QADoB,aA8ErBlB,YAAaqM,EAAanP,EAAC+C,EAAU,IAAM,GAC3CC,WAAYmM,EAAa1M,GAAiBgS,GAC1CtU,eAAgBA"}
|