{"product_id":"wifi-csi","title":"wifi csi (software)","description":"\u003cbody\u003e\n\n\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n    \u003ctitle\u003eWi-Fi CSI Program - SGP\u003c\/title\u003e\n    \n    \u003c!-- Google Fonts: Space Grotesk (Headers) and Inter (Body) --\u003e\n    \u003clink rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\"\u003e\n    \u003clink rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin\u003e\n    \u003clink href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;500;600\u0026amp;family=Space+Grotesk:wght@700;800\u0026amp;display=swap\" rel=\"stylesheet\"\u003e\n\n    \u003cstyle\u003e\n        :root {\n            \/* Neumorphic Color Palette *\/\n            --bg-color: #e0e5ec;\n            --text-main: #2d3748;\n            --text-muted: #718096;\n            --accent: #eab308; \/* Premium Amber\/Gold *\/\n            --success: #38a169;\n            --danger: #e53e3e;\n            \n            \/* Shadows (Extrusion\/Sunken Effects) *\/\n            --shadow-light: rgba(255, 255, 255, 0.8);\n            --shadow-dark: rgba(163, 177, 198, 0.6);\n            --neu-out: 9px 9px 16px var(--shadow-dark), -9px -9px 16px var(--shadow-light);\n            --neu-out-sm: 5px 5px 10px var(--shadow-dark), -5px -5px 10px var(--shadow-light);\n            --neu-in: inset 6px 6px 10px var(--shadow-dark), inset -6px -6px 10px var(--shadow-light);\n            \n            \/* Border Radii *\/\n            --radius-xl: 40px;\n            --radius-lg: 30px;\n            --radius-pill: 100px;\n            --transition: all 0.25s ease-in-out;\n        }\n\n        * { box-sizing: border-box; margin: 0; padding: 0; }\n\n        body {\n            background-color: var(--bg-color);\n            color: var(--text-main);\n            font-family: 'Inter', sans-serif;\n            line-height: 1.6;\n            -webkit-font-smoothing: antialiased;\n            padding: 20px;\n            overflow-x: hidden;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            min-height: 100vh;\n        }\n\n        h1, h2, h3, h4 {\n            font-family: 'Space Grotesk', sans-serif;\n            font-weight: 800;\n            color: var(--text-main);\n            margin-bottom: 15px;\n            letter-spacing: -0.5px;\n        }\n\n        .product-container { \n            max-width: 800px; \n            width: 100%;\n            margin: 40px auto; \n            padding-bottom: 60px; \n        }\n        \n        .neu-card { background: var(--bg-color); border-radius: var(--radius-xl); box-shadow: var(--neu-out); padding: 40px; margin-bottom: 40px; }\n        .neu-sunken { background: var(--bg-color); border-radius: var(--radius-lg); box-shadow: var(--neu-in); padding: 25px; }\n\n        \/* Header *\/\n        .header-section { text-align: center; margin-bottom: 40px; padding-top: 20px; }\n        .badge { \n            display: inline-block; \n            background: var(--bg-color); \n            box-shadow: var(--neu-in); \n            padding: 8px 20px; \n            border-radius: var(--radius-pill); \n            font-size: 14px; \n            font-weight: 700; \n            color: var(--danger); \n            text-transform: uppercase; \n            letter-spacing: 1px; \n            margin-bottom: 20px; \n        }\n        .main-title { font-size: clamp(36px, 6vw, 64px); background: linear-gradient(135deg, #2d3748 0%, #718096 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 20px; }\n        .subtitle { font-size: clamp(16px, 2.5vw, 22px); color: var(--text-main); font-weight: 600; margin: 0 auto 30px auto; }\n\n        \/* Terminal Window *\/\n        .terminal-window {\n            background: #1a202c;\n            border-radius: var(--radius-lg);\n            padding: 20px;\n            box-shadow: var(--neu-out);\n            font-family: monospace;\n            color: #a0aec0;\n            font-size: 14px;\n            margin-bottom: 40px;\n        }\n        .terminal-header {\n            display: flex;\n            gap: 8px;\n            margin-bottom: 20px;\n        }\n        .terminal-dot { width: 12px; height: 12px; border-radius: 50%; }\n        .dot-red { background: #fc8181; }\n        .dot-yellow { background: #f6e05e; }\n        .dot-green { background: #68d391; }\n        \n        .terminal-text span { color: #68d391; }\n        .terminal-text .warning { color: #f6e05e; }\n\n        \/* Text Content *\/\n        .content-text { font-size: 18px; color: var(--text-main); margin-bottom: 20px; text-align: justify; }\n        .highlight-box {\n            border-left: 4px solid var(--accent);\n            padding-left: 20px;\n            margin-top: 30px;\n        }\n\n        \/* Media Grid (Videos) *\/\n        .media-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 30px; margin-bottom: 40px; width: 100%; }\n        .video-container { border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--neu-out); position: relative; background: var(--bg-color); padding: 10px; }\n        .video-container video { width: 100%; aspect-ratio: 16\/9; object-fit: cover; border-radius: 20px; display: block; }\n\n        \/* Disclaimer *\/\n        .disclaimer { margin-top: 20px; text-align: center; }\n        .disclaimer h4 { color: var(--danger); font-size: 16px; margin-bottom: 10px; text-transform: uppercase; }\n        .disclaimer p { font-size: 14px; color: var(--text-muted); }\n\n        \/* --- SIMULATION HUD STYLES --- *\/\n        .sim-wrapper {\n            background: var(--bg-color);\n            border-radius: var(--radius-xl);\n            box-shadow: var(--neu-out);\n            padding: 15px;\n            margin-bottom: 40px;\n            width: 100%;\n        }\n        .sim-container {\n            position: relative;\n            width: 100%;\n            height: 450px;\n            background-color: #000;\n            font-family: 'Courier New', Courier, monospace;\n            color: #00ff33;\n            overflow: hidden;\n            border-radius: 25px;\n        }\n        .sim-container * { box-sizing: border-box; }\n        .sim-container #sim-canvas-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; }\n        .sim-container #tactical-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 10; pointer-events: none; background: radial-gradient(circle at center, transparent 40%, rgba(0, 10, 0, 0.8) 100%), linear-gradient(rgba(0, 255, 0, 0.03) 50%, rgba(0, 0, 0, 0.1) 50%); background-size: 100% 100%, 100% 4px; box-shadow: inset 0 0 100px rgba(0, 255, 0, 0.2); }\n        .sim-container .hud-element { position: absolute; background: rgba(0, 20, 0, 0.6); border: 1px solid #00ff33; padding: 10px; box-shadow: 0 0 10px rgba(0, 255, 51, 0.2), inset 0 0 10px rgba(0, 255, 51, 0.1); backdrop-filter: blur(2px); z-index: 20;}\n        .sim-container .hud-element::before, .sim-container .hud-element::after { content: ''; position: absolute; width: 6px; height: 6px; border-color: #00ff33; border-style: solid; }\n        .sim-container .top-left { top: 15px; left: 15px; border-left: 2px solid #00ff33; }\n        .sim-container .top-right { top: 15px; right: 15px; text-align: right; border-right: 2px solid #00ff33; }\n        .sim-container .bottom-left { bottom: 15px; left: 15px; }\n        .sim-container .bottom-right { bottom: 15px; right: 15px; width: 160px; }\n        .sim-container .blink { animation: sim-blinker 1.5s linear infinite; }\n        @keyframes sim-blinker { 50% { opacity: 0.3; } }\n        .sim-container h1, .sim-container h2, .sim-container h3 { margin: 0 0 5px 0; font-size: 12px; letter-spacing: 1px; text-transform: uppercase; color: #00ff33; font-family: 'Courier New', Courier, monospace;}\n        .sim-container p { margin: 2px 0; font-size: 10px; color: #00ff33; font-family: 'Courier New', Courier, monospace;}\n        .sim-container .crosshair { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 60px; height: 60px; z-index: 10; pointer-events: none; }\n        .sim-container .crosshair::before, .sim-container .crosshair::after { content: ''; position: absolute; background: rgba(0, 255, 51, 0.5); }\n        .sim-container .crosshair::before { top: 50%; left: 0; width: 100%; height: 1px; }\n        .sim-container .crosshair::after { top: 0; left: 50%; width: 1px; height: 100%; }\n        .sim-container .crosshair .circle { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 40px; height: 40px; border: 1px solid rgba(0, 255, 51, 0.3); border-radius: 50%; }\n        .sim-container .signal-bars { display: flex; align-items: flex-end; height: 25px; gap: 2px; margin-top: 5px; }\n        .sim-container .bar { flex: 1; background-color: #00ff33; width: 6px; animation: sim-signal-bounce 0.5s infinite alternate; opacity: 0.8; }\n        .sim-container .bar:nth-child(1) { animation-delay: 0.1s; }\n        .sim-container .bar:nth-child(2) { animation-delay: 0.3s; }\n        .sim-container .bar:nth-child(3) { animation-delay: 0.5s; }\n        .sim-container .bar:nth-child(4) { animation-delay: 0.2s; }\n        .sim-container .bar:nth-child(5) { animation-delay: 0.4s; }\n        .sim-container .bar:nth-child(6) { animation-delay: 0.6s; }\n        .sim-container .bar:nth-child(7) { animation-delay: 0.1s; }\n        .sim-container .bar:nth-child(8) { animation-delay: 0.5s; }\n        @keyframes sim-signal-bounce { 0% { height: 10%; } 100% { height: 100%; } }\n        .sim-container .controls-info { position: absolute; bottom: 15px; left: 50%; transform: translateX(-50%); text-align: center; font-size: 9px; opacity: 0.7; pointer-events: none; z-index: 10; width: 100%; }\n\n        \/* Animación Fade *\/\n        @keyframes fadeUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }\n    .reveal { animation: fadeUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards; }\n\n    @media (max-width: 768px) { .neu-card { padding: 25px; } .main-title { font-size: 38px; } }\n\u003c\/style\u003e\n\n\n\n\u003cdiv class=\"product-container reveal\"\u003e\n\n    \u003cheader class=\"header-section\"\u003e\n        \u003ch1 class=\"main-title\"\u003eWIFI CSI PROGRAM\u003c\/h1\u003e\n        \n        \u003cdiv class=\"neu-sunken\" style=\"display: inline-block; padding: 15px 30px;\"\u003e\n            \u003cdiv class=\"badge\"\u003eATTENTION\u003c\/div\u003e\n            \u003cp class=\"subtitle\" style=\"margin-bottom: 0;\"\u003e\n                With the purchase of the \u003cstrong\u003eSGP Card Mini\u003c\/strong\u003e you will have access to this code completely for \u003cstrong\u003eFREE\u003c\/strong\u003e.\n            \u003c\/p\u003e\n        \u003c\/div\u003e\n    \u003c\/header\u003e\n\n    \u003cdiv class=\"neu-card\"\u003e\n        \u003cp class=\"content-text\"\u003e\n            This code is made for those who want to test the functions of \u003cstrong\u003eWi-Fi CSI in an extreme way\u003c\/strong\u003e.\n        \u003c\/p\u003e\n        \n        \u003cp class=\"content-text\"\u003e\n            This program will detect the position of people in a room, \u003cstrong\u003eeven through walls\u003c\/strong\u003e. It is a fully editable code so that you can use it with any open-source device.\n        \u003c\/p\u003e\n\n        \u003cdiv class=\"highlight-box\"\u003e\n            \u003cp class=\"content-text\" style=\"font-size: 16px; margin-bottom: 0;\"\u003e\n                Although it is compatible with different hardware, \u003cstrong\u003ewe highly recommend using one of our devices\u003c\/strong\u003e. For better accuracy, the \u003cstrong\u003eSGP Card Mini\u003c\/strong\u003e should be used. In fact, it is recommended to use this code with \u003cstrong\u003e4 connected SGP Card Minis\u003c\/strong\u003e to achieve excellent detection in the environment.\n            \u003c\/p\u003e\n        \u003c\/div\u003e\n    \u003c\/div\u003e\n\n    \u003c!-- Live Simulation Box --\u003e\n    \u003cdiv class=\"sim-wrapper\"\u003e\n        \u003ch3 style=\"text-align: center; padding-top: 5px; margin-bottom: 15px;\"\u003eLIVE TRACKER SIMULATION\u003c\/h3\u003e\n        \u003cdiv class=\"sim-container\"\u003e\n            \u003cdiv id=\"sim-canvas-container\"\u003e\u003c\/div\u003e\n            \u003cdiv id=\"tactical-overlay\"\u003e\n                \u003cdiv class=\"crosshair\"\u003e\n                    \u003cdiv class=\"circle\"\u003e\u003c\/div\u003e\n                \u003c\/div\u003e\n            \u003c\/div\u003e\n\n            \u003c!-- UI \/ HUD Cyberpunk (Translated to English) --\u003e\n            \u003cdiv class=\"hud-element top-left\"\u003e\n                \u003ch1 class=\"blink\"\u003eC.S.I. SYSTEM ACTIVE\u003c\/h1\u003e\n                \u003cp\u003eTYPE: WIFI SIGNAL REFRACTION\u003c\/p\u003e\n                \u003cp\u003eFREQ: 5.8 GHz [HIGH BAND]\u003c\/p\u003e\n                \u003cp id=\"time-display\"\u003eT: 00:00:00:00\u003c\/p\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"hud-element top-right\"\u003e\n                \u003ch2\u003eTARGET LOCKED\u003c\/h2\u003e\n                \u003cp\u003eDISTANCE: \u003cspan id=\"dist-val\"\u003e4.2\u003c\/span\u003em\u003c\/p\u003e\n                \u003cp\u003eHEART RATE (EST.): \u003cspan class=\"blink\"\u003e78 BPM\u003c\/span\u003e\u003c\/p\u003e\n                \u003cp\u003eSTATUS: \u003cspan id=\"status-val\" style=\"color: #fff;\"\u003eMOTION DETECTED\u003c\/span\u003e\u003c\/p\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"hud-element bottom-left\"\u003e\n                \u003ch3\u003eSENSOR ARRAY\u003c\/h3\u003e\n                \u003cp\u003eMULTIPATH: COMPENSATED\u003c\/p\u003e\n                \u003cp\u003eAMBIENT NOISE: 12%\u003c\/p\u003e\n                \u003cp\u003eSPATIAL RES: SUB-MILLIMETER\u003c\/p\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"hud-element bottom-right\"\u003e\n                \u003ch3\u003eSUBCARRIER AMPLITUDE\u003c\/h3\u003e\n                \u003cdiv class=\"signal-bars\"\u003e\n                    \u003cdiv class=\"bar\"\u003e\u003c\/div\u003e\n\u003cdiv class=\"bar\"\u003e\u003c\/div\u003e\n\u003cdiv class=\"bar\"\u003e\u003c\/div\u003e\n                    \u003cdiv class=\"bar\"\u003e\u003c\/div\u003e\n\u003cdiv class=\"bar\"\u003e\u003c\/div\u003e\n                \u003c\/div\u003e\n                \u003cp style=\"margin-top:10px; font-size:0.6rem;\"\u003eREAL-TIME SPECTRUM ANALYSIS\u003c\/p\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"controls-info\"\u003e\n                [ LEFT CLICK: ROTATE ] - [ WHEEL: ZOOM ] - [ RIGHT CLICK: PAN ]\n            \u003c\/div\u003e\n        \u003c\/div\u003e\n    \u003c\/div\u003e\n\n    \u003c!-- Video Section --\u003e\n    \u003cdiv class=\"media-grid\"\u003e\n        \u003cdiv class=\"video-container\"\u003e\n            \u003cvideo autoplay loop muted playsinline controls\u003e\n                \u003csource src=\"https:\/\/cdn.shopify.com\/videos\/c\/o\/v\/1c5a6763b9fc410eb95d7ecca54f226c.mp4\" type=\"video\/mp4\"\u003e\n                Your browser does not support HTML5 video.\n            \u003c\/source\u003e\u003c\/video\u003e\n        \u003c\/div\u003e\n        \u003cdiv class=\"video-container\"\u003e\n            \u003cvideo autoplay loop muted playsinline controls\u003e\n                \u003csource src=\"https:\/\/cdn.shopify.com\/videos\/c\/o\/v\/a75a0ebd9c3d401d935577b1109b1f33.mp4\" type=\"video\/mp4\"\u003e\n                Your browser does not support HTML5 video.\n            \u003c\/source\u003e\u003c\/video\u003e\n        \u003c\/div\u003e\n    \u003c\/div\u003e\n\n    \u003cdiv class=\"disclaimer neu-sunken\"\u003e\n        \u003ch4\u003eResearch Use and Beta Phase\u003c\/h4\u003e\n        \u003cp\u003e\n            \u003cstrong\u003eATTENTION:\u003c\/strong\u003e The use of this code is purely for research. \n            Please note that it is in a \u003cstrong\u003ebeta\u003c\/strong\u003e state and may have minor errors depending on detection distances or electromagnetic environmental conditions.\n        \u003c\/p\u003e\n    \u003c\/div\u003e\n\n\u003c\/div\u003e\n\n\u003c!-- Three.js Scripts --\u003e\n\u003cscript src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/three.js\/r128\/three.min.js\"\u003e\u003c\/script\u003e\n\u003cscript src=\"https:\/\/cdn.jsdelivr.net\/npm\/three@0.128.0\/examples\/js\/controls\/OrbitControls.js\"\u003e\u003c\/script\u003e\n\n\u003cscript\u003e\n    \/\/ Self-invoking function to prevent global scope pollution\n    (function() {\n        const container = document.getElementById('sim-canvas-container');\n        if (!container) return;\n\n        \/\/ --- CONFIGURACIÓN DE THREE.JS ---\n        const scene = new THREE.Scene();\n        scene.fog = new THREE.FogExp2(0x001100, 0.05);\n\n        \/\/ Se usa container.clientWidth en lugar de window.innerWidth\n        const camera = new THREE.PerspectiveCamera(45, container.clientWidth \/ container.clientHeight, 0.1, 1000);\n        camera.position.set(0, 1.5, 4); \n\n        const renderer = new THREE.WebGLRenderer({ antialias: true });\n        renderer.setSize(container.clientWidth, container.clientHeight);\n        renderer.setClearColor(0x000500); \n        container.appendChild(renderer.domElement);\n\n        const controls = new THREE.OrbitControls(camera, renderer.domElement);\n        controls.target.set(0, 1, 0);\n        controls.enableDamping = true;\n        controls.dampingFactor = 0.05;\n        controls.maxPolarAngle = Math.PI \/ 2 + 0.1; \n\n        \/\/ --- ILUMINACIÓN ---\n        const ambientLight = new THREE.AmbientLight(0x003300); \n        scene.add(ambientLight);\n\n        const pointLight = new THREE.PointLight(0x00ff33, 2, 10);\n        pointLight.position.set(0, 3, 2);\n        scene.add(pointLight);\n\n        \/\/ --- ENTORNO CYBERPUNK (GRID) ---\n        const gridHelper = new THREE.GridHelper(20, 40, 0x00ff33, 0x003300);\n        gridHelper.position.y = 0;\n        gridHelper.material.transparent = true;\n        gridHelper.material.opacity = 0.4;\n        scene.add(gridHelper);\n\n        const circleGeo = new THREE.RingGeometry(1.5, 1.55, 32);\n        const circleMat = new THREE.MeshBasicMaterial({ color: 0x00ff33, side: THREE.DoubleSide, transparent: true, opacity: 0.3 });\n        const radarCircle = new THREE.Mesh(circleGeo, circleMat);\n        radarCircle.rotation.x = Math.PI \/ 2;\n        scene.add(radarCircle);\n\n        \/\/ --- ESTRUCTURA DEL ESQUELETO ---\n        const baseJoints = {\n            headTop: new THREE.Vector3(0, 1.85, 0),\n            head: new THREE.Vector3(0, 1.75, 0),\n            neck: new THREE.Vector3(0, 1.6, 0),\n            shoulderL: new THREE.Vector3(-0.25, 1.5, 0),\n            shoulderR: new THREE.Vector3(0.25, 1.5, 0),\n            elbowL: new THREE.Vector3(-0.35, 1.15, 0),\n            elbowR: new THREE.Vector3(0.35, 1.15, 0),\n            handL: new THREE.Vector3(-0.35, 0.8, 0),\n            handR: new THREE.Vector3(0.35, 0.8, 0),\n            spineTop: new THREE.Vector3(0, 1.4, 0),\n            spineMid: new THREE.Vector3(0, 1.2, 0),\n            pelvis: new THREE.Vector3(0, 0.95, 0),\n            hipL: new THREE.Vector3(-0.15, 0.9, 0),\n            hipR: new THREE.Vector3(0.15, 0.9, 0),\n            kneeL: new THREE.Vector3(-0.15, 0.45, 0),\n            kneeR: new THREE.Vector3(0.15, 0.45, 0),\n            footL: new THREE.Vector3(-0.15, 0.05, 0),\n            footR: new THREE.Vector3(0.15, 0.05, 0),\n        };\n\n        const joints = {};\n        const targetJoints = {};\n        for (let key in baseJoints) {\n            joints[key] = baseJoints[key].clone();\n            targetJoints[key] = baseJoints[key].clone();\n        }\n\n        const bodyVolumes = [\n            { start: 'headTop', end: 'head', radius: 0.12, type: 'head' }, \n            { start: 'head', end: 'neck', radius: 0.08, type: 'neck' }, \n            { start: 'neck', end: 'spineTop', radius: 0.1, type: 'torso' }, \n            { start: 'spineTop', end: 'spineMid', radius: 0.12, type: 'torso' }, \n            { start: 'spineMid', end: 'pelvis', radius: 0.13, type: 'torso' }, \n            { start: 'spineTop', end: 'shoulderL', radius: 0.09, type: 'torso' }, \n            { start: 'shoulderL', end: 'elbowL', radius: 0.07, type: 'arm' }, \n            { start: 'elbowL', end: 'handL', radius: 0.06, type: 'arm' }, \n            { start: 'spineTop', end: 'shoulderR', radius: 0.09, type: 'torso' }, \n            { start: 'shoulderR', end: 'elbowR', radius: 0.07, type: 'arm' }, \n            { start: 'elbowR', end: 'handR', radius: 0.06, type: 'arm' }, \n            { start: 'pelvis', end: 'hipL', radius: 0.11, type: 'leg' }, \n            { start: 'hipL', end: 'kneeL', radius: 0.09, type: 'leg' }, \n            { start: 'kneeL', end: 'footL', radius: 0.08, type: 'leg' }, \n            { start: 'pelvis', end: 'hipR', radius: 0.11, type: 'leg' }, \n            { start: 'hipR', end: 'kneeR', radius: 0.09, type: 'leg' }, \n            { start: 'kneeR', end: 'footR', radius: 0.08, type: 'leg' }, \n        ];\n\n        \/\/ --- NUBE DE PUNTOS CSI ---\n        const particlesCount = 12000; \n        const particlesGeo = new THREE.BufferGeometry();\n        const particlesPos = new Float32Array(particlesCount * 3);\n        const particlesColor = new Float32Array(particlesCount * 3); \n        \n        for(let i=0; i \u003c particlesCount * 3; i+=3) {\n            particlesColor[i] = 0;\n            particlesColor[i+1] = 0.5;\n            particlesColor[i+2] = 0;\n        }\n\n        particlesGeo.setAttribute('position', new THREE.BufferAttribute(particlesPos, 3));\n        particlesGeo.setAttribute('color', new THREE.BufferAttribute(particlesColor, 3));\n\n        const particlesMat = new THREE.PointsMaterial({\n            size: 0.015, \n            vertexColors: true,\n            transparent: true,\n            opacity: 0.7,\n            blending: THREE.AdditiveBlending\n        });\n\n        const particleSystem = new THREE.Points(particlesGeo, particlesMat);\n        scene.add(particleSystem);\n\n        let clock = new THREE.Clock();\n        let currentState = 'walk';\n        let lastStateChange = 0;\n        let stateDuration = 5; \n\n        function getRandomPointInCylinder(start, end, radius) {\n            const axis = new THREE.Vector3().subVectors(end, start);\n            const len = axis.length();\n            axis.normalize();\n\n            let perp = new THREE.Vector3(1, 0, 0);\n            if (Math.abs(axis.x) \u003e 0.9) perp = new THREE.Vector3(0, 1, 0);\n            perp.cross(axis).normalize();\n\n            const perp2 = new THREE.Vector3().crossVectors(axis, perp).normalize();\n\n            const angle = Math.random() * Math.PI * 2;\n            const r = Math.sqrt(Math.random()) * radius; \n            const h = Math.random() * len; \n\n            const point = new THREE.Vector3().copy(start)\n                .addScaledVector(axis, h)\n                .addScaledVector(perp, r * Math.cos(angle))\n                .addScaledVector(perp2, r * Math.sin(angle));\n            \n            return point;\n        }\n\n        function getRandomPointInSphere(center, radius) {\n            const u = Math.random();\n            const v = Math.random();\n            const theta = u * 2.0 * Math.PI;\n            const phi = Math.acos(2.0 * v - 1.0);\n            const r = Math.cbrt(Math.random()) * radius;\n            const sinTheta = Math.sin(theta);\n            const cosTheta = Math.cos(theta);\n            const sinPhi = Math.sin(phi);\n            const cosPhi = Math.cos(phi);\n            const x = r * sinPhi * cosTheta;\n            const y = r * sinPhi * sinTheta;\n            const z = r * cosPhi;\n            return new THREE.Vector3(center.x + x, center.y + y, center.z + z);\n        }\n\n        function updateSkeleton(time) {\n            if (time - lastStateChange \u003e stateDuration) {\n                lastStateChange = time;\n                const rand = Math.random();\n                const statusElement = document.getElementById('status-val');\n                \n                if (rand \u003c 0.35) {\n                    currentState = 'walk';\n                    stateDuration = 4 + Math.random() * 5; \n                    statusElement.innerText = \"MOTION (WALKING)\";\n                } else if (rand \u003c 0.55) {\n                    currentState = 'wave';\n                    stateDuration = 3 + Math.random() * 2; \n                    statusElement.innerText = \"GESTURE: WAVING\";\n                } else if (rand \u003c 0.8) {\n                    currentState = 'idle';\n                    stateDuration = 4 + Math.random() * 4; \n                    statusElement.innerText = \"IDLE (OBSERVING)\";\n                } else {\n                    currentState = 'point';\n                    stateDuration = 2.5 + Math.random() * 2; \n                    statusElement.innerText = \"GESTURE: POINTING\";\n                }\n            }\n\n            for (let key in targetJoints) {\n                targetJoints[key].copy(baseJoints[key]);\n            }\n\n            const breath = Math.sin(time * 2) * 0.015;\n            targetJoints.spineTop.y += breath;\n            targetJoints.spineMid.y += breath * 0.5;\n            targetJoints.shoulderL.y += breath;\n            targetJoints.shoulderR.y += breath;\n            targetJoints.neck.y += breath;\n            targetJoints.head.y += breath;\n            targetJoints.headTop.y += breath;\n\n            if (currentState === 'walk') {\n                const walkSpeed = 3;\n                const t = time * walkSpeed;\n                const bounce = Math.abs(Math.sin(t)) * 0.05;\n                \n                for (let key in targetJoints) targetJoints[key].y += bounce;\n\n                targetJoints.handL.z += Math.sin(t) * 0.3;\n                targetJoints.elbowL.z += Math.sin(t) * 0.15;\n                targetJoints.handR.z += Math.sin(t + Math.PI) * 0.3;\n                targetJoints.elbowR.z += Math.sin(t + Math.PI) * 0.15;\n\n                targetJoints.footL.z += Math.sin(t + Math.PI) * 0.3;\n                targetJoints.footL.y += Math.max(0, Math.sin(t + Math.PI) * 0.1);\n                targetJoints.kneeL.z += Math.sin(t + Math.PI) * 0.15 + 0.1;\n\n                targetJoints.footR.z += Math.sin(t) * 0.3;\n                targetJoints.footR.y += Math.max(0, Math.sin(t) * 0.1);\n                targetJoints.kneeR.z += Math.sin(t) * 0.15 + 0.1;\n\n                targetJoints.shoulderL.z -= Math.sin(t) * 0.05;\n                targetJoints.shoulderR.z -= Math.sin(t + Math.PI) * 0.05;\n            } \n            else if (currentState === 'wave') {\n                targetJoints.shoulderR.y += 0.05; \n                targetJoints.elbowR.set(0.45, 1.45, 0); \n                targetJoints.handR.set(0.5, 1.8, Math.sin(time * 12) * 0.15); \n                \n                targetJoints.head.x += 0.05;\n                targetJoints.headTop.x += 0.08;\n                targetJoints.headTop.y -= 0.02; \n            }\n            else if (currentState === 'idle') {\n                const lookX = Math.sin(time * 1.2) * 0.08; \n                const lookZ = Math.cos(time * 0.8) * 0.05; \n                targetJoints.head.x += lookX;\n                targetJoints.headTop.x += lookX * 1.5;\n                targetJoints.head.z += lookZ;\n                targetJoints.headTop.z += lookZ * 1.5;\n                \n                targetJoints.handL.x -= 0.03;\n                targetJoints.handL.z -= 0.05;\n                targetJoints.handR.x += 0.03;\n                targetJoints.handR.z -= 0.05;\n            }\n            else if (currentState === 'point') {\n                targetJoints.shoulderL.z += 0.1; \n                targetJoints.shoulderL.y += 0.05;\n                targetJoints.elbowL.set(-0.3, 1.3, 0.2); \n                targetJoints.handL.set(-0.2, 1.35, 0.6); \n                \n                targetJoints.spineTop.z += 0.05;\n                targetJoints.head.z += 0.08;\n                targetJoints.headTop.z += 0.1;\n                targetJoints.headTop.y -= 0.02;\n            }\n\n            const lerpFactor = 0.08; \n            for (let key in joints) {\n                joints[key].lerp(targetJoints[key], lerpFactor);\n            }\n\n            const pPositions = particleSystem.geometry.attributes.position.array;\n            const pColors = particleSystem.geometry.attributes.color.array;\n            const pointsPerVolume = Math.floor(particlesCount \/ bodyVolumes.length);\n            let pIndex = 0;\n\n            bodyVolumes.forEach(vol =\u003e {\n                const start = joints[vol.start];\n                const end = joints[vol.end];\n                \n                for(let j=0; j \u003c pointsPerVolume; j++) {\n                    let point;\n                    if (vol.type === 'head') {\n                         point = getRandomPointInSphere(start, vol.radius); \n                    } else {\n                         point = getRandomPointInCylinder(start, end, vol.radius);\n                    }\n                    \n                    let px = point.x;\n                    let py = point.y;\n                    let pz = point.z;\n\n                    const rfWave1 = Math.sin(px * 3 + pz * 1.5 + time * 4);\n                    const rfWave2 = Math.cos(py * 4 - time * 2);\n                    const distFromCenter = Math.sqrt(px*px + pz*pz);\n                    const rfWave3 = Math.sin(distFromCenter * 5 - time * 3);\n\n                    let combinedRF = (rfWave1 + rfWave2 + rfWave3) \/ 3;\n                    combinedRF = (combinedRF + 1) \/ 2; \n\n                    const waveIntensity = Math.pow(combinedRF, 3);\n\n                    let r = 0.0;\n                    let g = 0.2 + (Math.random() * 0.1); \n                    let b = 0.05;\n\n                    g += waveIntensity * 0.8; \n                    b += waveIntensity * 0.4; \n                    r += waveIntensity * 0.1; \n\n                    const noise = 0.025; \n                    px += (Math.random() - 0.5) * noise;\n                    py += (Math.random() - 0.5) * noise;\n                    pz += (Math.random() - 0.5) * noise;\n\n                    if(waveIntensity \u003e 0.85 \u0026\u0026 Math.random() \u003e 0.98) {\n                         px += (Math.random() - 0.5) * 0.2;\n                    }\n\n                    pPositions[pIndex] = px;\n                    pPositions[pIndex+1] = py;\n                    pPositions[pIndex+2] = pz;\n                    \n                    pColors[pIndex] = r;\n                    pColors[pIndex+1] = g;\n                    pColors[pIndex+2] = b;\n\n                    pIndex += 3;\n                }\n            });\n            while (pIndex \u003c particlesCount * 3) { \n                pPositions[pIndex] = 0; \n                pColors[pIndex] = 0; \n                pIndex++;\n            }\n            particleSystem.geometry.attributes.position.needsUpdate = true;\n            particleSystem.geometry.attributes.color.needsUpdate = true; \n        }\n\n        function updateHUD(time) {\n            const millis = Math.floor(time * 1000).toString().padStart(4, '0').substring(0, 2);\n            document.getElementById('time-display').innerText = `T: 00:${Math.floor(time\/60).toString().padStart(2,'0')}:${(Math.floor(time)%60).toString().padStart(2,'0')}:${millis}`;\n\n            if(Math.random() \u003e 0.95) {\n                const mockDist = (4.0 + Math.random() * 0.5).toFixed(2);\n                document.getElementById('dist-val').innerText = mockDist;\n            }\n        }\n\n        function animate() {\n            requestAnimationFrame(animate);\n            \n            const elapsedTime = clock.getElapsedTime();\n            \n            updateSkeleton(elapsedTime);\n            updateHUD(elapsedTime);\n            \n            controls.update();\n\n            radarCircle.scale.x = 1 + Math.sin(elapsedTime * 2) * 0.02;\n            radarCircle.scale.y = 1 + Math.sin(elapsedTime * 2) * 0.02;\n\n            renderer.render(scene, camera);\n        }\n\n        window.addEventListener('resize', () =\u003e {\n            camera.aspect = container.clientWidth \/ container.clientHeight;\n            camera.updateProjectionMatrix();\n            renderer.setSize(container.clientWidth, container.clientHeight);\n        });\n\n        animate();\n    })();\n\u003c\/script\u003e\n\n\n\u003c\/body\u003e","brand":"sgpstore","offers":[{"title":"Default Title","offer_id":57872990863692,"sku":null,"price":49.0,"currency_code":"EUR","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/1057\/9807\/4700\/files\/rn-image_picker_lib_temp_c2ed273f-a959-43a2-ac9a-fe663cb01a04.png?v=1782242329","url":"https:\/\/sgpstore.net\/products\/wifi-csi","provider":"sgpstore","version":"1.0","type":"link"}