{"id":2,"date":"2020-11-28T14:30:10","date_gmt":"2020-11-28T14:30:10","guid":{"rendered":"http:\/\/lebanidze.com\/?page_id=2"},"modified":"2025-11-11T05:48:03","modified_gmt":"2025-11-11T05:48:03","slug":"sample-page","status":"publish","type":"page","link":"https:\/\/lebanidze.com\/","title":{"rendered":"Home page"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"2\" class=\"elementor elementor-2\">\n\t\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-332ebab elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"332ebab\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-ad1de5e\" data-id=\"ad1de5e\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-63b31ff elementor-widget elementor-widget-html\" data-id=\"63b31ff\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<!-- ========================================= -->\n<!-- MODULE #1: GLOBAL + PER-SCENE UI\/CONTROL -->\n<!-- (Including model drag + floating logic)   -->\n<!-- ========================================= -->\n\n<script async src=\"https:\/\/unpkg.com\/es-module-shims@1.6.3\/dist\/es-module-shims.js\"><\/script>\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/dat-gui\/0.7.9\/dat.gui.min.js\"><\/script>\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/stats.js@0.17.0\/build\/stats.min.js\"><\/script>\n\n<!-- Optional: Import map if you use ES modules for Three.js -->\n<script type=\"importmap\">\n{\n  \"imports\": {\n    \"three\": \"https:\/\/unpkg.com\/three@0.152.0\/build\/three.module.js\",\n    \"three\/addons\/\": \"https:\/\/unpkg.com\/three@0.152.0\/examples\/jsm\/\"\n  }\n}\n<\/script>\n\n<script type=\"module\">\nimport * as THREE from 'three';\nimport { GLTFLoader } from 'three\/addons\/loaders\/GLTFLoader.js';\n\n\/\/ NEW: Import post-processing modules for chromatic aberration\nimport { EffectComposer } from 'three\/addons\/postprocessing\/EffectComposer.js';\nimport { RenderPass } from 'three\/addons\/postprocessing\/RenderPass.js';\nimport { ShaderPass } from 'three\/addons\/postprocessing\/ShaderPass.js';\nimport { RGBShiftShader } from 'three\/addons\/shaders\/RGBShiftShader.js';\n\nconst mySystem = (() => {\n  \/\/ ========== 1) debugMode + param objects ==========\n  const debugMode = 0; \/\/ 0 = off, 1 = on\n\n  const params = {\n    global: {\n      fogColor: '#000000',\n      fogNear: 12.1,\n      fogFar: 18,\n\n      cameraX: -0.1,\n      cameraY: -0.1,\n      cameraZ: 16,\n\n      emissiveStartAngle: 99,\n      emissiveEndAngle: 171,\n\n      ambientColor: '#ffffff',\n      ambientIntensity: 0.1,\n\n      directionalColor: '#ffffff',\n      directionalIntensity: 0.0,\n      directionalPosX: 5,\n      directionalPosY: 10,\n      directionalPosZ: 7.5\n    },\n\n    scene1: {\n      modelScale: 3.6,\n      spotLightColor: '#ffffff',\n      spotLightIntensity: 4.4,\n      spotLightAngle: 0.35,\n      spotLightPenumbra: 0.7,\n      spotLightDecay: 2,\n      spotLightFocus: 1,\n      spotLightDistance: 50,\n      spotLightPosX: 2.5,\n      spotLightPosY: 5.0,\n      spotLightPosZ: 2.5,\n      showSpotlightHelper: false\n    },\n\n    scene2: {\n      modelScale: 0.75,\n      spotLightColor: '#ffffff',\n      spotLightIntensity: 220,\n      spotLightAngle: 0.4,\n      spotLightPenumbra: 0.5,\n      spotLightDecay: 1.5,\n      spotLightFocus: 1,\n      spotLightDistance: 40,\n      spotLightPosX: -11,\n      spotLightPosY: -22,\n      spotLightPosZ: 5.0,\n      showSpotlightHelper: false\n    },\n\n    \/\/ Scene 4 (Lunar, e.g.)\n    scene4: {\n      modelScale: 2,\n      spotLightColor: '#ffffff',\n      spotLightIntensity: 1,\n      spotLightAngle: 0.41,\n      spotLightPenumbra: 0.5,\n      spotLightDecay: 1.5,\n      spotLightFocus: 1,\n      spotLightDistance: 50,\n      spotLightPosX: 0,\n      spotLightPosY: 8.3,\n      spotLightPosZ: 4.0,\n      showSpotlightHelper: false\n    },\n\n    scene3: {\n      modelScale: 1.0,\n      spotLightColor: '#ffffff',\n      spotLightIntensity: 100,\n      spotLightAngle: 0.45,\n      spotLightPenumbra: 0.5,\n      spotLightDecay: 2,\n      spotLightFocus: 1,\n      spotLightDistance: 50,\n      spotLightPosX: 0,\n      spotLightPosY: 5,\n      spotLightPosZ: 0,\n      showSpotlightHelper: false\n    },\n\n    scene5: {\n      modelScale: 2.7,\n      spotLightColor: '#ffffff',\n      spotLightIntensity: 11,\n      spotLightAngle: 0.22,\n      spotLightPenumbra: 1,\n      spotLightDecay: 1.8,\n      spotLightFocus: 1,\n      spotLightDistance: 28,\n      spotLightPosX: -6.2,\n      spotLightPosY: -8,\n      spotLightPosZ: 15,\n      showSpotlightHelper: false\n    },\n\n    scene6: {\n      modelScale: 1.5,\n      spotLightColor: '#ffffff',\n      spotLightIntensity: 200,\n      spotLightAngle: 0.4,\n      spotLightPenumbra: 0.5,\n      spotLightDecay: 1,\n      spotLightFocus: 1,\n      spotLightDistance: 40,\n      spotLightPosX: -2,\n      spotLightPosY: 10,\n      spotLightPosZ: -2,\n      showSpotlightHelper: false\n    },\n\n    scene7: {\n      modelScale: 2.75,\n      spotLightColor: '#ffffff',\n      spotLightIntensity: 10,\n      spotLightAngle: 0.2,\n      spotLightPenumbra: 0.3,\n      spotLightDecay: 2,\n      spotLightFocus: 1,\n      spotLightDistance: 50,\n      spotLightPosX: 5,\n      spotLightPosY: 10,\n      spotLightPosZ: 5,\n      showSpotlightHelper: false\n    }\n  };\n\n  \/\/ 2) Fix emissive angle ordering if needed\n  function fixAngleOrder() {\n    const g = params.global;\n    if (g.emissiveStartAngle > g.emissiveEndAngle) {\n      const temp = g.emissiveStartAngle;\n      g.emissiveStartAngle = g.emissiveEndAngle;\n      g.emissiveEndAngle = temp;\n    }\n  }\n\n  \/\/ 3) The update\/notify system\n  const updateListeners = [];\n  function addUpdateListener(callback) {\n    updateListeners.push(callback);\n  }\n  function notifyUpdate() {\n    updateListeners.forEach((cb) => cb(params));\n  }\n\n  \/\/ ========== 4) Standardized Sliders ==========\n  function createStandardSlider(folder, sceneObj, propKey, options = {}) {\n    const { name = propKey, min, max, step, color = false } = options;\n    if (color) {\n      folder.addColor(sceneObj, propKey)\n        .name(name)\n        .onChange(() => notifyUpdate());\n    } else if (typeof min === 'number' && typeof max === 'number') {\n      folder.add(sceneObj, propKey, min, max, step)\n        .name(name)\n        .onChange(() => notifyUpdate());\n    } else {\n      folder.add(sceneObj, propKey)\n        .name(name)\n        .onChange(() => notifyUpdate());\n    }\n  }\n\n  function addCommonSceneSliders(folder, sceneObj) {\n    createStandardSlider(folder, sceneObj, 'modelScale', {\n      name: 'Model Scale',\n      min: 0.1, max: 5, step: 0.1\n    });\n    createStandardSlider(folder, sceneObj, 'spotLightColor', {\n      name: 'SpotLight Color',\n      color: true\n    });\n    createStandardSlider(folder, sceneObj, 'spotLightIntensity', {\n      name: 'Spot Intensity',\n      min: 0, max: 500\n    });\n    createStandardSlider(folder, sceneObj, 'spotLightAngle', {\n      name: 'Spot Angle',\n      min: 0, max: Math.PI \/ 2, step: 0.01\n    });\n    createStandardSlider(folder, sceneObj, 'spotLightPenumbra', {\n      name: 'Penumbra',\n      min: 0, max: 1, step: 0.01\n    });\n    createStandardSlider(folder, sceneObj, 'spotLightDecay', {\n      name: 'Decay',\n      min: 1, max: 2, step: 0.01\n    });\n    createStandardSlider(folder, sceneObj, 'spotLightFocus', {\n      name: 'Focus',\n      min: 0, max: 1, step: 0.01\n    });\n    createStandardSlider(folder, sceneObj, 'spotLightDistance', {\n      name: 'Distance',\n      min: 0, max: 50\n    });\n    createStandardSlider(folder, sceneObj, 'spotLightPosX', {\n      name: 'Spot Pos X',\n      min: -50, max: 50\n    });\n    createStandardSlider(folder, sceneObj, 'spotLightPosY', {\n      name: 'Spot Pos Y',\n      min: -50, max: 50\n    });\n    createStandardSlider(folder, sceneObj, 'spotLightPosZ', {\n      name: 'Spot Pos Z',\n      min: -50, max: 50\n    });\n    folder.add(sceneObj, 'showSpotlightHelper')\n      .name('Show Helper')\n      .onChange(() => notifyUpdate());\n  }\n\n  function addSceneToGUI(gui, sceneName) {\n    if (sceneName === 'global') return; \/\/ skip the global param block\n\n    const sceneParams = params[sceneName];\n    if (!sceneParams) return;\n\n    const sceneFolder = gui.addFolder(`Scene: ${sceneName}`);\n    addCommonSceneSliders(sceneFolder, sceneParams);\n\n    sceneFolder.open();\n  }\n\n  \/\/ ========== 5) Reusable Utility Functions ==========\n  \/\/ 5A) Camera\/Lighting\/Loading\n  function createCamera(container) {\n    const camera = new THREE.PerspectiveCamera(\n      30,\n      container.offsetWidth \/ container.offsetHeight,\n      0.25,\n      20\n    );\n    return camera;\n  }\n\n  function updateCameraPosition(camera, globalParams) {\n    camera.position.set(\n      globalParams.cameraX,\n      globalParams.cameraY,\n      globalParams.cameraZ\n    );\n  }\n\n  function createAmbientLight(globalParams) {\n    return new THREE.AmbientLight(\n      globalParams.ambientColor,\n      globalParams.ambientIntensity\n    );\n  }\n\n  function createDirectionalLight(globalParams) {\n    const light = new THREE.DirectionalLight(\n      globalParams.directionalColor,\n      globalParams.directionalIntensity\n    );\n    updateDirectionalLightPosition(light, globalParams);\n    return light;\n  }\n\n  function updateDirectionalLightPosition(light, globalParams) {\n    light.position.set(\n      globalParams.directionalPosX,\n      globalParams.directionalPosY,\n      globalParams.directionalPosZ\n    );\n  }\n\n  function createSpotLight(sceneParams) {\n    const textureLoader = new THREE.TextureLoader();\n    const projectionTexture = textureLoader.load(\n      'https:\/\/lebanidze.com\/wp-content\/uploads\/2025\/01\/ruins_by_lukasmosf_d3h0x9b-fullview.jpg'\n    );\n\n    const light = new THREE.SpotLight(\n      sceneParams.spotLightColor,\n      sceneParams.spotLightIntensity\n    );\n    updateSpotLightProperties(light, sceneParams, projectionTexture);\n    return light;\n  }\n\n  function updateSpotLightProperties(light, sceneParams, texture) {\n    light.position.set(\n      sceneParams.spotLightPosX,\n      sceneParams.spotLightPosY,\n      sceneParams.spotLightPosZ\n    );\n    light.angle = sceneParams.spotLightAngle;\n    light.penumbra = sceneParams.spotLightPenumbra;\n    light.decay = sceneParams.spotLightDecay;\n    light.distance = sceneParams.spotLightDistance;\n    light.map = texture;\n    light.castShadow = true;\n    light.shadow.mapSize.set(1024, 1024);\n    light.shadow.camera.near = 1;\n    light.shadow.camera.far = 10;\n    light.shadow.focus = sceneParams.spotLightFocus;\n  }\n\n  function createRenderer(container) {\n    const renderer = new THREE.WebGLRenderer({\n      antialias: true,\n      alpha: true,\n      powerPreference: \"high-performance\"\n    });\n    renderer.setPixelRatio(window.devicePixelRatio);\n    renderer.setSize(container.offsetWidth, container.offsetHeight);\n    renderer.toneMapping = THREE.ACESFilmicToneMapping;\n    renderer.shadowMap.enabled = true;\n    renderer.shadowMap.type = THREE.PCFSoftShadowMap;\n    container.appendChild(renderer.domElement);\n    return renderer;\n  }\n\n  function createIntersectionObserver(container, callback) {\n    const observer = new IntersectionObserver((entries) => {\n      entries.forEach(entry => callback(entry.isIntersecting));\n    }, { threshold: 0.01 });\n    observer.observe(container);\n    return observer;\n  }\n\n  function loadModel(url) {\n    return new Promise((resolve, reject) => {\n      const loader = new GLTFLoader();\n      loader.load(\n        url,\n        (gltf) => resolve(gltf.scene),\n        undefined,\n        (error) => reject(error)\n      );\n    });\n  }\n\n  \/\/ 5B) Model Interaction & Animation\n  function handleModelDrag(model, isDragging, previousMousePosition, e) {\n    if (!isDragging || !model) return;\n    const deltaX = e.clientX - previousMousePosition.x;\n    const deltaY = e.clientY - previousMousePosition.y;\n    model.rotation.y += deltaX * 0.005;\n    model.rotation.x += deltaY * 0.005;\n    previousMousePosition.x = e.clientX;\n    previousMousePosition.y = e.clientY;\n  }\n\n  function applyFloatingAndRotation(model, elapsedTime) {\n    if (!model) return;\n    model.position.y = Math.sin(elapsedTime * 1.5) * 0.05;\n    model.rotation.z = Math.sin(elapsedTime * 0.8) * THREE.MathUtils.DEG2RAD * 2;\n  }\n\n  \/\/ NEW: Utility function for chromatic aberration post processing.\n  \/\/ This sets up an EffectComposer with a RenderPass and an RGBShiftShader pass.\n  function setupChromaticAberration(renderer, scene, camera) {\n    const composer = new EffectComposer(renderer);\n    composer.addPass(new RenderPass(scene, camera));\n    const rgbShiftPass = new ShaderPass(RGBShiftShader);\n    rgbShiftPass.uniforms['amount'].value = 0.0017; \/\/ Adjust this value as desired.\n    composer.addPass(rgbShiftPass);\n    return composer;\n  }\n\n  \/\/ ========== 6) dat.GUI + Stats setup if debugMode=1 ==========\n  let stats = null;\n  if (debugMode === 1) {\n    \/\/ a) stats\n    stats = new Stats();\n    document.body.appendChild(stats.dom);\n    stats.dom.style.position = 'fixed';\n    stats.dom.style.top = '50px';\n    stats.dom.style.left = '0px';\n\n    \/\/ b) build dat.GUI\n    const gui = new dat.GUI({ width: 300 });\n\n    \/\/ Global Settings folder\n    const globalFolder = gui.addFolder('Global Settings');\n    globalFolder.addColor(params.global, 'fogColor')\n      .name('Fog Color')\n      .onChange(() => notifyUpdate());\n    globalFolder.add(params.global, 'fogNear', 0.1, 20)\n      .name('Fog Near')\n      .onChange(() => notifyUpdate());\n    globalFolder.add(params.global, 'fogFar', 1, 50)\n      .name('Fog Far')\n      .onChange(() => notifyUpdate());\n\n    globalFolder.add(params.global, 'cameraX', -10, 10)\n      .name('Camera X')\n      .onChange(() => notifyUpdate());\n    globalFolder.add(params.global, 'cameraY', -10, 10)\n      .name('Camera Y')\n      .onChange(() => notifyUpdate());\n    globalFolder.add(params.global, 'cameraZ', -10, 20)\n      .name('Camera Z')\n      .onChange(() => notifyUpdate());\n\n    globalFolder.add(params.global, 'emissiveStartAngle', 0, 360)\n      .name('Emissive Start')\n      .onChange(() => {\n        fixAngleOrder();\n        notifyUpdate();\n      });\n    globalFolder.add(params.global, 'emissiveEndAngle', 0, 360)\n      .name('Emissive End')\n      .onChange(() => {\n        fixAngleOrder();\n        notifyUpdate();\n      });\n\n    globalFolder.addColor(params.global, 'ambientColor')\n      .name('Ambient Color')\n      .onChange(() => notifyUpdate());\n    globalFolder.add(params.global, 'ambientIntensity', 0, 5)\n      .name('Ambient Intensity')\n      .onChange(() => notifyUpdate());\n\n    globalFolder.addColor(params.global, 'directionalColor')\n      .name('Dir Light Color')\n      .onChange(() => notifyUpdate());\n    globalFolder.add(params.global, 'directionalIntensity', 0, 10)\n      .name('Dir Light Intensity')\n      .onChange(() => notifyUpdate());\n    globalFolder.add(params.global, 'directionalPosX', -50, 50)\n      .name('Dir Pos X')\n      .onChange(() => notifyUpdate());\n    globalFolder.add(params.global, 'directionalPosY', -50, 50)\n      .name('Dir Pos Y')\n      .onChange(() => notifyUpdate());\n    globalFolder.add(params.global, 'directionalPosZ', -50, 50)\n      .name('Dir Pos Z')\n      .onChange(() => notifyUpdate());\n    globalFolder.open();\n\n    \/\/ Create folders for each scene\n    Object.keys(params).forEach((sceneKey) => {\n      if (sceneKey !== 'global') {\n        addSceneToGUI(gui, sceneKey);\n      }\n    });\n  }\n\n  \/\/ Notify once so default params apply\n  notifyUpdate();\n\n  \/\/ ========== 7) Return mySystem Object ==========\n  return {\n    debugMode,\n    params,\n    addUpdateListener,\n    notifyUpdate,\n    statsBegin: () => stats?.begin(),\n    statsEnd: () => stats?.end(),\n    utils: {\n      \/\/ Group all utility functions here\n      createCamera,\n      updateCameraPosition,\n      createAmbientLight,\n      createDirectionalLight,\n      updateDirectionalLightPosition,\n      createSpotLight,\n      updateSpotLightProperties,\n      createRenderer,\n      createIntersectionObserver,\n      loadModel,\n      handleModelDrag,\n      applyFloatingAndRotation,\n      \/\/ NEW: Chromatic aberration setup utility.\n      setupChromaticAberration\n    }\n  };\n})();\n\nwindow.mySystem = mySystem;\n<\/script>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-feb26d9 elementor-widget elementor-widget-html\" data-id=\"feb26d9\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<script type=\"module\">\nimport * as THREE from 'three';\n\n\/\/ \u2705 Load shared utilities from the main init script  \nconst { utils } = window.mySystem;\n\n\/\/ \u2705 Create Particle-Specific Init System\nconst particleSystemInit = (() => {\n  \/\/ \ud83d\udd39 Enable debug mode (0 = Off, 1 = On)\n  const debugMode = 0;\n\n  \/\/ \ud83d\udd39 Particle-Specific Parameters\n  const params = {\n    sceneP1: { \n      waveMultiplier: 4.05, particleColor: '#8dbcff',\n      m: 65, n: 1, i: 0, j: 100, ZMultiplier: 2.04, p: 1.6, q: 48, \/\/Z\n      particleSize: 0.7, particleCount: 320, lfoFrequency: 0.3, lfoAmplitude: 0.02, timeSpeed: 0.9\n    },\n    sceneP2: { \n      waveMultiplier: 4.93, particleColor: '#8dbcff',\n      m: 0.64, n: 42, i: 2.8, j: 83.1, ZMultiplier: 1, p: 2.3, q: 0, \/\/Z\n      particleSize: 0.7, particleCount: 320, lfoFrequency: 1.648, lfoAmplitude: 0.004, timeSpeed: 2\n    },\n    sceneP3: { \n      waveMultiplier: 4.93, particleColor: '#8dbcff',\n      m: 0.1, n: 47, i: 2.8, j: 93.8, ZMultiplier: 1, p: 2.2, q: 0, \/\/Z\n      particleSize: 0.7, particleCount: 320, lfoFrequency: 1.648, lfoAmplitude: 0.004, timeSpeed: 2\n    },\n    sceneP4: { \n      waveMultiplier: 4.05, particleColor: '#8dbcff',\n      m: 0.46, n: 96, i: 3.5, j: 100, ZMultiplier: 0.521, p: 3.7, q: 100, \/\/Z\n      particleSize: 0.4, particleCount: 320, lfoFrequency: 0, lfoAmplitude: 0, timeSpeed: 1.1\n    },\n    \/\/ Note the comma above before sceneP6!\n    sceneP6: { \n      waveMultiplier: 3.16, particleColor: '#8dbcff',\n      m: 0.54, n: 55, i: 1.6, j: 100, ZMultiplier: 3, p: 2.1, q: 100, \/\/Z\n      particleSize: 0.4, particleCount: 320, lfoFrequency: 0, lfoAmplitude: 0, timeSpeed: 1.1\n    }\n  };\n\n  \/\/ \ud83d\udd39 Initialize sceneP5 for XY Pad control.\n  \/\/ This scene\u2019s parameters will be updated via an XY pad (see below).\n  params.sceneP5 = {};\n\n  \/\/ \ud83d\udd39 Update listeners for real-time adjustments\n  const updateListeners = [];\n  function addUpdateListener(callback) {\n    updateListeners.push(callback);\n  }\n\n  function notifyUpdate(sceneKey) {\n    updateListeners.forEach((cb) => {\n      cb(sceneKey, params[sceneKey]); \/\/ \ud83d\udd25 Pass only the updated scene\n    });\n  }\n\n  \/\/ \ud83d\udd39 Function to Create Sliders for Each Particle Scene\n  function createParticleSliders(folder, sceneKey) {\n    const sceneObj = params[sceneKey];\n\n    function onSliderChange() {\n      notifyUpdate(sceneKey);\n    }\n\n    createSlider(folder, sceneObj, 'waveMultiplier', 0, 100, 0.01, onSliderChange);\n    createSlider(folder, sceneObj, 'm', 0.1, 31, 0.01, onSliderChange);\n    createSlider(folder, sceneObj, 'n', 1, 100, 1, onSliderChange);\n    createSlider(folder, sceneObj, 'i', 0, 31, 0.1, onSliderChange);\n    createSlider(folder, sceneObj, 'j', 0, 100, 0.1, onSliderChange);\n    createSlider(folder, sceneObj, 'ZMultiplier', 0, 3, 0.001, onSliderChange);\n    createSlider(folder, sceneObj, 'p', 0, 5.0, 0.1, onSliderChange); \/\/Z\n    createSlider(folder, sceneObj, 'q', 0, 100, 0.1, onSliderChange); \/\/Z\n    createSlider(folder, sceneObj, 'particleSize', 0.1, 5, 0.1, onSliderChange);\n    createSlider(folder, sceneObj, 'particleCount', 50, 1000, 10, onSliderChange);\n    createSlider(folder, sceneObj, 'timeSpeed', 0.1, 5.0, 0.1, onSliderChange);\n    createSlider(folder, sceneObj, 'lfoFrequency', 0, 10, 0.001, onSliderChange);\n    createSlider(folder, sceneObj, 'lfoAmplitude', 0, 2, 0.001, onSliderChange);\n    folder.addColor(sceneObj, 'particleColor')\n      .name('Particle Color')\n      .onChange(() => notifyUpdate(sceneKey));\n  }\n\n  \/\/ \ud83d\udd39 Standardized Slider Creation\n  function createSlider(folder, obj, key, min, max, step, callback) {\n    folder.add(obj, key, min, max, step).onChange(callback);\n  }\n\n  \/\/ \ud83d\udd39 Setup Debug UI (dat.GUI on the **Left Side**)\n  let gui = null;\n  if (debugMode) {\n    gui = new dat.GUI({ width: 300 });\n    gui.domElement.style.position = 'fixed';\n    gui.domElement.style.left = '0px'; \n    gui.domElement.style.top = '50px';\n\n    \/\/ Create Folders for Each Particle Scene (only for P1\u2013P4, since P5 is controlled via the XY pad)\n    Object.keys(params).forEach((sceneKey) => {\n      if (sceneKey !== 'sceneP5') {\n        const sceneFolder = gui.addFolder(`Particle Scene: ${sceneKey}`);\n        createParticleSliders(sceneFolder, sceneKey);\n        sceneFolder.open();\n      }\n    });\n  }\n\n  \/\/ \ud83d\udd39 Add WebGL Context Loss and Restoration Handlers\n  if (window.mySystem && window.mySystem.renderer) {\n    const canvas = window.mySystem.renderer.domElement;\n    canvas.addEventListener('webglcontextlost', (event) => {\n      event.preventDefault();\n      console.warn('WebGL context lost');\n    }, false);\n    canvas.addEventListener('webglcontextrestored', () => {\n      console.info('WebGL context restored');\n    }, false);\n  } else {\n    console.warn('Renderer not found on window.mySystem.renderer. WebGL context events will not be handled.');\n  }\n\n  \/\/ Optional: Listen for tab visibility changes.\n  document.addEventListener('visibilitychange', () => {\n    if (!document.hidden) {\n      console.info('Tab is active again');\n    }\n  });\n\n  \/\/ ===========================================================\n  \/\/ New: XY Pad Module Integration for controlling sceneP5\n  \/\/ ===========================================================\n\n  \/\/ Helper function: linear interpolation between two numbers.\n  function lerp(a, b, t) {\n    return a + (b - a) * t;\n  }\n\n  \/\/ Helper functions for color interpolation.\n  function hexToRgb(hex) {\n    hex = hex.replace(\/^#\/, '');\n    let bigint = parseInt(hex, 16);\n    let r, g, b;\n    if (hex.length === 3) {\n      r = (bigint >> 8) & 0xF;\n      g = (bigint >> 4) & 0xF;\n      b = bigint & 0xF;\n      r = (r << 4) | r;\n      g = (g << 4) | g;\n      b = (b << 4) | b;\n    } else {\n      r = (bigint >> 16) & 255;\n      g = (bigint >> 8) & 255;\n      b = bigint & 255;\n    }\n    return { r, g, b };\n  }\n\n  function rgbToHex(r, g, b) {\n    return \"#\" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);\n  }\n\n  \/**\n   * updateSceneP5: Updates the parameters for sceneP5 based on XY pad input.\n   * The XY pad is assumed to provide normalized coordinates (x and y from 0 to 1).\n   *\n   * First, bilinear interpolation is used to blend the values from the four corners:\n   *   Top-left: sceneP1, Top-right: sceneP2, Bottom-left: sceneP3, Bottom-right: sceneP4.\n   *\n   * Then a blend factor (1 at the center, 0 at the edges) is computed and used to blend\n   * the result with sceneP6 so that at (0.5, 0.5) the final parameters exactly match sceneP6.\n   *\/\n  function updateSceneP5(x, y) {\n    const dx = Math.abs(x - 0.5);\n    const dy = Math.abs(y - 0.5);\n    const d = Math.sqrt(dx * dx + dy * dy);\n    const maxD = Math.sqrt(0.5 * 0.5 + 0.5 * 0.5); \/\/ \u2248 0.7071\n    const blendFactor = 1 - Math.min(d \/ maxD, 1);\n\n    \/\/ List of keys to interpolate (excluding particleCount, lfoFrequency, lfoAmplitude, timeSpeed)\n    const keys = [\n      'waveMultiplier',\n      'm',\n      'n',\n      'i',\n      'j',\n      'ZMultiplier',\n      'p',\n      'q',\n      'particleSize'\n    ];\n\n    keys.forEach(key => {\n      \/\/ Bilinear interpolation between the four corner scenes:\n      const topValue = lerp(params.sceneP1[key], params.sceneP2[key], x);\n      const bottomValue = lerp(params.sceneP3[key], params.sceneP4[key], x);\n      const S = lerp(bottomValue, topValue, y);\n      \/\/ Blend S with sceneP6 using the blendFactor:\n      params.sceneP5[key] = lerp(S, params.sceneP6[key], blendFactor);\n    });\n\n    \/\/ Color interpolation:\n    const colorTL = hexToRgb(params.sceneP1.particleColor);\n    const colorTR = hexToRgb(params.sceneP2.particleColor);\n    const colorBL = hexToRgb(params.sceneP3.particleColor);\n    const colorBR = hexToRgb(params.sceneP4.particleColor);\n    const topColor = {\n      r: lerp(colorTL.r, colorTR.r, x),\n      g: lerp(colorTL.g, colorTR.g, x),\n      b: lerp(colorTL.b, colorTR.b, x)\n    };\n    const bottomColor = {\n      r: lerp(colorBL.r, colorBR.r, x),\n      g: lerp(colorBL.g, colorBR.g, x),\n      b: lerp(colorBL.b, colorBR.b, x)\n    };\n    let Scolor = {\n      r: Math.round(lerp(bottomColor.r, topColor.r, y)),\n      g: Math.round(lerp(bottomColor.g, topColor.g, y)),\n      b: Math.round(lerp(bottomColor.b, topColor.b, y))\n    };\n    \/\/ Blend with sceneP6's color:\n    const sceneP6Color = hexToRgb(params.sceneP6.particleColor);\n    const finalColor = {\n      r: Math.round(lerp(Scolor.r, sceneP6Color.r, blendFactor)),\n      g: Math.round(lerp(Scolor.g, sceneP6Color.g, blendFactor)),\n      b: Math.round(lerp(Scolor.b, sceneP6Color.b, blendFactor))\n    };\n    params.sceneP5.particleColor = rgbToHex(finalColor.r, finalColor.g, finalColor.b);\n\n    \/\/ Copy unchanged keys from sceneP1.\n    params.sceneP5.particleCount = params.sceneP1.particleCount;\n    params.sceneP5.lfoFrequency = params.sceneP1.lfoFrequency;\n    params.sceneP5.lfoAmplitude = params.sceneP1.lfoAmplitude;\n    params.sceneP5.timeSpeed = params.sceneP1.timeSpeed;\n\n    \/\/ Notify update listeners.\n    notifyUpdate('sceneP5');\n  }\n\n  \/\/ Expose the XY pad controller.\n  const xyPadController = {\n    update: updateSceneP5\n  };\n\n  \/\/ ===========================================================\n  \/\/ End of XY Pad Module Integration\n  \/\/ ===========================================================\n\n  return {\n    debugMode,\n    params,\n    addUpdateListener,\n    notifyUpdate,\n    xyPadController\n  };\n})();\n\nwindow.particleSystemInit = particleSystemInit;\n<\/script>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-e033785 elementor-section-full_width elementor-section-height-default elementor-section-height-default\" data-id=\"e033785\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-top-column elementor-element elementor-element-d59a8dc\" data-id=\"d59a8dc\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-575d0a4 elementor-widget elementor-widget-html\" data-id=\"575d0a4\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<script type=\"module\">\nimport * as THREE from 'three';\n\n\/\/ \u2705 Load shared utilities from the main init script  \nconst { params, addUpdateListener } = window.particleSystemInit;\nconst sceneParams = params.sceneP5; \/\/ using the same parameters for both particles and splines\n\n\/\/ scene-wide variables\nlet container, camera, scene, renderer, composer;\nlet particleSystem = null; \/\/ will hold our instanced mesh for particles\nlet splineLine = null;     \/\/ will hold our spline (THREE.Line)\nlet inView = false;\nconst clock = new THREE.Clock();\nlet currentParticleCount = sceneParams.particleCount;\n\n\/\/ store the random size multiplier for each particle instance\nlet instanceSizes = null;\n\n\/\/ create the circle texture only once\nlet circleTexture = null;\n\ninit();\nanimate();\n\nfunction init() {\n  container = document.getElementById('sceneP5-container');\n  container.style.width = '100%';\n  container.style.height = '80vh';\n  container.style.overflow = 'hidden';\n\n  window.mySystem.utils.createIntersectionObserver(container, (visible) => {\n    inView = visible;\n  });\n\n  camera = window.mySystem.utils.createCamera(container);\n  camera.position.set(0, 0, 27);\n  camera.far = 200;\n  camera.updateProjectionMatrix();\n\n  scene = new THREE.Scene();\n  scene.background = new THREE.Color('#000000');\n\n  renderer = window.mySystem.utils.createRenderer(container);\n  composer = window.mySystem.utils.setupChromaticAberration(renderer, scene, camera);\n\n  \/\/ log the GPU's point size range (for debugging)\n  const gl = renderer.getContext();\n  console.log('point size range:', gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE));\n\n  attachContextEventListeners();\n\n  \/\/ build both particle system and spline\n  buildParticleSystem();\n  buildSpline();\n\n  \/\/ fixed update listener with proper parameter handling\n  addUpdateListener((sceneKey, updatedParams) => {\n    if (sceneKey === 'sceneP5') {\n      Object.assign(sceneParams, updatedParams);\n      updateSceneFromParams();\n    }\n  });\n\n  \/\/ optional: listen for tab visibility changes\n  document.addEventListener('visibilitychange', () => {\n    if (!document.hidden) {\n      console.log('Tab is visible again');\n    }\n  });\n}\n\nfunction attachContextEventListeners() {\n  renderer.domElement.addEventListener('webglcontextlost', (event) => {\n    event.preventDefault();\n    console.warn('WebGL context lost');\n  }, false);\n\n  renderer.domElement.addEventListener('webglcontextrestored', () => {\n    console.info('WebGL context restored');\n    renderer = window.mySystem.utils.createRenderer(container);\n    composer = window.mySystem.utils.setupChromaticAberration(renderer, scene, camera);\n    attachContextEventListeners();\n    disposeParticleSystem();\n    buildParticleSystem();\n    disposeSpline();\n    buildSpline();\n  }, false);\n}\n\nfunction updateSceneFromParams() {\n  if (sceneParams.particleCount !== currentParticleCount) {\n    disposeParticleSystem();\n    buildParticleSystem();\n    return;\n  }\n  if (particleSystem) {\n    particleSystem.material.color.set(sceneParams.particleColor);\n    particleSystem.material.needsUpdate = true;\n  }\n  if (splineLine) {\n    splineLine.material.color.set(sceneParams.particleColor);\n    splineLine.material.needsUpdate = true;\n  }\n}\n\nfunction disposeParticleSystem() {\n  if (particleSystem) {\n    scene.remove(particleSystem);\n    particleSystem.geometry.dispose();\n    particleSystem.material.dispose();\n    particleSystem = null;\n  }\n}\n\nfunction buildParticleSystem() {\n  currentParticleCount = sceneParams.particleCount;\n  \n  \/\/ create random size multipliers for each instance (range: ~0.75 to 1.25)\n  instanceSizes = new Float32Array(currentParticleCount);\n  for (let i = 0; i < currentParticleCount; i++) {\n    instanceSizes[i] = 0.3 + (Math.random() - 0.5) * 0.2;\n  }\n\n  \/\/ create a simple plane geometry.\n  const geometry = new THREE.PlaneGeometry(1, 1);\n  \n  \/\/ create the circle texture only once\n  if (!circleTexture) {\n    circleTexture = buildCircleTexture();\n  }\n\n  const material = new THREE.MeshBasicMaterial({\n    color: sceneParams.particleColor,\n    map: circleTexture,\n    transparent: true,\n    alphaTest: 0.01,\n    blending: THREE.AdditiveBlending,\n    depthWrite: false,\n    depthTest: true\n  });\n\n  particleSystem = new THREE.InstancedMesh(geometry, material, currentParticleCount);\n  particleSystem.instanceMatrix.setUsage(THREE.DynamicDrawUsage);\n  scene.add(particleSystem);\n}\n\nfunction disposeSpline() {\n  if (splineLine) {\n    scene.remove(splineLine);\n    splineLine.geometry.dispose();\n    splineLine.material.dispose();\n    splineLine = null;\n  }\n}\n\nfunction buildSpline() {\n  const sampleCount = 100; \/\/ number of points along the spline\n  const points = [];\n  \/\/ generate initial points with t = 0\n  for (let i = 0; i <= sampleCount; i++) {\n    const xVal = i \/ sampleCount;\n    const t = 0; \/\/ initial time\n    \/\/ using the formulas:\n    \/\/ X(x,t) = acos(m * x) * cos(n * x + t)\n    \/\/ Y(x,t) = sin(i * x + t) * sin(j * x + t)\n    \/\/ Z(x,t) = cos(p * x * \u03c0) * cos(q * x + t)\n    const Xval = Math.acos(sceneParams.m * xVal) * Math.cos(sceneParams.n * xVal + t);\n    const Yval = Math.sin(sceneParams.i * xVal + t) * Math.sin(sceneParams.j * xVal + t);\n    const pi = Math.PI;\n    const Zval = Math.cos(sceneParams.p * xVal * pi) * Math.cos(sceneParams.q * xVal + t);\n    points.push(new THREE.Vector3(\n      Xval * sceneParams.waveMultiplier,\n      Yval * sceneParams.waveMultiplier,\n      Zval * sceneParams.waveMultiplier * sceneParams.ZMultiplier\n    ));\n  }\n  \n  const geometry = new THREE.BufferGeometry().setFromPoints(points);\n  const material = new THREE.LineBasicMaterial({\n    color: sceneParams.particleColor,\n    transparent: true,\n    opacity: 0.3,\n    linewidth: 4,\n    blending: THREE.AdditiveBlending,\n    depthWrite: false,\n    depthTest: true\n  });\n  \n  splineLine = new THREE.Line(geometry, material);\n  scene.add(splineLine);\n}\n\nfunction buildCircleTexture() {\n  const size = 50;\n  const canvas = document.createElement('canvas');\n  canvas.width = size;\n  canvas.height = size;\n  const ctx = canvas.getContext('2d');\n  \n  \/\/ clear background to fully transparent\n  ctx.clearRect(0, 0, size, size);\n  \n  \/\/ create a radial gradient that gives a smooth, circular falloff\n  const gradient = ctx.createRadialGradient(size\/2, size\/2, 0, size\/2, size\/2, size\/2);\n  gradient.addColorStop(0, 'rgba(255,255,255,1)');\n  gradient.addColorStop(0.2, 'rgba(255,255,255,0.7)');\n  gradient.addColorStop(0.7, 'rgba(255,255,255,0.2)');\n  gradient.addColorStop(1, 'rgba(255,255,255,0)');\n  ctx.fillStyle = gradient;\n  ctx.fillRect(0, 0, size, size);\n  \n  const texture = new THREE.CanvasTexture(canvas);\n  texture.minFilter = THREE.LinearFilter;\n  texture.magFilter = THREE.LinearFilter;\n  texture.format = THREE.RGBAFormat;\n  return texture;\n}\n\nfunction animate() {\n  requestAnimationFrame(animate);\n  if (!inView) return;\n  \n  const elapsed = clock.getElapsedTime();\n  const lfoValue = Math.sin(elapsed * sceneParams.lfoFrequency) * sceneParams.lfoAmplitude;\n  const elapsedTime = elapsed * (sceneParams.timeSpeed + Math.abs(lfoValue));\n  \n  const pi = Math.PI;\n  const half = 0.5;\n  \n  \/\/ update particle instanced mesh matrices\n  if (particleSystem) {\n    const dummyMatrix = new THREE.Matrix4();\n    const dummyPosition = new THREE.Vector3();\n    const dummyQuaternion = new THREE.Quaternion();\n    const dummyScale = new THREE.Vector3();\n    \n    dummyQuaternion.copy(camera.quaternion);\n    \n    for (let i = 0; i < currentParticleCount; i++) {\n      const xVal = i \/ (currentParticleCount - 1);\n      const Xval = Math.cos(sceneParams.m * xVal * pi) * Math.cos(sceneParams.n * xVal + elapsedTime);\n      const Yval = Math.sin(sceneParams.i * xVal + elapsedTime) * Math.sin(sceneParams.j * xVal + half * elapsedTime);\n      const Zval = Math.cos(sceneParams.p * xVal * pi) * Math.cos(sceneParams.q * xVal + elapsedTime);\n      \n      dummyPosition.set(\n        Xval * sceneParams.waveMultiplier,\n        Yval * sceneParams.waveMultiplier,\n        Zval * sceneParams.waveMultiplier * sceneParams.ZMultiplier\n      );\n      \n      const scaleVal = instanceSizes[i] * sceneParams.particleSize;\n      dummyScale.set(scaleVal, scaleVal, scaleVal);\n      \n      dummyMatrix.compose(dummyPosition, dummyQuaternion, dummyScale);\n      particleSystem.setMatrixAt(i, dummyMatrix);\n    }\n    particleSystem.instanceMatrix.needsUpdate = true;\n  }\n  \n  \/\/ update spline geometry points\n  if (splineLine) {\n    const sampleCount = 320;\n    const points = [];\n    const t = elapsed; \/\/ time variable for animation\n    for (let i = 0; i <= sampleCount; i++) {\n      const xVal = i \/ sampleCount;\n      const Xval = Math.cos(sceneParams.m * xVal * pi) * Math.cos(sceneParams.n * xVal + elapsedTime);\n      const Yval = Math.sin(sceneParams.i * xVal + elapsedTime) * Math.sin(sceneParams.j * xVal + half * elapsedTime);\n      const Zval = Math.cos(sceneParams.p * xVal * pi) * Math.cos(sceneParams.q * xVal + elapsedTime);\n      \n      points.push(new THREE.Vector3(\n        Xval * sceneParams.waveMultiplier,\n        Yval * sceneParams.waveMultiplier,\n        Zval * sceneParams.waveMultiplier * sceneParams.ZMultiplier\n      ));\n    }\n    splineLine.geometry.setFromPoints(points);\n    splineLine.geometry.needsUpdate = true;\n  }\n  \n  composer.render();\n}\n<\/script>\n\n<div id=\"sceneP5-container\" style=\"width: 100%; height: 80vh;\"><\/div>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-top-column elementor-element elementor-element-ae729cb\" data-id=\"ae729cb\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-6be2c75 e-transform elementor-widget elementor-widget-html\" data-id=\"6be2c75\" data-element_type=\"widget\" data-settings=\"{&quot;_transform_scaleX_effect&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_scaleX_effect_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_scaleX_effect_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_scaleY_effect&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_scaleY_effect_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_scaleY_effect_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]}}\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<div class=\"elementor-widget-xy-pad\">\n  <div class=\"xy-pad\">\n    <div class=\"xy-pad-cursor\"><\/div>\n  <\/div>\n<\/div>\n\n<style>\n  \/* Container for the XY pad *\/\n  .elementor-widget-xy-pad .xy-pad {\n    width:200px;\n    height: 200px;\n    opacity: 90%;\n    background: radial-gradient(circle, #010305, #091b35);\n    border: 2px solid #1e5c74;\n    border-radius: 8px;\n    position: relative;\n    margin: 20px auto;\n    box-shadow: inset 0 0 8px rgba(0,0,0,0.2);\n  }\n  \n  \/* The draggable cursor *\/\n  .elementor-widget-xy-pad .xy-pad-cursor {\n    width: 30px;\n    height: 30px;\n    background: #052532;\n    border: 2px solid #4a8ca6;\n    border-radius: 90%;\n    position: absolute;\n    left: 50%;\n    top: 50%;\n    transform: translate(-50%, -50%);\n    cursor: grab;\n  box-shadow: \n    0 0 8px #4a8ca6,   \/* outer glow *\/\n    0 0 16px #4a8ca6,  \/* stronger glow *\/\n    0 2px 6px rgba(0, 0, 0, 0.9); \n    transition: background 0.2s ease;\n  }\n  \n  .elementor-widget-xy-pad .xy-pad-cursor:active {\n    background: #000000;\n    cursor: grabbing;\n    box-shadow: \n    0 0 8px #537886,   \/* outer glow *\/\n    0 0 16px #537886,  \/* stronger glow *\/\n    0 2px 6px rgba(0, 0, 0, 0.9); \n  }\n<\/style>\n\n<script>\njQuery(document).ready(function($) {\n  \/\/ Select the XY pad container and its cursor element.\n  var $xyPad = $('.elementor-widget-xy-pad .xy-pad');\n  var $cursor = $('.elementor-widget-xy-pad .xy-pad-cursor');\n  var dragging = false;\n\n  \/**\n   * updateXY:\n   * Calculates the cursor\u2019s new position relative to the XY pad,\n   * updates the cursor\u2019s CSS, and passes normalized coordinates (x,y)\n   * to the particleSystemInit.xyPadController.update(x, y) method.\n   *\n   * Normalized coordinates:\n   *    x: 0 (left) to 1 (right)\n   *    y: 0 (bottom) to 1 (top) \u2013 note that y is inverted so 1 is at the top.\n   *\/\n  function updateXY(e) {\n    var offset = $xyPad.offset();\n    var width = $xyPad.width();\n    var height = $xyPad.height();\n    var pageX, pageY;\n    \n    \/\/ Support both mouse and touch events:\n    if (e && e.type.indexOf('touch') !== -1) {\n      pageX = e.originalEvent.touches[0].pageX;\n      pageY = e.originalEvent.touches[0].pageY;\n    } else if (e) {\n      pageX = e.pageX;\n      pageY = e.pageY;\n    } else {\n      \/\/ If no event is provided, default to the upper right corner.\n      pageX = offset.left + width;  \/\/ right edge\n      pageY = offset.top;           \/\/ top edge\n    }\n    \n    \/\/ Calculate the relative position inside the pad.\n    var posX = pageX - offset.left;\n    var posY = pageY - offset.top;\n    \n    \/\/ Clamp the position inside the pad boundaries.\n    posX = Math.max(0, Math.min(posX, width));\n    posY = Math.max(0, Math.min(posY, height));\n    \n    \/\/ Compute normalized values.\n    var x = posX \/ width;\n    var y = 1 - (posY \/ height); \/\/ invert y so that 1 is at the top\n    \n    \/\/ Update the cursor's visual position.\n    $cursor.css({\n      left: (x * 100) + '%',\n      top: ((1 - y) * 100) + '%'\n    });\n    \n    \/\/ Update scene via your controller.\n    if (window.particleSystemInit && window.particleSystemInit.xyPadController) {\n      window.particleSystemInit.xyPadController.update(x, y);\n    }\n  }\n\n  \/\/ When clicking anywhere on the XY pad, jump the cursor there and start dragging.\n  $xyPad.on('mousedown touchstart', function(e) {\n    e.preventDefault();\n    dragging = true;\n    updateXY(e);\n  });\n  \n  \/\/ While dragging, update the cursor's position.\n  $(document).on('mousemove.xyPad touchmove.xyPad', function(e) {\n    if (dragging) {\n      updateXY(e);\n    }\n  });\n  \n  \/\/ Stop dragging when the mouse or touch is released.\n  $(document).on('mouseup.xyPad touchend.xyPad', function() {\n    dragging = false;\n  });\n  \n  \/\/ --- Initialize the XY pad with a default value (upper right corner) so that the controller is set immediately ---\n  \/\/ Set the cursor to the upper right corner:\n  $cursor.css({\n    left: '100%',\n    top: '0%'\n  });\n  \/\/ Update the controller using upper right values (x=1, y=1)\n  if (window.particleSystemInit && window.particleSystemInit.xyPadController) {\n    window.particleSystemInit.xyPadController.update(1, 1);\n  }\n});\n<\/script>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-f066c4a elementor-hidden-tablet elementor-hidden-mobile elementor-hidden-desktop elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"f066c4a\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-cfb0795\" data-id=\"cfb0795\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-7f2bbca elementor-widget elementor-widget-html\" data-id=\"7f2bbca\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<script type=\"module\">\nimport * as THREE from 'three';\n\nconst { params, addUpdateListener } = window.particleSystemInit;\nconst sceneParams = params.sceneP1;\n\n\/\/ scene-wide variables\nlet container, camera, scene, renderer;\nlet particleSystem = null; \/\/ will hold our instanced mesh\nlet inView = false;\nconst clock = new THREE.Clock();\nlet currentParticleCount = sceneParams.particleCount;\n\n\/\/ store the random size multiplier for each instance here\nlet instanceSizes = null;\n\n\/\/ create the circle texture only once\nlet circleTexture = null;\n\ninit();\nanimate();\n\nfunction init() {\n  container = document.getElementById('sceneP1-container');\n  container.style.width = '100%';\n  container.style.height = '80vh';\n  container.style.overflow = 'hidden';\n\n  window.mySystem.utils.createIntersectionObserver(container, (visible) => {\n    inView = visible;\n  });\n\n  camera = window.mySystem.utils.createCamera(container);\n  camera.position.set(0, 0, 27);\n  camera.far = 200;\n  camera.updateProjectionMatrix();\n\n  scene = new THREE.Scene();\n  scene.background = new THREE.Color('#000000');\n\n  renderer = window.mySystem.utils.createRenderer(container);\n\n  \/\/ log the GPU's point size range (for debugging)\n  const gl = renderer.getContext();\n  console.log('point size range:', gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE));\n\n  \/\/ attach WebGL context loss\/restoration handlers\n  attachContextEventListeners();\n\n  buildParticleSystem();\n\n  \/\/ fixed update listener with proper parameter handling\n  addUpdateListener((sceneKey, updatedParams) => {\n    if (sceneKey === 'sceneP1') {\n      Object.assign(sceneParams, updatedParams);\n      updateSceneFromParams();\n    }\n  });\n\n  \/\/ optional: listen for tab visibility changes\n  document.addEventListener('visibilitychange', () => {\n    if (!document.hidden) {\n      console.log('Tab is visible again');\n      \/\/ optionally, reinitialize parts of your scene if needed\n    }\n  });\n}\n\nfunction attachContextEventListeners() {\n  renderer.domElement.addEventListener('webglcontextlost', (event) => {\n    event.preventDefault();\n    console.warn('WebGL context lost');\n  }, false);\n\n  renderer.domElement.addEventListener('webglcontextrestored', () => {\n    console.log('WebGL context restored');\n    \/\/ re-create the renderer\n    renderer = window.mySystem.utils.createRenderer(container);\n    attachContextEventListeners(); \/\/ reattach listeners on the new renderer\n    \/\/ reinitialize your particle system\n    disposeParticleSystem();\n    buildParticleSystem();\n  }, false);\n}\n\nfunction updateSceneFromParams() {\n  \/\/ always rebuild if particle count changes\n  if (sceneParams.particleCount !== currentParticleCount) {\n    disposeParticleSystem();\n    buildParticleSystem();\n    return;\n  }\n  \/\/ update material color if needed\n  if (particleSystem) {\n    particleSystem.material.color.set(sceneParams.particleColor);\n    particleSystem.material.needsUpdate = true;\n  }\n}\n\nfunction disposeParticleSystem() {\n  if (particleSystem) {\n    scene.remove(particleSystem);\n    particleSystem.geometry.dispose();\n    particleSystem.material.dispose();\n    particleSystem = null;\n  }\n}\n\nfunction buildParticleSystem() {\n  currentParticleCount = sceneParams.particleCount;\n  \n  \/\/ create random size multipliers for each instance \n  \/\/ adjust these values as needed so that sceneParams.particleSize produces a visible effect\n  instanceSizes = new Float32Array(currentParticleCount);\n  for (let i = 0; i < currentParticleCount; i++) {\n    instanceSizes[i] = 0.3 + (Math.random() - 0.5) * 0.2;\n  }\n\n  \/\/ create a simple plane geometry.\n  \/\/ using a circle texture, the plane appears as a circle.\n  const geometry = new THREE.PlaneGeometry(1, 1);\n  \n  \/\/ create the circle texture only once\n  if (!circleTexture) {\n    circleTexture = buildCircleTexture();\n  }\n\n  const material = new THREE.MeshBasicMaterial({\n    color: sceneParams.particleColor,\n    map: circleTexture,\n    transparent: true,\n    alphaTest: 0.01,\n    blending: THREE.AdditiveBlending,\n    depthWrite: false,\n    depthTest: true\n  });\n\n  \/\/ create the instanced mesh with the specified count\n  particleSystem = new THREE.InstancedMesh(geometry, material, currentParticleCount);\n  \/\/ mark the instanceMatrix as dynamic so we can update it every frame\n  particleSystem.instanceMatrix.setUsage(THREE.DynamicDrawUsage);\n\n  scene.add(particleSystem);\n}\n\nfunction buildCircleTexture() {\n  const size = 50;\n  const canvas = document.createElement('canvas');\n  canvas.width = size;\n  canvas.height = size;\n  const ctx = canvas.getContext('2d');\n\n  \/\/ clear background to fully transparent\n  ctx.clearRect(0, 0, size, size);\n\n  \/\/ create a radial gradient that gives a smooth, circular falloff\n  const gradient = ctx.createRadialGradient(size \/ 2, size \/ 2, 0, size \/ 2, size \/ 2, size \/ 2);\n  gradient.addColorStop(0, 'rgba(255,255,255,1)');\n  gradient.addColorStop(0.2, 'rgba(255,255,255,0.7)');\n  gradient.addColorStop(0.7, 'rgba(255,255,255,0.2)');\n  gradient.addColorStop(1, 'rgba(255,255,255,0)');\n  ctx.fillStyle = gradient;\n  ctx.fillRect(0, 0, size, size);\n\n  const texture = new THREE.CanvasTexture(canvas);\n  texture.minFilter = THREE.LinearFilter;\n  texture.magFilter = THREE.LinearFilter;\n  texture.format = THREE.RGBAFormat;\n  return texture;\n}\n\nfunction animate() {\n  requestAnimationFrame(animate);\n  if (!inView) return;\n\n  const elapsed = clock.getElapsedTime();\n  const lfoValue = Math.sin(elapsed * sceneParams.lfoFrequency) * sceneParams.lfoAmplitude;\n  const elapsedTime = elapsed * (sceneParams.timeSpeed + Math.abs(lfoValue));\n\n  \/\/ precalculate constant values to avoid redundant computations in the loop\n  const pi = Math.PI;\n  const half = 0.5;\n\n  \/\/ update the instance matrices every frame\n  if (particleSystem) {\n    \/\/ reuse these objects for performance\n    const dummyMatrix = new THREE.Matrix4();\n    const dummyPosition = new THREE.Vector3();\n    const dummyQuaternion = new THREE.Quaternion();\n    const dummyScale = new THREE.Vector3();\n\n    \/\/ have each instance face the camera:\n    dummyQuaternion.copy(camera.quaternion);\n    \/\/ if the plane geometry orientation needs correction, you can uncomment the following lines:\n    \/\/ const billboardRotation = new THREE.Quaternion();\n    \/\/ billboardRotation.setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);\n    \/\/ dummyQuaternion.multiply(billboardRotation);\n\n    for (let i = 0; i < currentParticleCount; i++) {\n      const xVal = i \/ (currentParticleCount - 1);\n\n      const Xval = Math.cos(sceneParams.m * xVal * pi) * Math.cos(sceneParams.n * xVal + elapsedTime);\n      const Yval = Math.sin(sceneParams.i * xVal + elapsedTime) * Math.sin(sceneParams.j * xVal + half * elapsedTime);\n      const Zval = Math.cos(sceneParams.p * xVal * pi) * Math.cos(sceneParams.q * xVal + elapsedTime);\n\n      dummyPosition.set(\n        Xval * sceneParams.waveMultiplier,\n        Yval * sceneParams.waveMultiplier,\n        Zval * sceneParams.waveMultiplier * sceneParams.ZMultiplier\n      );\n\n      \/\/ apply the random size multiplier for this instance, scaled by sceneParams.particleSize\n      const scaleVal = instanceSizes[i] * sceneParams.particleSize;\n      dummyScale.set(scaleVal, scaleVal, scaleVal);\n\n      dummyMatrix.compose(dummyPosition, dummyQuaternion, dummyScale);\n      particleSystem.setMatrixAt(i, dummyMatrix);\n    }\n    particleSystem.instanceMatrix.needsUpdate = true;\n  }\n\n  renderer.render(scene, camera);\n}\n<\/script>\n\n<div id=\"sceneP1-container\" style=\"width: 100%; height: 80vh;\"><\/div>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-35e1903 elementor-widget elementor-widget-html\" data-id=\"35e1903\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<script type=\"module\">\nimport * as THREE from 'three';\n\nconst { params, addUpdateListener } = window.particleSystemInit;\nconst sceneParams = params.sceneP2;\n\n\/\/ scene-wide variables\nlet container, camera, scene, renderer;\nlet particleSystem = null; \/\/ will hold our instanced mesh\nlet inView = false;\nconst clock = new THREE.Clock();\nlet currentParticleCount = sceneParams.particleCount;\n\n\/\/ store the random size multiplier for each instance here\nlet instanceSizes = null;\n\n\/\/ create the circle texture only once\nlet circleTexture = null;\n\ninit();\nanimate();\n\nfunction init() {\n  container = document.getElementById('sceneP2-container');\n  container.style.width = '100%';\n  container.style.height = '80vh';\n  container.style.overflow = 'hidden';\n\n  window.mySystem.utils.createIntersectionObserver(container, (visible) => {\n    inView = visible;\n  });\n\n  camera = window.mySystem.utils.createCamera(container);\n  camera.position.set(0, 0, 27);\n  camera.far = 200;\n  camera.updateProjectionMatrix();\n\n  scene = new THREE.Scene();\n  scene.background = new THREE.Color('#000000');\n\n  renderer = window.mySystem.utils.createRenderer(container);\n\n  \/\/ log the GPU's point size range (for debugging)\n  const gl = renderer.getContext();\n  console.log('point size range:', gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE));\n\n  \/\/ attach WebGL context loss\/restoration handlers\n  attachContextEventListeners();\n\n  buildParticleSystem();\n\n  \/\/ fixed update listener with proper parameter handling\n  addUpdateListener((sceneKey, updatedParams) => {\n    if (sceneKey === 'sceneP2') {\n      Object.assign(sceneParams, updatedParams);\n      updateSceneFromParams();\n    }\n  });\n\n  \/\/ optional: listen for tab visibility changes\n  document.addEventListener('visibilitychange', () => {\n    if (!document.hidden) {\n      console.log('Tab is visible again');\n      \/\/ optionally, reinitialize parts of your scene if needed\n    }\n  });\n}\n\nfunction attachContextEventListeners() {\n  renderer.domElement.addEventListener('webglcontextlost', (event) => {\n    event.preventDefault();\n    console.warn('WebGL context lost');\n  }, false);\n\n  renderer.domElement.addEventListener('webglcontextrestored', () => {\n    console.log('WebGL context restored');\n    \/\/ re-create the renderer\n    renderer = window.mySystem.utils.createRenderer(container);\n    attachContextEventListeners(); \/\/ reattach listeners on the new renderer\n    \/\/ reinitialize your particle system\n    disposeParticleSystem();\n    buildParticleSystem();\n  }, false);\n}\n\nfunction updateSceneFromParams() {\n  \/\/ always rebuild if particle count changes\n  if (sceneParams.particleCount !== currentParticleCount) {\n    disposeParticleSystem();\n    buildParticleSystem();\n    return;\n  }\n  \/\/ update material color if needed\n  if (particleSystem) {\n    particleSystem.material.color.set(sceneParams.particleColor);\n    particleSystem.material.needsUpdate = true;\n  }\n}\n\nfunction disposeParticleSystem() {\n  if (particleSystem) {\n    scene.remove(particleSystem);\n    particleSystem.geometry.dispose();\n    particleSystem.material.dispose();\n    particleSystem = null;\n  }\n}\n\nfunction buildParticleSystem() {\n  currentParticleCount = sceneParams.particleCount;\n  \n  \/\/ create random size multipliers for each instance (range: ~0.75 to 1.25)\n  \/\/ adjust these values as needed so that sceneParams.particleSize produces a visible effect\n  instanceSizes = new Float32Array(currentParticleCount);\n  for (let i = 0; i < currentParticleCount; i++) {\n    instanceSizes[i] = 0.3 + (Math.random() - 0.5) * 0.2;\n  }\n\n  \/\/ create a simple plane geometry.\n  \/\/ using a circle texture, the plane appears as a circle.\n  const geometry = new THREE.PlaneGeometry(1, 1);\n  \n  \/\/ create the circle texture only once\n  if (!circleTexture) {\n    circleTexture = buildCircleTexture();\n  }\n\n  const material = new THREE.MeshBasicMaterial({\n    color: sceneParams.particleColor,\n    map: circleTexture,\n    transparent: true,\n    alphaTest: 0.01,\n    blending: THREE.AdditiveBlending,\n    depthWrite: false,\n    depthTest: true\n  });\n\n  \/\/ create the instanced mesh with the specified count\n  particleSystem = new THREE.InstancedMesh(geometry, material, currentParticleCount);\n  \/\/ mark the instanceMatrix as dynamic so we can update it every frame\n  particleSystem.instanceMatrix.setUsage(THREE.DynamicDrawUsage);\n\n  scene.add(particleSystem);\n}\n\nfunction buildCircleTexture() {\n  const size = 50;\n  const canvas = document.createElement('canvas');\n  canvas.width = size;\n  canvas.height = size;\n  const ctx = canvas.getContext('2d');\n\n  \/\/ clear background to fully transparent\n  ctx.clearRect(0, 0, size, size);\n\n  \/\/ create a radial gradient that gives a smooth, circular falloff\n  const gradient = ctx.createRadialGradient(size \/ 2, size \/ 2, 0, size \/ 2, size \/ 2, size \/ 2);\n  gradient.addColorStop(0, 'rgba(255,255,255,1)');\n  gradient.addColorStop(0.2, 'rgba(255,255,255,0.7)');\n  gradient.addColorStop(0.7, 'rgba(255,255,255,0.2)');\n  gradient.addColorStop(1, 'rgba(255,255,255,0)');\n  ctx.fillStyle = gradient;\n  ctx.fillRect(0, 0, size, size);\n\n  const texture = new THREE.CanvasTexture(canvas);\n  texture.minFilter = THREE.LinearFilter;\n  texture.magFilter = THREE.LinearFilter;\n  texture.format = THREE.RGBAFormat;\n  return texture;\n}\n\nfunction animate() {\n  requestAnimationFrame(animate);\n  if (!inView) return;\n\n  const elapsed = clock.getElapsedTime();\n  \/\/ note: we still compute lfoValue and elapsedTime for potential use\n  const lfoValue = Math.sin(elapsed * sceneParams.lfoFrequency) * sceneParams.lfoAmplitude;\n  const elapsedTime = elapsed * (sceneParams.timeSpeed + Math.abs(lfoValue));\n\n  \/\/ precalculate constant values to avoid redundant computations in the loop\n  const pi = Math.PI;\n  const half = 0.5;\n\n  \/\/ update the instance matrices every frame\n  if (particleSystem) {\n    \/\/ reuse these objects for performance\n    const dummyMatrix = new THREE.Matrix4();\n    const dummyPosition = new THREE.Vector3();\n    const dummyQuaternion = new THREE.Quaternion();\n    const dummyScale = new THREE.Vector3();\n\n    \/\/ have each instance face the camera:\n    dummyQuaternion.copy(camera.quaternion);\n    \/\/ if the plane geometry orientation needs correction, you can uncomment the following lines:\n    \/\/ const billboardRotation = new THREE.Quaternion();\n    \/\/ billboardRotation.setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);\n    \/\/ dummyQuaternion.multiply(billboardRotation);\n\n    for (let i = 0; i < currentParticleCount; i++) {\n      const xVal = i \/ (currentParticleCount - 1);\n\n      \/\/ adjust X and Y using your desired equations with time added for animation:\n      \/\/ X(x, t) = acos(m * x) * cos(n * x + t)\n      \/\/ Y(x, t) = sin(i * x + t) * sin(j * x + t)\n      const Xval = Math.acos(sceneParams.m * xVal) * Math.cos(sceneParams.n * xVal + elapsedTime);\n      const Yval = Math.sin(sceneParams.i * xVal + elapsedTime) * Math.sin(sceneParams.j * xVal + elapsedTime);\n      \n      \/\/ keep Z as before (animated)\n      const Zval = Math.cos(sceneParams.p * xVal * pi) * Math.cos(sceneParams.q * xVal + elapsedTime);\n\n      dummyPosition.set(\n        Xval * sceneParams.waveMultiplier,\n        Yval * sceneParams.waveMultiplier,\n        Zval * sceneParams.waveMultiplier * sceneParams.ZMultiplier\n      );\n\n      \/\/ apply the random size multiplier for this instance, scaled by sceneParams.particleSize\n      const scaleVal = instanceSizes[i] * sceneParams.particleSize;\n      dummyScale.set(scaleVal, scaleVal, scaleVal);\n\n      dummyMatrix.compose(dummyPosition, dummyQuaternion, dummyScale);\n      particleSystem.setMatrixAt(i, dummyMatrix);\n    }\n    particleSystem.instanceMatrix.needsUpdate = true;\n  }\n\n  renderer.render(scene, camera);\n}\n<\/script>\n\n<div id=\"sceneP2-container\" style=\"width: 100%; height: 80vh;\"><\/div>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0a19436 elementor-widget elementor-widget-html\" data-id=\"0a19436\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<script type=\"module\">\nimport * as THREE from 'three';\n\nconst { params, addUpdateListener } = window.particleSystemInit;\nconst sceneParams = params.sceneP3;\n\n\/\/ scene-wide variables\nlet container, camera, scene, renderer;\nlet particleSystem = null; \/\/ will hold our instanced mesh\nlet inView = false;\nconst clock = new THREE.Clock();\nlet currentParticleCount = sceneParams.particleCount;\n\n\/\/ store the random size multiplier for each instance here\nlet instanceSizes = null;\n\n\/\/ create the circle texture only once\nlet circleTexture = null;\n\ninit();\nanimate();\n\nfunction init() {\n  container = document.getElementById('sceneP3-container');\n  container.style.width = '100%';\n  container.style.height = '80vh';\n  container.style.overflow = 'hidden';\n\n  window.mySystem.utils.createIntersectionObserver(container, (visible) => {\n    inView = visible;\n  });\n\n  camera = window.mySystem.utils.createCamera(container);\n  camera.position.set(0, 0, 27);\n  camera.far = 200;\n  camera.updateProjectionMatrix();\n\n  scene = new THREE.Scene();\n  scene.background = new THREE.Color('#000000');\n\n  renderer = window.mySystem.utils.createRenderer(container);\n\n  \/\/ log the GPU's point size range (for debugging)\n  const gl = renderer.getContext();\n  console.log('point size range:', gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE));\n\n  \/\/ attach WebGL context loss\/restoration handlers\n  attachContextEventListeners();\n\n  buildParticleSystem();\n\n  \/\/ fixed update listener with proper parameter handling\n  addUpdateListener((sceneKey, updatedParams) => {\n    if (sceneKey === 'sceneP3') {\n      Object.assign(sceneParams, updatedParams);\n      updateSceneFromParams();\n    }\n  });\n\n  \/\/ optional: listen for tab visibility changes\n  document.addEventListener('visibilitychange', () => {\n    if (!document.hidden) {\n      console.log('Tab is visible again');\n      \/\/ optionally, reinitialize parts of your scene if needed\n    }\n  });\n}\n\nfunction attachContextEventListeners() {\n  renderer.domElement.addEventListener('webglcontextlost', (event) => {\n    event.preventDefault();\n    console.warn('WebGL context lost');\n  }, false);\n\n  renderer.domElement.addEventListener('webglcontextrestored', () => {\n    console.log('WebGL context restored');\n    \/\/ re-create the renderer\n    renderer = window.mySystem.utils.createRenderer(container);\n    attachContextEventListeners(); \/\/ reattach listeners on the new renderer\n    \/\/ reinitialize your particle system\n    disposeParticleSystem();\n    buildParticleSystem();\n  }, false);\n}\n\nfunction updateSceneFromParams() {\n  \/\/ always rebuild if particle count changes\n  if (sceneParams.particleCount !== currentParticleCount) {\n    disposeParticleSystem();\n    buildParticleSystem();\n    return;\n  }\n  \/\/ update material color if needed\n  if (particleSystem) {\n    particleSystem.material.color.set(sceneParams.particleColor);\n    particleSystem.material.needsUpdate = true;\n  }\n}\n\nfunction disposeParticleSystem() {\n  if (particleSystem) {\n    scene.remove(particleSystem);\n    particleSystem.geometry.dispose();\n    particleSystem.material.dispose();\n    particleSystem = null;\n  }\n}\n\nfunction buildParticleSystem() {\n  currentParticleCount = sceneParams.particleCount;\n  \n  \/\/ create random size multipliers for each instance \n  \/\/ adjust these values as needed so that sceneParams.particleSize produces a visible effect\n  instanceSizes = new Float32Array(currentParticleCount);\n  for (let i = 0; i < currentParticleCount; i++) {\n    instanceSizes[i] = 0.3 + (Math.random() - 0.5) * 0.2;\n  }\n\n  \/\/ create a simple plane geometry.\n  \/\/ using a circle texture, the plane appears as a circle.\n  const geometry = new THREE.PlaneGeometry(1, 1);\n  \n  \/\/ create the circle texture only once\n  if (!circleTexture) {\n    circleTexture = buildCircleTexture();\n  }\n\n  const material = new THREE.MeshBasicMaterial({\n    color: sceneParams.particleColor,\n    map: circleTexture,\n    transparent: true,\n    alphaTest: 0.01,\n    blending: THREE.AdditiveBlending,\n    depthWrite: false,\n    depthTest: true\n  });\n\n  \/\/ create the instanced mesh with the specified count\n  particleSystem = new THREE.InstancedMesh(geometry, material, currentParticleCount);\n  \/\/ mark the instanceMatrix as dynamic so we can update it every frame\n  particleSystem.instanceMatrix.setUsage(THREE.DynamicDrawUsage);\n\n  scene.add(particleSystem);\n}\n\nfunction buildCircleTexture() {\n  const size = 50;\n  const canvas = document.createElement('canvas');\n  canvas.width = size;\n  canvas.height = size;\n  const ctx = canvas.getContext('2d');\n\n  \/\/ clear background to fully transparent\n  ctx.clearRect(0, 0, size, size);\n\n  \/\/ create a radial gradient that gives a smooth, circular falloff\n  const gradient = ctx.createRadialGradient(size \/ 2, size \/ 2, 0, size \/ 2, size \/ 2, size \/ 2);\n  gradient.addColorStop(0, 'rgba(255,255,255,1)');\n  gradient.addColorStop(0.2, 'rgba(255,255,255,0.7)');\n  gradient.addColorStop(0.7, 'rgba(255,255,255,0.2)');\n  gradient.addColorStop(1, 'rgba(255,255,255,0)');\n  ctx.fillStyle = gradient;\n  ctx.fillRect(0, 0, size, size);\n\n  const texture = new THREE.CanvasTexture(canvas);\n  texture.minFilter = THREE.LinearFilter;\n  texture.magFilter = THREE.LinearFilter;\n  texture.format = THREE.RGBAFormat;\n  return texture;\n}\n\nfunction animate() {\n  requestAnimationFrame(animate);\n  if (!inView) return;\n\n  const elapsed = clock.getElapsedTime();\n  const lfoValue = Math.sin(elapsed * sceneParams.lfoFrequency) * sceneParams.lfoAmplitude;\n  const elapsedTime = elapsed * (sceneParams.timeSpeed + Math.abs(lfoValue));\n\n  \/\/ precalculate constant values to avoid redundant computations in the loop\n  const pi = Math.PI;\n  const half = 0.5;\n\n  \/\/ update the instance matrices every frame\n  if (particleSystem) {\n    \/\/ reuse these objects for performance\n    const dummyMatrix = new THREE.Matrix4();\n    const dummyPosition = new THREE.Vector3();\n    const dummyQuaternion = new THREE.Quaternion();\n    const dummyScale = new THREE.Vector3();\n\n    \/\/ have each instance face the camera:\n    dummyQuaternion.copy(camera.quaternion);\n    \/\/ if the plane geometry orientation needs correction, you can uncomment the following lines:\n    \/\/ const billboardRotation = new THREE.Quaternion();\n    \/\/ billboardRotation.setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);\n    \/\/ dummyQuaternion.multiply(billboardRotation);\n\n    for (let i = 0; i < currentParticleCount; i++) {\n      const xVal = i \/ (currentParticleCount - 1);\n\n      const Xval = Math.cos(sceneParams.m * xVal * pi) * Math.cos(sceneParams.n * xVal + elapsedTime);\n      const Yval = Math.sin(sceneParams.i * xVal + elapsedTime) * Math.sin(sceneParams.j * xVal + half * elapsedTime);\n      const Zval = Math.cos(sceneParams.p * xVal * pi) * Math.cos(sceneParams.q * xVal + elapsedTime);\n\n      dummyPosition.set(\n        Xval * sceneParams.waveMultiplier,\n        Yval * sceneParams.waveMultiplier,\n        Zval * sceneParams.waveMultiplier * sceneParams.ZMultiplier\n      );\n\n      \/\/ apply the random size multiplier for this instance, scaled by sceneParams.particleSize\n      const scaleVal = instanceSizes[i] * sceneParams.particleSize;\n      dummyScale.set(scaleVal, scaleVal, scaleVal);\n\n      dummyMatrix.compose(dummyPosition, dummyQuaternion, dummyScale);\n      particleSystem.setMatrixAt(i, dummyMatrix);\n    }\n    particleSystem.instanceMatrix.needsUpdate = true;\n  }\n\n  renderer.render(scene, camera);\n}\n<\/script>\n\n<div id=\"sceneP3-container\" style=\"width: 100%; height: 80vh;\"><\/div>\n\n\n\n\n\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-896eea4 elementor-widget elementor-widget-html\" data-id=\"896eea4\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<script type=\"module\">\nimport * as THREE from 'three';\n\nconst { params, addUpdateListener } = window.particleSystemInit;\nconst sceneParams = params.sceneP4;\n\n\/\/ scene-wide variables\nlet container, camera, scene, renderer;\nlet particleSystem = null; \/\/ will hold our instanced mesh\nlet inView = false;\nconst clock = new THREE.Clock();\nlet currentParticleCount = sceneParams.particleCount;\n\n\/\/ store the random size multiplier for each instance here\nlet instanceSizes = null;\n\n\/\/ create the circle texture only once\nlet circleTexture = null;\n\ninit();\nanimate();\n\nfunction init() {\n  container = document.getElementById('sceneP4-container');\n  container.style.width = '100%';\n  container.style.height = '80vh';\n  container.style.overflow = 'hidden';\n\n  window.mySystem.utils.createIntersectionObserver(container, (visible) => {\n    inView = visible;\n  });\n\n  camera = window.mySystem.utils.createCamera(container);\n  camera.position.set(0, 0, 27);\n  camera.far = 200;\n  camera.updateProjectionMatrix();\n\n  scene = new THREE.Scene();\n  scene.background = new THREE.Color('#000000');\n\n  renderer = window.mySystem.utils.createRenderer(container);\n\n  \/\/ log the GPU's point size range (for debugging)\n  const gl = renderer.getContext();\n  console.log('point size range:', gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE));\n\n  \/\/ attach WebGL context loss\/restoration handlers\n  attachContextEventListeners();\n\n  buildParticleSystem();\n\n  \/\/ fixed update listener with proper parameter handling\n  addUpdateListener((sceneKey, updatedParams) => {\n    if (sceneKey === 'sceneP4') {\n      Object.assign(sceneParams, updatedParams);\n      updateSceneFromParams();\n    }\n  });\n\n  \/\/ optional: listen for tab visibility changes\n  document.addEventListener('visibilitychange', () => {\n    if (!document.hidden) {\n      console.log('Tab is visible again');\n      \/\/ optionally, reinitialize parts of your scene if needed\n    }\n  });\n}\n\nfunction attachContextEventListeners() {\n  renderer.domElement.addEventListener('webglcontextlost', (event) => {\n    event.preventDefault();\n    console.warn('WebGL context lost');\n  }, false);\n\n  renderer.domElement.addEventListener('webglcontextrestored', () => {\n    console.log('WebGL context restored');\n    \/\/ re-create the renderer\n    renderer = window.mySystem.utils.createRenderer(container);\n    attachContextEventListeners(); \/\/ reattach listeners on the new renderer\n    \/\/ reinitialize your particle system\n    disposeParticleSystem();\n    buildParticleSystem();\n  }, false);\n}\n\nfunction updateSceneFromParams() {\n  \/\/ always rebuild if particle count changes\n  if (sceneParams.particleCount !== currentParticleCount) {\n    disposeParticleSystem();\n    buildParticleSystem();\n    return;\n  }\n  \/\/ update material color if needed\n  if (particleSystem) {\n    particleSystem.material.color.set(sceneParams.particleColor);\n    particleSystem.material.needsUpdate = true;\n  }\n}\n\nfunction disposeParticleSystem() {\n  if (particleSystem) {\n    scene.remove(particleSystem);\n    particleSystem.geometry.dispose();\n    particleSystem.material.dispose();\n    particleSystem = null;\n  }\n}\n\nfunction buildParticleSystem() {\n  currentParticleCount = sceneParams.particleCount;\n  \n  \/\/ create random size multipliers for each instance \n  \/\/ adjust these values as needed so that sceneParams.particleSize produces a visible effect\n  instanceSizes = new Float32Array(currentParticleCount);\n  for (let i = 0; i < currentParticleCount; i++) {\n    instanceSizes[i] = 0.3 + (Math.random() - 0.5) * 0.2;\n  }\n\n  \/\/ create a simple plane geometry.\n  \/\/ using a circle texture, the plane appears as a circle.\n  const geometry = new THREE.PlaneGeometry(1, 1);\n  \n  \/\/ create the circle texture only once\n  if (!circleTexture) {\n    circleTexture = buildCircleTexture();\n  }\n\n  const material = new THREE.MeshBasicMaterial({\n    color: sceneParams.particleColor,\n    map: circleTexture,\n    transparent: true,\n    alphaTest: 0.01,\n    blending: THREE.AdditiveBlending,\n    depthWrite: false,\n    depthTest: true\n  });\n\n  \/\/ create the instanced mesh with the specified count\n  particleSystem = new THREE.InstancedMesh(geometry, material, currentParticleCount);\n  \/\/ mark the instanceMatrix as dynamic so we can update it every frame\n  particleSystem.instanceMatrix.setUsage(THREE.DynamicDrawUsage);\n\n  scene.add(particleSystem);\n}\n\nfunction buildCircleTexture() {\n  const size = 50;\n  const canvas = document.createElement('canvas');\n  canvas.width = size;\n  canvas.height = size;\n  const ctx = canvas.getContext('2d');\n\n  \/\/ clear background to fully transparent\n  ctx.clearRect(0, 0, size, size);\n\n  \/\/ create a radial gradient that gives a smooth, circular falloff\n  const gradient = ctx.createRadialGradient(size \/ 2, size \/ 2, 0, size \/ 2, size \/ 2, size \/ 2);\n  gradient.addColorStop(0, 'rgba(255,255,255,1)');\n  gradient.addColorStop(0.2, 'rgba(255,255,255,0.7)');\n  gradient.addColorStop(0.7, 'rgba(255,255,255,0.2)');\n  gradient.addColorStop(1, 'rgba(255,255,255,0)');\n  ctx.fillStyle = gradient;\n  ctx.fillRect(0, 0, size, size);\n\n  const texture = new THREE.CanvasTexture(canvas);\n  texture.minFilter = THREE.LinearFilter;\n  texture.magFilter = THREE.LinearFilter;\n  texture.format = THREE.RGBAFormat;\n  return texture;\n}\n\nfunction animate() {\n  requestAnimationFrame(animate);\n  if (!inView) return;\n\n  const elapsed = clock.getElapsedTime();\n  const lfoValue = Math.sin(elapsed * sceneParams.lfoFrequency) * sceneParams.lfoAmplitude;\n  const elapsedTime = elapsed * (sceneParams.timeSpeed + Math.abs(lfoValue));\n\n  \/\/ precalculate constant values to avoid redundant computations in the loop\n  const pi = Math.PI;\n  const half = 0.5;\n\n  \/\/ update the instance matrices every frame\n  if (particleSystem) {\n    \/\/ reuse these objects for performance\n    const dummyMatrix = new THREE.Matrix4();\n    const dummyPosition = new THREE.Vector3();\n    const dummyQuaternion = new THREE.Quaternion();\n    const dummyScale = new THREE.Vector3();\n\n    \/\/ have each instance face the camera:\n    dummyQuaternion.copy(camera.quaternion);\n    \/\/ if the plane geometry orientation needs correction, you can uncomment the following lines:\n    \/\/ const billboardRotation = new THREE.Quaternion();\n    \/\/ billboardRotation.setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);\n    \/\/ dummyQuaternion.multiply(billboardRotation);\n\n    for (let i = 0; i < currentParticleCount; i++) {\n      const xVal = i \/ (currentParticleCount - 1);\n\n      const Xval = Math.cos(sceneParams.m * xVal * pi) * Math.cos(sceneParams.n * xVal + elapsedTime);\n      const Yval = Math.sin(sceneParams.i * xVal + elapsedTime) * Math.sin(sceneParams.j * xVal + half * elapsedTime);\n      const Zval = Math.cos(sceneParams.p * xVal * pi) * Math.cos(sceneParams.q * xVal + elapsedTime);\n\n      dummyPosition.set(\n        Xval * sceneParams.waveMultiplier,\n        Yval * sceneParams.waveMultiplier,\n        Zval * sceneParams.waveMultiplier * sceneParams.ZMultiplier\n      );\n\n      \/\/ apply the random size multiplier for this instance, scaled by sceneParams.particleSize\n      const scaleVal = instanceSizes[i] * sceneParams.particleSize;\n      dummyScale.set(scaleVal, scaleVal, scaleVal);\n\n      dummyMatrix.compose(dummyPosition, dummyQuaternion, dummyScale);\n      particleSystem.setMatrixAt(i, dummyMatrix);\n    }\n    particleSystem.instanceMatrix.needsUpdate = true;\n  }\n\n  renderer.render(scene, camera);\n}\n<\/script>\n\n<div id=\"sceneP4-container\" style=\"width: 100%; height: 80vh;\"><\/div>\n\n\n\n\n\n\n\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-519446a elementor-section-height-min-height elementor-section-boxed elementor-section-height-default elementor-section-items-middle\" data-id=\"519446a\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-0900717\" data-id=\"0900717\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-inner-section elementor-element elementor-element-82fbb21 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"82fbb21\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-inner-column elementor-element elementor-element-62ec7a8\" data-id=\"62ec7a8\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-7694274 elementor-widget elementor-widget-text-editor\" data-id=\"7694274\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<style>\/*! elementor - v3.6.6 - 08-06-2022 *\/\n.elementor-widget-text-editor.elementor-drop-cap-view-stacked .elementor-drop-cap{background-color:#818a91;color:#fff}.elementor-widget-text-editor.elementor-drop-cap-view-framed .elementor-drop-cap{color:#818a91;border:3px solid;background-color:transparent}.elementor-widget-text-editor:not(.elementor-drop-cap-view-default) .elementor-drop-cap{margin-top:8px}.elementor-widget-text-editor:not(.elementor-drop-cap-view-default) .elementor-drop-cap-letter{width:1em;height:1em}.elementor-widget-text-editor .elementor-drop-cap{float:left;text-align:center;line-height:1;font-size:50px}.elementor-widget-text-editor .elementor-drop-cap-letter{display:inline-block}<\/style>\t\t\t\t<p>Welcome.<\/p><p>I am Luka Lebanidze, an audio-visual artist &amp; a media entrepreneur.<\/p><p>Currently, I lead the video game division at\u00a0<a href=\"https:\/\/postredaudio.com\/\" target=\"_blank\" rel=\"noopener\">POSTRED<\/a>,\u00a0a creative audio services company.<\/p>\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-inner-section elementor-element elementor-element-81b17ad elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"81b17ad\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-inner-column elementor-element elementor-element-e20a668\" data-id=\"e20a668\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-40c6d75 elementor-widget elementor-widget-html\" data-id=\"40c6d75\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<script type=\"module\">\nimport * as THREE from 'three';\n\n\/\/ Grab the relevant objects\/params from the global system\nconst { params, addUpdateListener, utils, statsBegin, statsEnd } = window.mySystem;\nconst globalParams = params.global;\nconst sceneParams = params.scene2;\n\n\/\/ Constants\nconst MAX_EMISSIVE_INTENSITY = 40.0;\n\n\/\/ Scene-level variables\nlet camera, scene, renderer;\nlet ambientLight, directionalLight, spotLight, lightHelper;\nlet model = null;\nlet emissiveMaterial = null;\nlet inView = false;\n\n\/\/ Interaction\nlet isDragging = false;\nlet previousMousePosition = { x: 0, y: 0 };\nlet scrollDelta = 0;\n\nconst clock = new THREE.Clock();\n\n\/\/ Init & animate\ninit();\nanimate();\n\n\/** =========================\n *  1) INIT \/ SETUP\n *  ========================= *\/\nasync function init() {\n  const container = document.getElementById('scene-container-2');\n  container.style.width = '100%';\n  container.style.height = '50vh';\n  container.style.overflow = 'hidden';\n\n  \/\/ IntersectionObserver\n  utils.createIntersectionObserver(container, (visible) => {\n    inView = visible;\n  });\n\n  \/\/ CAMERA\n  camera = utils.createCamera(container);\n  utils.updateCameraPosition(camera, globalParams);\n\n  \/\/ SCENE\n  scene = new THREE.Scene();\n  applyFogSettings(globalParams);\n\n  \/\/ AMBIENT LIGHT (custom intensity +1.42)\n  ambientLight = utils.createAmbientLight(globalParams);\n  ambientLight.intensity = globalParams.ambientIntensity + 1.42;\n  scene.add(ambientLight);\n\n  \/\/ DIRECTIONAL LIGHT\n  directionalLight = utils.createDirectionalLight(globalParams);\n  scene.add(directionalLight);\n\n  \/\/ SPOT LIGHT (with custom texture)\n  spotLight = utils.createSpotLight(sceneParams);\n  scene.add(spotLight);\n\n  lightHelper = new THREE.SpotLightHelper(spotLight);\n  lightHelper.visible = sceneParams.showSpotlightHelper;\n  scene.add(lightHelper);\n\n  \/\/ MODEL Group (Mosquito + Amber both go here)\n  model = new THREE.Group();\n  model.rotation.set(0, 1.58, 0); \/\/ ~90 deg\n  model.scale.set(sceneParams.modelScale, sceneParams.modelScale, sceneParams.modelScale);\n  scene.add(model);\n\n  \/\/ Load sub-models\n  await loadMosquitoInto(model);\n  await loadAmberInto(model);\n\n  \/\/ RENDERER\n  renderer = utils.createRenderer(container);\n\n  \/\/ EVENT LISTENERS\n  setupEventListeners(container);\n}\n\n\/** FOG SETTINGS *\/\nfunction applyFogSettings(g) {\n  scene.fog = new THREE.Fog(\n    new THREE.Color(g.fogColor),\n    g.fogNear,\n    g.fogFar\n  );\n  scene.background = new THREE.Color(g.fogColor);\n}\n\n\/** LOAD Mosquito model *\/\nasync function loadMosquitoInto(parentGroup) {\n  try {\n    const mosquito = await utils.loadModel(\n      'https:\/\/lebanidze.com\/wp-content\/uploads\/2025\/01\/compressed_1736111153097_lantern_v4.glb'\n    );\n    mosquito.traverse((child) => {\n      if (child.isMesh && child.material) {\n        \/\/ If it has an emissive or emissiveMap\n        if (child.material.emissiveMap ||\n            (child.material.emissive && !child.material.emissive.equals(new THREE.Color(0x000000)))) {\n          child.material.emissive = new THREE.Color(0xffffff);\n          child.material.emissiveIntensity = 0.0;\n          emissiveMaterial = child.material; \/\/ store reference\n        }\n      }\n    });\n    parentGroup.add(mosquito);\n    render();\n  } catch (error) {\n    console.error('Mosquito model loading failed:', error);\n  }\n}\n\n\/** LOAD Amber model with specialized material *\/\nasync function loadAmberInto(parentGroup) {\n  try {\n    const amber = await utils.loadModel(\n      'https:\/\/lebanidze.com\/wp-content\/uploads\/2025\/01\/amber_v4.glb'\n    );\n    amber.traverse((child) => {\n      if (child.isMesh) {\n        const origMat = child.material;\n        const customMat = new THREE.MeshPhysicalMaterial({\n          color: origMat.color || 0xffc855,\n          roughness: origMat.roughness ?? 0.1,\n          metalness: origMat.metalness ?? 0.0,\n          ior: 0.4,\n          transmission: 0.95,\n          thickness: 0.5,\n          envMapIntensity: 0.5,\n          reflectivity: 0.2,\n          transparent: true,\n          opacity: 1,\n          side: THREE.DoubleSide,\n\n          map: origMat.map || null,\n          normalMap: origMat.normalMap || null,\n          roughnessMap: origMat.roughnessMap || null,\n          metalnessMap: origMat.metalnessMap || null,\n          emissive: origMat.emissive || new THREE.Color(0x000000),\n          emissiveIntensity: origMat.emissiveIntensity || 0,\n          emissiveMap: origMat.emissiveMap || null,\n        });\n        child.material = customMat;\n      }\n    });\n    parentGroup.add(amber);\n    render();\n  } catch (error) {\n    console.error('Amber model loading failed:', error);\n  }\n}\n\n\/** =========================\n *  2) EVENT LISTENERS\n *  ========================= *\/\nfunction setupEventListeners(container) {\n  function onMouseDown(e) {\n    isDragging = true;\n    previousMousePosition.x = e.clientX;\n    previousMousePosition.y = e.clientY;\n  }\n\n  function onMouseMove(e) {\n    \/\/ Use global drag logic but with a slightly different rotation speed\n    if (isDragging && model) {\n      \/\/ We can do a custom approach or pass a \"speed\" param to handleModelDrag\n      \/\/ For simplicity, we'll reuse handleModelDrag, then do an additional slow-down factor if we want.\n      utils.handleModelDrag(model, isDragging, previousMousePosition, e);\n      \/\/ If you want slightly different speed:\n\/\/      model.rotation.y *= 0.6; \/\/ example tweak\n      render();\n    }\n  }\n\n  function onMouseUp() {\n    isDragging = false;\n  }\n\n  function onScroll() {\n    const currentScrollY = window.scrollY;\n    scrollDelta += (currentScrollY - previousMousePosition.y);\n    previousMousePosition.y = currentScrollY;\n  }\n\n  function onWindowResize() {\n    const width = container.offsetWidth;\n    const height = container.offsetHeight;\n    camera.aspect = width \/ height;\n    camera.updateProjectionMatrix();\n    renderer.setSize(width, height);\n    render();\n  }\n\n  container.addEventListener('mousedown', onMouseDown);\n  container.addEventListener('mousemove', onMouseMove);\n  container.addEventListener('mouseup', onMouseUp);\n\n  window.addEventListener('scroll', onScroll);\n  window.addEventListener('resize', onWindowResize);\n}\n\n\/** =========================\n *  3) PARAM UPDATES\n *  ========================= *\/\naddUpdateListener((allParams) => {\n  updateSceneFromParams(allParams.global, allParams.scene2);\n});\n\n\/** React to changes in global\/scene2 params *\/\nfunction updateSceneFromParams(g, s) {\n  \/\/ Scene\/fog\n  if (scene) {\n    applyFogSettings(g);\n  }\n  \/\/ Camera\n  if (camera) {\n    utils.updateCameraPosition(camera, g);\n    camera.updateProjectionMatrix();\n  }\n  \/\/ Ambient\n  if (ambientLight) {\n    ambientLight.color.set(g.ambientColor);\n    \/\/ +1.42 as before\n    ambientLight.intensity = g.ambientIntensity + 1.42;\n  }\n  \/\/ Directional\n  if (directionalLight) {\n    directionalLight.color.set(g.directionalColor);\n    directionalLight.intensity = g.directionalIntensity;\n    utils.updateDirectionalLightPosition(directionalLight, g);\n  }\n  \/\/ Model scale\n  if (model) {\n    model.scale.set(s.modelScale, s.modelScale, s.modelScale);\n  }\n  \/\/ SpotLight\n  if (spotLight) {\n    spotLight.color.set(s.spotLightColor);\n    spotLight.intensity = s.spotLightIntensity;\n    spotLight.position.set(s.spotLightPosX, s.spotLightPosY, s.spotLightPosZ);\n    spotLight.angle = s.spotLightAngle;\n    spotLight.penumbra = s.spotLightPenumbra;\n    spotLight.decay = s.spotLightDecay;\n    spotLight.distance = s.spotLightDistance;\n    spotLight.shadow.focus = s.spotLightFocus;\n  }\n  \/\/ Helper\n  if (lightHelper) {\n    lightHelper.visible = s.showSpotlightHelper;\n    lightHelper.update();\n  }\n  render();\n}\n\n\/** =========================\n *  4) ANIMATION LOOP\n *  ========================= *\/\nfunction animate() {\n  requestAnimationFrame(animate);\n\n  \/\/ Skip if not visible\n  if (!inView) return;\n\n  statsBegin?.();\n\n  const elapsedTime = clock.getElapsedTime();\n  const deltaTime = clock.getDelta();\n\n  \/\/ Bobbing + rotation\n  if (model) {\n    \/\/ Optionally use the global function for basic floating\/rotation\n    \/\/ Then do additional custom logic (auto-rotate, bigger emissive, etc.)\n\n    \/\/ 4A) Basic floating + rotation\n    utils.applyFloatingAndRotation(model, elapsedTime);\n\n    \/\/ 4B) Gentle auto-rotation around Y\n    model.rotation.y += 0.1 * deltaTime;\n\n    \/\/ 4C) Scroll-based rotation\n    model.rotation.x += scrollDelta * 0.0004;\n    scrollDelta *= 0.97;\n\n    \/\/ 4D) Emissive fade\n    if (emissiveMaterial) {\n      const rotationAngle = (THREE.MathUtils.radToDeg(model.rotation.y) + 360) % 360;\n      const { emissiveStartAngle, emissiveEndAngle } = globalParams;\n      if (rotationAngle >= emissiveStartAngle && rotationAngle <= emissiveEndAngle) {\n        const normAngle = (rotationAngle - emissiveStartAngle) \/ (emissiveEndAngle - emissiveStartAngle);\n        emissiveMaterial.emissiveIntensity =\n          THREE.MathUtils.lerp(0, MAX_EMISSIVE_INTENSITY, normAngle);\n      } else {\n        emissiveMaterial.emissiveIntensity = 0.0;\n      }\n    }\n    if (lightHelper) lightHelper.update();\n  }\n\n  render();\n  statsEnd?.();\n}\n\n\/** Render *\/\nfunction render() {\n  if (renderer && scene && camera) {\n    renderer.render(scene, camera);\n  }\n}\n<\/script>\n\n<!-- Scene Container -->\n<div id=\"scene-container-2\" style=\"width: 100%; height: 50vh; overflow: hidden;\">\n<\/div>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-inner-column elementor-element elementor-element-331b0ef\" data-id=\"331b0ef\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-ce61dcd elementor-widget elementor-widget-text-editor\" data-id=\"ce61dcd\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<p><em>Contemplate the amber by rotating it.<\/em><\/p>\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-e46efcd elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"e46efcd\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-36d312f\" data-id=\"36d312f\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap\">\n\t\t\t\t\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-0529050 elementor-section-full_width elementor-section-height-default elementor-section-height-default\" data-id=\"0529050\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-3551c6e\" data-id=\"3551c6e\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-inner-section elementor-element elementor-element-f5e4b84 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"f5e4b84\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-inner-column elementor-element elementor-element-1d034fb\" data-id=\"1d034fb\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-a8770ab elementor-widget elementor-widget-text-editor\" data-id=\"a8770ab\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<p><em>Through <\/em><em>the rock&#8217;s rotation you may find the inscription.<\/em><\/p>\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-inner-column elementor-element elementor-element-6c79edc\" data-id=\"6c79edc\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-ff54011 elementor-widget elementor-widget-html\" data-id=\"ff54011\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<!-- MODULE #2: FIRST THREE.JS SCENE (Celestial Stone) -->\n<script type=\"module\">\nimport * as THREE from 'three';\n\n\/\/ Destructure from mySystem for convenience\nconst { params, addUpdateListener, utils, statsBegin, statsEnd } = window.mySystem;\n\n\/\/ We'll reference the relevant param blocks\nconst globalParams = params.global;\nconst sceneParams = params.scene1;\n\n\/\/ Scene-wide variables\nlet camera, scene, renderer;\nlet ambientLight, directionalLight, spotLight, lightHelper, model;\nlet emissiveMaterial = null;\n\n\/\/ IntersectionObserver \/ in-view state\nlet inView = false;\n\n\/\/ Interaction states\nlet isDragging = false;\nlet previousMousePosition = { x: 0, y: 0 };\nlet scrollDelta = 0;\n\n\/\/ Clock for animation\nconst clock = new THREE.Clock();\n\n\/\/ Initialize and start animation\ninit();\nanimate();\n\n\/\/ ==================== MAIN SETUP ====================\nasync function init() {\n  const container = document.getElementById('scene-container-1');\n  container.style.width = '100%';\n  container.style.height = '40vh';\n  container.style.overflow = 'hidden';\n\n  \/\/ Use global utility for IntersectionObserver\n  utils.createIntersectionObserver(container, (visible) => {\n    inView = visible;\n  });\n\n  \/\/ Camera\n  camera = utils.createCamera(container);\n  utils.updateCameraPosition(camera, globalParams);\n\n  \/\/ Create a new Scene\n  scene = new THREE.Scene();\n  applyFogSettings(globalParams);\n\n  \/\/ Ambient Light\n  ambientLight = utils.createAmbientLight(globalParams);\n  scene.add(ambientLight);\n\n  \/\/ Directional Light\n  directionalLight = utils.createDirectionalLight(globalParams);\n  scene.add(directionalLight);\n\n  \/\/ SpotLight (with custom texture if needed)\n  spotLight = utils.createSpotLight(sceneParams);\n  scene.add(spotLight);\n\n  \/\/ SpotLight Helper\n  lightHelper = new THREE.SpotLightHelper(spotLight);\n  lightHelper.visible = sceneParams.showSpotlightHelper;\n  scene.add(lightHelper);\n\n  \/\/ Load the model using global utility\n  try {\n    model = await utils.loadModel(\n      'https:\/\/lebanidze.com\/wp-content\/uploads\/2025\/01\/compressed_1736111245040_Celestial_Cipher_Stone_v3.glb'\n    );\n    \/\/ If there's an emissive material, store it\n    model.traverse((child) => {\n      if (child.isMesh && child.material && child.material.emissive) {\n        emissiveMaterial = child.material;\n      }\n    });\n    \/\/ Position \/ scale\n    model.scale.set(sceneParams.modelScale, sceneParams.modelScale, sceneParams.modelScale);\n    scene.add(model);\n    render();\n  } catch (error) {\n    console.error('Model loading failed:', error);\n  }\n\n  \/\/ Renderer\n  renderer = utils.createRenderer(container);\n\n  \/\/ Set up event listeners\n  setupEventListeners(container);\n}\n\n\/\/ ==================== FOG & PARAM UPDATES ====================\nfunction applyFogSettings(g) {\n  scene.fog = new THREE.Fog(\n    new THREE.Color(g.fogColor),\n    g.fogNear,\n    g.fogFar\n  );\n  scene.background = new THREE.Color(g.fogColor);\n}\n\n\/\/ Called whenever global\/scene parameters change\nfunction updateSceneFromParams(g, s) {\n  \/\/ Fog & background\n  if (scene) {\n    applyFogSettings(g);\n  }\n  \/\/ Camera\n  if (camera) {\n    utils.updateCameraPosition(camera, g);\n    camera.updateProjectionMatrix();\n  }\n  \/\/ Ambient Light\n  if (ambientLight) {\n    ambientLight.color.set(g.ambientColor);\n    ambientLight.intensity = g.ambientIntensity;\n  }\n  \/\/ Directional Light\n  if (directionalLight) {\n    directionalLight.color.set(g.directionalColor);\n    directionalLight.intensity = g.directionalIntensity;\n    utils.updateDirectionalLightPosition(directionalLight, g);\n  }\n  \/\/ SpotLight\n  if (spotLight) {\n    spotLight.color.set(s.spotLightColor);\n    spotLight.intensity = s.spotLightIntensity;\n    spotLight.position.set(s.spotLightPosX, s.spotLightPosY, s.spotLightPosZ);\n    spotLight.angle = s.spotLightAngle;\n    spotLight.penumbra = s.spotLightPenumbra;\n    spotLight.decay = s.spotLightDecay;\n    spotLight.distance = s.spotLightDistance;\n    spotLight.shadow.focus = s.spotLightFocus;\n  }\n  \/\/ Helper\n  if (lightHelper) {\n    lightHelper.visible = s.showSpotlightHelper;\n    lightHelper.update();\n  }\n  \/\/ Model scale\n  if (model) {\n    model.scale.set(s.modelScale, s.modelScale, s.modelScale);\n  }\n  render();\n}\n\n\/\/ ==================== EVENT LISTENERS ====================\nfunction setupEventListeners(container) {\n  function onMouseDown(e) {\n    isDragging = true;\n    previousMousePosition.x = e.clientX;\n    previousMousePosition.y = e.clientY;\n  }\n\n  function onMouseUp() {\n    isDragging = false;\n  }\n\n  function onMouseMove(e) {\n    \/\/ Use global drag logic\n    utils.handleModelDrag(model, isDragging, previousMousePosition, e);\n    render();\n  }\n\n  function onScroll() {\n    const currentScrollY = window.scrollY;\n    scrollDelta += (currentScrollY - previousMousePosition.y);\n    previousMousePosition.y = currentScrollY;\n  }\n\n  function onWindowResize() {\n    const width = container.offsetWidth;\n    const height = container.offsetHeight;\n    camera.aspect = width \/ height;\n    camera.updateProjectionMatrix();\n    renderer.setSize(width, height);\n    render();\n  }\n\n  container.addEventListener('mousedown', onMouseDown);\n  container.addEventListener('mousemove', onMouseMove);\n  container.addEventListener('mouseup', onMouseUp);\n\n  window.addEventListener('scroll', onScroll);\n  window.addEventListener('resize', onWindowResize);\n}\n\n\/\/ ==================== ANIMATION LOOP ====================\nfunction animate() {\n  requestAnimationFrame(animate);\n\n  \/\/ Skip if not in view\n  if (!inView) return;\n\n  statsBegin?.();\n\n  const elapsedTime = clock.getElapsedTime();\n\n  if (model) {\n    \/\/ Global floating & rotation\n    utils.applyFloatingAndRotation(model, elapsedTime);\n\n    \/\/ Additional scroll-based rotation\n    model.rotation.x += scrollDelta * 0.0004;\n    scrollDelta *= 0.97; \/\/ Easing\n\n    \/\/ Emissive fade\n    if (emissiveMaterial) {\n      const rotationAngle = (THREE.MathUtils.radToDeg(model.rotation.y) + 360) % 360;\n      const { emissiveStartAngle, emissiveEndAngle } = globalParams;\n      if (rotationAngle >= emissiveStartAngle && rotationAngle <= emissiveEndAngle) {\n        const normAngle = (rotationAngle - emissiveStartAngle) \/ (emissiveEndAngle - emissiveStartAngle);\n        emissiveMaterial.emissiveIntensity = THREE.MathUtils.lerp(0, 1.0, normAngle);\n      } else {\n        emissiveMaterial.emissiveIntensity = 0;\n      }\n    }\n\n    if (lightHelper) {\n      lightHelper.update();\n    }\n  }\n\n  render();\n  statsEnd?.();\n}\n\nfunction render() {\n  renderer?.render(scene, camera);\n}\n\n\/\/ ==================== LISTEN FOR PARAM CHANGES ====================\naddUpdateListener((allParams) => {\n  updateSceneFromParams(allParams.global, allParams.scene1);\n});\n<\/script>\n\n<!-- Scene container -->\n<div id=\"scene-container-1\" style=\"width: 100%; height: 80vh;\"><\/div>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-377c7d9 elementor-section-full_width elementor-reverse-mobile elementor-section-height-default elementor-section-height-default\" data-id=\"377c7d9\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-no\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-top-column elementor-element elementor-element-24c2671\" data-id=\"24c2671\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-7897753 elementor-widget elementor-widget-html\" data-id=\"7897753\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<script type=\"module\">\nimport * as THREE from 'three';\nimport { GLTFLoader } from 'three\/addons\/loaders\/GLTFLoader.js';\n\n\/\/ Destructure from mySystem\nconst { params, statsBegin, statsEnd, addUpdateListener, utils } = window.mySystem;\n\nlet camera, scene, renderer, model;\nlet ambientLight, directionalLight, spotLight, lightHelper;\nlet emissiveMaterial = null;\n\n\/\/ Interaction variables\nlet isDragging = false;\nlet previousMousePosition = { x: 0, y: 0 };\nlet scrollDelta = 0;\nconst clock = new THREE.Clock();\nlet inView = false; \/\/ For IntersectionObserver usage\n\n\/\/ Initialize and start animating\ninit();\nanimate();\n\n\/\/ ==================== MAIN SETUP ====================\nasync function init() {\n  const container = document.getElementById('scene-container-4');\n  container.style.overflow = 'hidden';\n\n  \/\/ Observe container visibility\n  utils.createIntersectionObserver(container, (visible) => inView = visible);\n\n  \/\/ Camera (global creation + param usage)\n  camera = utils.createCamera(container);\n  utils.updateCameraPosition(camera, params.global);\n\n  \/\/ Scene & Fog\n  scene = new THREE.Scene();\n  applyFogSettings(params.global);\n\n  \/\/ Lighting\n  ambientLight = utils.createAmbientLight(params.global);\n  directionalLight = utils.createDirectionalLight(params.global);\n  spotLight = utils.createSpotLight(params.scene4);\n\n  lightHelper = new THREE.SpotLightHelper(spotLight);\n  lightHelper.visible = params.scene4.showSpotlightHelper;\n\n  scene.add(ambientLight, directionalLight, spotLight, lightHelper);\n\n  \/\/ Load Model\n  try {\n    model = await utils.loadModel(\n      'https:\/\/lebanidze.com\/wp-content\/uploads\/2025\/01\/compressed_1735642001834_Lunar_Stone_v1.glb'\n    );\n    model.traverse(child => {\n      if (child.isMesh && child.material?.emissive) {\n        emissiveMaterial = child.material;\n        emissiveMaterial.emissiveIntensity = 0;\n        \/\/ Ensure materials respond to fog\n        child.material.fog = true;\n      }\n    });\n    scene.add(model);\n    updateModelScale(params.scene4);\n  } catch (error) {\n    console.error('Model loading failed:', error);\n  }\n\n  \/\/ Renderer\n  renderer = utils.createRenderer(container);\n\n  \/\/ Event listeners\n  setupEventListeners(container);\n}\n\n\/\/ ==================== EVENT LISTENERS ====================\nfunction setupEventListeners(container) {\n  function onMouseDown(e) {\n    isDragging = true;\n    previousMousePosition.x = e.clientX;\n    previousMousePosition.y = e.clientY;\n  }\n\n  function onMouseUp() {\n    isDragging = false;\n  }\n\n  function onMouseLeave() {\n    isDragging = false;\n  }\n\n  function updateScroll() {\n    const currentScrollY = window.scrollY;\n    scrollDelta += (currentScrollY - previousMousePosition.y);\n    previousMousePosition.y = currentScrollY;\n  }\n\n  function onWindowResize() {\n    camera.aspect = container.offsetWidth \/ container.offsetHeight;\n    camera.updateProjectionMatrix();\n    renderer.setSize(container.offsetWidth, container.offsetHeight);\n    render();\n  }\n\n  \/\/ Attach\n  container.addEventListener('mousedown', onMouseDown);\n  container.addEventListener('mouseup', onMouseUp);\n  container.addEventListener('mouseleave', onMouseLeave);\n  container.addEventListener('mousemove', (e) => {\n    \/\/ Use the global drag logic\n    utils.handleModelDrag(model, isDragging, previousMousePosition, e);\n    render(); \/\/ Re-render after dragging\n  });\n\n  \/\/ Attach scroll\/resize to window (adjust if container is the scrolling element)\n  window.addEventListener('scroll', updateScroll);\n  window.addEventListener('resize', onWindowResize);\n}\n\n\/\/ ==================== PARAM UPDATES ====================\nfunction applyFogSettings(globalParams) {\n  scene.fog = new THREE.Fog(\n    new THREE.Color(globalParams.fogColor),\n    globalParams.fogNear,\n    globalParams.fogFar\n  );\n  scene.background = new THREE.Color(globalParams.fogColor);\n}\n\nfunction handleParameterUpdates(globalParams, sceneParams) {\n  \/\/ Camera\n  utils.updateCameraPosition(camera, globalParams);\n  camera.updateProjectionMatrix();\n\n  \/\/ Fog & background\n  applyFogSettings(globalParams);\n\n  \/\/ Ambient Light\n  ambientLight.color.set(globalParams.ambientColor);\n  ambientLight.intensity = globalParams.ambientIntensity;\n\n  \/\/ Directional Light\n  directionalLight.color.set(globalParams.directionalColor);\n  directionalLight.intensity = globalParams.directionalIntensity;\n  utils.updateDirectionalLightPosition(directionalLight, globalParams);\n\n  \/\/ SpotLight\n  spotLight.color.set(sceneParams.spotLightColor);\n  spotLight.intensity = sceneParams.spotLightIntensity;\n  spotLight.position.set(\n    sceneParams.spotLightPosX,\n    sceneParams.spotLightPosY,\n    sceneParams.spotLightPosZ\n  );\n  spotLight.angle = sceneParams.spotLightAngle;\n  spotLight.penumbra = sceneParams.spotLightPenumbra;\n  spotLight.decay = sceneParams.spotLightDecay;\n  spotLight.distance = sceneParams.spotLightDistance;\n  spotLight.shadow.focus = sceneParams.spotLightFocus;\n\n  lightHelper.visible = sceneParams.showSpotlightHelper;\n  lightHelper.update();\n\n  \/\/ Model Scale\n  updateModelScale(sceneParams);\n}\n\nfunction updateModelScale(sceneParams) {\n  if (model) {\n    model.scale.setScalar(sceneParams.modelScale);\n  }\n}\n\n\/\/ ==================== ANIMATION LOOP ====================\nfunction animate() {\n  requestAnimationFrame(animate);\n  if (!inView) return; \/\/ Skip expensive ops if not in view\n\n  statsBegin?.();\n\n  const elapsedTime = clock.getElapsedTime();\n\n  if (model) {\n    \/\/ Use the global \"floating + rotation\" logic\n    utils.applyFloatingAndRotation(model, elapsedTime);\n\n    \/\/ Additional scroll-based rotation\n    model.rotation.x += scrollDelta * 0.0004;\n    scrollDelta *= 0.97; \/\/ Easing out\n\n    \/\/ Emissive fade logic\n    if (emissiveMaterial) {\n      const rotationAngle = (THREE.MathUtils.radToDeg(model.rotation.y) + 360) % 360;\n      const { emissiveStartAngle, emissiveEndAngle } = params.global;\n\n      if (rotationAngle >= emissiveStartAngle && rotationAngle <= emissiveEndAngle) {\n        const normAngle = (rotationAngle - emissiveStartAngle) \/ (emissiveEndAngle - emissiveStartAngle);\n        emissiveMaterial.emissiveIntensity = THREE.MathUtils.lerp(0, 1.0, normAngle);\n      } else {\n        emissiveMaterial.emissiveIntensity = 0;\n      }\n    }\n\n    if (lightHelper.visible) {\n      lightHelper.update();\n    }\n  }\n\n  render();\n  statsEnd?.();\n}\n\nfunction render() {\n  renderer?.render(scene, camera);\n}\n\n\/\/ Listen for parameter updates\naddUpdateListener((allParams) => {\n  handleParameterUpdates(allParams.global, allParams.scene4);\n  render(); \/\/ Re-render immediately after param change\n});\n<\/script>\n\n<!-- The container for this scene -->\n<div id=\"scene-container-4\" style=\"width: 100%; height: 80vh;\"><\/div>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-top-column elementor-element elementor-element-e05d2f6\" data-id=\"e05d2f6\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-d9b63b6 elementor-widget elementor-widget-text-editor\" data-id=\"d9b63b6\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<p><em>This rock does not have anything written on it&#8230;<\/em><\/p>\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-4b7a286 elementor-section-full_width elementor-section-height-default elementor-section-height-default\" data-id=\"4b7a286\" data-element_type=\"section\" data-settings=\"{&quot;background_background&quot;:&quot;video&quot;,&quot;background_video_link&quot;:&quot;https:\\\/\\\/lebanidze.com\\\/wp-content\\\/uploads\\\/2020\\\/11\\\/Bow_Balcony2.mp4&quot;}\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-background-video-container elementor-hidden-phone\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<video class=\"elementor-background-video-hosted elementor-html5-video\" autoplay muted playsinline loop><\/video>\n\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t\t<div class=\"elementor-background-overlay\"><\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-no\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-b03faba\" data-id=\"b03faba\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-inner-section elementor-element elementor-element-1785967 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"1785967\" data-element_type=\"section\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-inner-column elementor-element elementor-element-385f3d6\" data-id=\"385f3d6\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap\">\n\t\t\t\t\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t<div class=\"elementor-column elementor-col-50 elementor-inner-column elementor-element elementor-element-c69bb70\" data-id=\"c69bb70\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-4a084ee elementor-widget elementor-widget-text-editor\" data-id=\"4a084ee\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<p><b>ABOUT<\/b><\/p><p><span style=\"font-size: 15.4px;\">I blend my lifelong passion for technology, art, and experiments. This journey, rooted in many of my childhood interests, such as piano, watercolor, and video game modding, has led me on an exciting path. I oversee the creation and delivery of audio services and foster collaboration between our teams at POSTRED and video game studios.<\/span><\/p><p><span style=\"font-size: 15.4px;\">\u00a0<\/span><\/p><p><span style=\"font-size: 15.4px;\">\u00a0<\/span><\/p><p><span style=\"font-size: 15.4px;\">\u00a0<\/span><\/p><p><span style=\"font-size: 15.4px;\">\u00a0<\/span><\/p>\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-33b31df elementor-section-full_width elementor-section-height-min-height elementor-hidden-desktop elementor-hidden-tablet elementor-hidden-mobile elementor-section-height-default elementor-section-items-middle\" data-id=\"33b31df\" data-element_type=\"section\" data-settings=\"{&quot;background_background&quot;:&quot;video&quot;}\">\n\t\t\t\t\t\t\t<div class=\"elementor-background-overlay\"><\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-no\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-1ce369c\" data-id=\"1ce369c\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-inner-section elementor-element elementor-element-8a81614 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"8a81614\" data-element_type=\"section\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-inner-column elementor-element elementor-element-5888c99\" data-id=\"5888c99\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-a6aca4f e-transform e-transform elementor-widget elementor-widget-image\" data-id=\"a6aca4f\" data-element_type=\"widget\" data-settings=\"{&quot;_transform_scale_effect&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:0.6,&quot;sizes&quot;:[]},&quot;_transform_translateX_effect&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_translateX_effect_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_translateX_effect_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_translateY_effect&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_translateY_effect_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_translateY_effect_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_scale_effect_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_scale_effect_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]}}\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<style>\/*! elementor - v3.6.6 - 08-06-2022 *\/\n.elementor-widget-image{text-align:center}.elementor-widget-image a{display:inline-block}.elementor-widget-image a img[src$=\".svg\"]{width:48px}.elementor-widget-image img{vertical-align:middle;display:inline-block}<\/style>\t\t\t\t\t\t\t\t\t\t\t\t<img width=\"640\" height=\"962\" src=\"https:\/\/lebanidze.com\/wp-content\/uploads\/2023\/10\/Luka-Lebanidze-Orange-4-768x1154.jpg\" class=\"attachment-medium_large size-medium_large\" alt=\"\" loading=\"lazy\" srcset=\"https:\/\/lebanidze.com\/wp-content\/uploads\/2023\/10\/Luka-Lebanidze-Orange-4-768x1154.jpg 768w, https:\/\/lebanidze.com\/wp-content\/uploads\/2023\/10\/Luka-Lebanidze-Orange-4-200x300.jpg 200w, https:\/\/lebanidze.com\/wp-content\/uploads\/2023\/10\/Luka-Lebanidze-Orange-4-681x1024.jpg 681w, https:\/\/lebanidze.com\/wp-content\/uploads\/2023\/10\/Luka-Lebanidze-Orange-4-1022x1536.jpg 1022w, https:\/\/lebanidze.com\/wp-content\/uploads\/2023\/10\/Luka-Lebanidze-Orange-4-1363x2048.jpg 1363w, https:\/\/lebanidze.com\/wp-content\/uploads\/2023\/10\/Luka-Lebanidze-Orange-4-1568x2357.jpg 1568w, https:\/\/lebanidze.com\/wp-content\/uploads\/2023\/10\/Luka-Lebanidze-Orange-4-scaled.jpg 1703w\" sizes=\"(max-width: 34.9rem) calc(100vw - 2rem), (max-width: 53rem) calc(8 * (100vw \/ 12)), (min-width: 53rem) calc(6 * (100vw \/ 12)), 100vw\" \/>\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-8da0146 elementor-section-full_width elementor-section-height-default elementor-section-height-default\" data-id=\"8da0146\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-a1303ae\" data-id=\"a1303ae\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-inner-section elementor-element elementor-element-6588728 elementor-section-full_width elementor-section-height-default elementor-section-height-default\" data-id=\"6588728\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-inner-column elementor-element elementor-element-67c9ac8\" data-id=\"67c9ac8\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-d7bf2d0 elementor-widget elementor-widget-html\" data-id=\"d7bf2d0\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<script type=\"module\">\nimport * as THREE from 'three'; \n\/\/ If you need GLTFLoader for local use, but typically you can rely on global if it's in mySystem.utils\nimport { GLTFLoader } from 'three\/addons\/loaders\/GLTFLoader.js';\n\n\/\/ Destructure what you need from the global system\nconst { params, addUpdateListener, utils, statsBegin, statsEnd } = window.mySystem;\nconst globalParams = params.global;\nconst sceneParams = params.scene5;\n\n\/\/ Scene-level variables\nlet camera, scene, renderer;\nlet ambientLight, directionalLight, spotLight, lightHelper;\nlet model = null; \nlet emissiveMaterial = null;\nlet inView = false;\n\n\/\/ Interaction\nlet isDragging = false;\nlet previousMousePosition = { x: 0, y: 0 };\nlet scrollDelta = 0;\n\n\/\/ Clock for animation\nconst clock = new THREE.Clock();\n\n\/\/ 1) Init + Animate\ninit();\nanimate();\n\n\/\/ ==================== INIT ====================\nasync function init() {\n  const container = document.getElementById('scene-container-5');\n  container.style.width = '100%';\n  container.style.height = '95vh';\n  container.style.overflow = 'hidden';\n\n  \/\/ IntersectionObserver via global utils\n  utils.createIntersectionObserver(container, (visible) => {\n    inView = visible;\n  });\n\n  \/\/ CAMERA\n  camera = utils.createCamera(container);\n  utils.updateCameraPosition(camera, globalParams);\n\n  \/\/ SCENE & FOG\n  scene = new THREE.Scene();\n  applyFogSettings(globalParams);\n\n  \/\/ AMBIENT LIGHT\n  ambientLight = utils.createAmbientLight(globalParams);\n  scene.add(ambientLight);\n\n  \/\/ DIRECTIONAL LIGHT\n  directionalLight = utils.createDirectionalLight(globalParams);\n  scene.add(directionalLight);\n\n  \/\/ SPOTLIGHT (Override projection texture)\n  spotLight = utils.createSpotLight(sceneParams);\n  scene.add(spotLight);\n\n  \/\/ Override the projection texture in **this scene only**\n  const textureLoader = new THREE.TextureLoader();\n  const customProjectionTexture = textureLoader.load('https:\/\/lebanidze.com\/wp-content\/uploads\/2025\/01\/Cookie_tutorial_texture_flashlight.png');\n\n  \/\/ Apply the custom texture to the spotlight\n  spotLight.map = customProjectionTexture;\n\n  \/\/ SPOTLIGHT HELPER\n  lightHelper = new THREE.SpotLightHelper(spotLight);\n  lightHelper.visible = sceneParams.showSpotlightHelper;\n  scene.add(lightHelper);\n\n  \/\/ RENDERER\n  renderer = utils.createRenderer(container);\n\n  \/\/ LOAD the model with custom logic (video texture, etc.)\n  await loadOsciModel();\n\n  \/\/ Event Listeners\n  setupEventListeners(container);\n}\n\n\/\/ Apply global fog params\nfunction applyFogSettings(g) {\n  scene.fog = new THREE.Fog(\n    new THREE.Color(g.fogColor),\n    g.fogNear,\n    g.fogFar\n  );\n  scene.background = new THREE.Color(g.fogColor);\n}\n\n\/\/ GLTF model load\nasync function loadOsciModel() {\n  try {\n    const gltfScene = await utils.loadModel(\n      'https:\/\/lebanidze.com\/wp-content\/uploads\/2025\/01\/compressed_1736368319391_Osci_scene_v3.glb'\n    );\n    \n    \/\/ Remove embedded cameras or lights\n    gltfScene.traverse((child) => {\n      if (child.isCamera || child.isLight) {\n        child.parent?.remove(child);\n      }\n    });\n\n    \/\/ Attempt to find \"Device\"\n    const deviceObj = gltfScene.getObjectByName('Device');\n    model = deviceObj || gltfScene; \n    scene.add(model);\n\n    \/\/ Initial transform\n    model.position.set(0, 0, 0);\n    model.scale.set(sceneParams.modelScale, sceneParams.modelScale, sceneParams.modelScale);\n\n    \/\/ Video texture for \"Screen_Paper\"\n    const video = document.createElement('video');\n    video.src = 'https:\/\/lebanidze.com\/wp-content\/uploads\/2025\/01\/oscilloscope-recorded-in-the-lab-no-audio.mp4';\n    video.loop = true;\n    video.muted = true;\n    video.playsInline = true;\n    video.autoplay = true;\n    \/\/ Attempt to play\n    video.play().catch((err) => {\n      console.warn('Video autoplay failed, user gesture needed:', err);\n    });\n\n    const videoTexture = new THREE.VideoTexture(video);\n    videoTexture.encoding = THREE.sRGBEncoding;\n    videoTexture.minFilter = THREE.LinearFilter;\n    videoTexture.magFilter = THREE.LinearFilter;\n\n    \/\/ Traverse for mesh & emissive\n    model.traverse((child) => {\n      if (child.isMesh) {\n        child.castShadow = true;\n        child.receiveShadow = true;\n\n        if (child.name === 'Screen_Paper' && child.material) {\n          \/\/ Video on emissive\n          child.material.emissiveMap = videoTexture;\n          child.material.emissive = new THREE.Color(0x00ffa0);\n          child.material.emissiveIntensity = 5.0;\n          child.material.needsUpdate = true;\n        }\n\n        \/\/ If there's a generic emissive\n        if (child.material.emissive) {\n          emissiveMaterial = child.material;\n        }\n      }\n    });\n\n    render();\n  } catch (error) {\n    console.error('Error loading the GLB:', error);\n  }\n}\n\n\/\/ ==================== EVENT LISTENERS ====================\nfunction setupEventListeners(container) {\n  function onMouseDown(e) {\n    isDragging = true;\n    previousMousePosition.x = e.clientX;\n    previousMousePosition.y = e.clientY;\n  }\n  \n  function onMouseMove(e) {\n    \/\/ Use global handleModelDrag\n    utils.handleModelDrag(model, isDragging, previousMousePosition, e);\n    render();\n  }\n\n  function onMouseUp() {\n    isDragging = false;\n  }\n\n  function onScroll() {\n    const currentScrollY = window.scrollY;\n    scrollDelta += (currentScrollY - previousMousePosition.y);\n    previousMousePosition.y = currentScrollY;\n  }\n\n  function onWindowResize() {\n    const width = container.offsetWidth;\n    const height = container.offsetHeight;\n    camera.aspect = width \/ height;\n    camera.updateProjectionMatrix();\n    renderer.setSize(width, height);\n    render();\n  }\n\n  container.addEventListener('mousedown', onMouseDown);\n  container.addEventListener('mousemove', onMouseMove);\n  container.addEventListener('mouseup', onMouseUp);\n\n  window.addEventListener('scroll', onScroll);\n  window.addEventListener('resize', onWindowResize);\n}\n\n\/\/ ==================== PARAM UPDATES ====================\naddUpdateListener((allParams) => {\n  updateSceneFromParams(allParams.global, allParams.scene5);\n});\n\nfunction updateSceneFromParams(g, s) {\n  \/\/ Fog & background\n  if (scene) {\n    applyFogSettings(g);\n  }\n  \/\/ Camera\n  if (camera) {\n    utils.updateCameraPosition(camera, g);\n    camera.updateProjectionMatrix();\n  }\n  \/\/ Ambient\n  if (ambientLight) {\n    ambientLight.color.set(g.ambientColor);\n    ambientLight.intensity = g.ambientIntensity;\n  }\n  \/\/ Directional\n  if (directionalLight) {\n    directionalLight.color.set(g.directionalColor);\n    directionalLight.intensity = g.directionalIntensity;\n    utils.updateDirectionalLightPosition(directionalLight, g);\n  }\n  \/\/ Model scale\n  if (model) {\n    model.scale.set(s.modelScale, s.modelScale, s.modelScale);\n  }\n  \/\/ SpotLight\n  if (spotLight) {\n    spotLight.color.set(s.spotLightColor);\n    spotLight.intensity = s.spotLightIntensity;\n    spotLight.position.set(s.spotLightPosX, s.spotLightPosY, s.spotLightPosZ);\n    spotLight.angle = s.spotLightAngle;\n    spotLight.penumbra = s.spotLightPenumbra;\n    spotLight.decay = s.spotLightDecay;\n    spotLight.distance = s.spotLightDistance;\n    spotLight.shadow.focus = s.spotLightFocus;\n  }\n  \/\/ Helper\n  if (lightHelper) {\n    lightHelper.visible = s.showSpotlightHelper;\n    lightHelper.update();\n  }\n\n  render();\n}\n\n\/\/ ==================== ANIMATE LOOP ====================\nfunction animate() {\n  requestAnimationFrame(animate);\n  if (!inView) return;  \/\/ skip if not visible\n\n  statsBegin?.();\n\n  const elapsedTime = clock.getElapsedTime();\n\n  if (model) {\n    \/\/ Optionally apply global \"floating and rotation\"\n    utils.applyFloatingAndRotation(model, elapsedTime);\n\n    \/\/ Additional scroll-based rotation (uncomment if desired)\n    \/\/ model.rotation.x += scrollDelta * 0.000016;\n    scrollDelta *= 0.97;\n\n    \/\/ Emissive fade\n    if (emissiveMaterial) {\n      const rotationAngle = (THREE.MathUtils.radToDeg(model.rotation.y) + 360) % 360;\n      const { emissiveStartAngle, emissiveEndAngle } = globalParams;\n\n      if (rotationAngle >= emissiveStartAngle && rotationAngle <= emissiveEndAngle) {\n        const normAngle = (rotationAngle - emissiveStartAngle) \/ (emissiveEndAngle - emissiveStartAngle);\n        emissiveMaterial.emissiveIntensity = THREE.MathUtils.lerp(0, 1.0, normAngle);\n      } else {\n        emissiveMaterial.emissiveIntensity = 0.0;\n      }\n    }\n    if (lightHelper) lightHelper.update();\n  }\n\n  render();\n  statsEnd?.();\n}\n\nfunction render() {\n  if (renderer && scene && camera) {\n    renderer.render(scene, camera);\n  }\n}\n<\/script>\n\n<!-- Scene #5 Container -->\n<div id=\"scene-container-5\" style=\"width: 100%; height: 80vh;\">\n<\/div>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-10c0205 elementor-hidden-desktop elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-html\" data-id=\"10c0205\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<!-- =============================================== -->\n<!-- MODULE #6: MILITARY LAPTOP MODEL -->\n<!-- =============================================== -->\n<script type=\"module\">\nimport * as THREE from 'three';\nimport { GLTFLoader } from 'three\/addons\/loaders\/GLTFLoader.js';\n\n\/\/ 1) Pull in the global + scene #6 params from the init script\nconst globalParams = window.mySystem.params.global;\nconst sceneParams = window.mySystem.params.scene6;\n\n\/\/ 2) Listen for any dat.GUI changes\nwindow.mySystem.addUpdateListener((allParams) => {\n  updateSceneFromParams(allParams.global, allParams.scene6);\n});\n\n\/\/ 3) Scene-wide variables\nlet camera, scene, renderer;\nlet ambientLight, directionalLight;\nlet spotLight, lightHelper;\nlet model = null; \/\/ We'll store the \"Device\" node here\nlet emissiveMaterial = null;\n\n\/\/ IntersectionObserver\nlet inView = false;\n\n\/\/ Mouse\/scroll tracking\nlet isDragging = false;\nlet previousMousePosition = { x: 0, y: 0 };\nlet scrollDelta = 0;\nconst clock = new THREE.Clock();\n\n\/\/ Initialize & Animate\ninit();\nanimate();\n\n\/** \n * Sets up scene, camera, lights, renderer, & loads the model \n *\/\nfunction init() {\n  \/\/ Grab your container div\n  const container = document.getElementById('scene-container-6');\n  container.style.width = '100%';\n  container.style.height = '95vh';\n  container.style.overflow = 'hidden';\n\n  \/\/ IntersectionObserver to detect when container is in view\n  const observer = new IntersectionObserver((entries) => {\n    entries.forEach((entry) => {\n      inView = entry.isIntersecting;\n    });\n  }, { root: null, threshold: 0.01 });\n  observer.observe(container);\n\n  \/\/ Dimensions\n  const width = container.offsetWidth;\n  const height = container.offsetHeight;\n\n  \/\/ CAMERA (our own)\n  camera = new THREE.PerspectiveCamera(30, width \/ height, 0.25, 20);\n  camera.position.set(globalParams.cameraX, globalParams.cameraY, globalParams.cameraZ);\n\n  \/\/ SCENE\n  scene = new THREE.Scene();\n  scene.fog = new THREE.Fog(\n    new THREE.Color(globalParams.fogColor),\n    globalParams.fogNear,\n    globalParams.fogFar\n  );\n  scene.background = new THREE.Color(globalParams.fogColor);\n\n  \/\/ AMBIENT LIGHT (our own)\n  ambientLight = new THREE.AmbientLight(globalParams.ambientColor, globalParams.ambientIntensity);\n  scene.add(ambientLight);\n\n  \/\/ DIRECTIONAL LIGHT (our own)\n  directionalLight = new THREE.DirectionalLight(\n    globalParams.directionalColor, \n    globalParams.directionalIntensity\n  );\n  directionalLight.position.set(\n    globalParams.directionalPosX, \n    globalParams.directionalPosY, \n    globalParams.directionalPosZ\n  );\n  scene.add(directionalLight);\n\n  \/\/ SPOT LIGHT (normal, no texture projection)\n  spotLight = new THREE.SpotLight(sceneParams.spotLightColor, sceneParams.spotLightIntensity);\n  spotLight.position.set(sceneParams.spotLightPosX, sceneParams.spotLightPosY, sceneParams.spotLightPosZ);\n  spotLight.angle = sceneParams.spotLightAngle;\n  spotLight.penumbra = sceneParams.spotLightPenumbra;\n  spotLight.decay = sceneParams.spotLightDecay;\n  spotLight.distance = sceneParams.spotLightDistance;\n  spotLight.castShadow = true;\n  spotLight.shadow.mapSize.width = 1024;\n  spotLight.shadow.mapSize.height = 1024;\n  spotLight.shadow.camera.near = 1;\n  spotLight.shadow.camera.far = 10;\n  spotLight.shadow.focus = sceneParams.spotLightFocus;\n  scene.add(spotLight);\n\n  \/\/ SPOT LIGHT HELPER\n  lightHelper = new THREE.SpotLightHelper(spotLight);\n  lightHelper.visible = sceneParams.showSpotlightHelper;\n  scene.add(lightHelper);\n\n  \/\/ RENDERER\n  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n  renderer.setPixelRatio(window.devicePixelRatio);\n  renderer.setSize(width, height);\n  renderer.toneMapping = THREE.ACESFilmicToneMapping;\n  renderer.toneMappingExposure = 1;\n  renderer.shadowMap.enabled = true;\n  renderer.shadowMap.type = THREE.PCFSoftShadowMap;\n  renderer.setClearColor(0x000000, 0);\n  container.appendChild(renderer.domElement);\n\n  \/\/ LOAD THE GLB but remove any embedded cameras\/lights & get \"Device\"\n  loadLaptopModel();\n\n  \/\/ Event listeners\n  container.addEventListener('mousedown', onMouseDown);\n  container.addEventListener('mousemove', onMouseMove);\n  container.addEventListener('mouseup', onMouseUp);\n  window.addEventListener('scroll', onScroll);\n  window.addEventListener('resize', onWindowResize);\n}\n\n\/** \n * GLTFLoader that finds the \"Device\" node,\n * removes any glTF cameras\/lights, and keeps only the meshes\n * Then we attach a video as an emissive map on \"Screen\".\n *\/\nfunction loadLaptopModel() {\n  const loader = new GLTFLoader();\n  loader.load(\n    'https:\/\/lebanidze.com\/wp-content\/uploads\/2025\/01\/Military-laptop_v1b.glb',\n    (gltf) => {\n      \/\/ Remove embedded cameras or lights\n      gltf.scene.traverse((child) => {\n        if (child.isCamera || child.isLight) {\n          child.parent?.remove(child);\n        }\n      });\n\n      \/\/ Attempt to find the \"Device\" node by name\n      const deviceObj = gltf.scene.getObjectByName('Device');\n      if (deviceObj) {\n        model = deviceObj;\n      } else {\n        console.warn(\"No 'Device' object found in glTF. Falling back to entire scene.\");\n        model = gltf.scene;\n      }\n\n      \/\/ Add entire \"Device\" group to the scene\n      scene.add(model);\n\n      \/\/ Set initial transform based on sceneParams\n      model.position.set(0, 0, 0);\n      model.scale.set(sceneParams.modelScale, sceneParams.modelScale, sceneParams.modelScale);\n\n      \/\/ Prepare the video for the emissive map\n      const video = document.createElement('video');\n      video.src = 'https:\/\/lebanidze.com\/wp-content\/uploads\/2025\/01\/Interactive-began.mp4';\n      video.loop = true;\n      video.muted = true;\n      video.playsInline = true;\n      video.autoplay = true;\n\n      \/\/ Attempt to start playing the video\n      video.play().catch((err) => {\n        console.warn('Video did not autoplay; user gesture is required:', err);\n      });\n\n      \/\/ Convert the <video> into a texture\n      const videoTexture = new THREE.VideoTexture(video);\n      videoTexture.encoding = THREE.sRGBEncoding;\n      videoTexture.minFilter = THREE.LinearFilter;\n      videoTexture.magFilter = THREE.LinearFilter;\n\n      \/\/ Traverse to set up shadows\/emissiveMaterial references\n      model.traverse((child) => {\n        if (child.isMesh) {\n          child.castShadow = true;\n          child.receiveShadow = true;\n          \n          \/\/ If we find \"Screen\", map the video texture to its emissive channel\n          if (child.name === 'Screen' && child.material) {\n            \/\/ Keep its base texture from glTF, but add video as emissive\n            child.material.emissiveMap = videoTexture;\n            child.material.emissive = new THREE.Color(0x00ffaa);\n            child.material.emissiveIntensity = 5.0;\n            child.material.needsUpdate = true;\n          }\n\n          \/\/ If there's a generic emissive, store reference for fade logic\n          if (child.material && child.material.emissive) {\n            emissiveMaterial = child.material;\n          }\n        }\n      });\n\n      render();\n    },\n    undefined,\n    (err) => {\n      console.error('Error loading the GLB:', err);\n    }\n  );\n}\n\n\/** \n * Re-apply updated params whenever dat.GUI changes \n *\/\nfunction updateSceneFromParams(g, s) {\n  \/\/ Fog & background\n  if (scene) {\n    scene.fog.color.set(g.fogColor);\n    scene.fog.near = g.fogNear;\n    scene.fog.far = g.fogFar;\n    scene.background.set(g.fogColor);\n  }\n  \/\/ Camera position\n  if (camera) {\n    camera.position.set(g.cameraX, g.cameraY, g.cameraZ);\n    camera.updateProjectionMatrix();\n  }\n  \/\/ Ambient light\n  if (ambientLight) {\n    ambientLight.color.set(g.ambientColor);\n    ambientLight.intensity = g.ambientIntensity;\n  }\n  \/\/ Directional light\n  if (directionalLight) {\n    directionalLight.color.set(g.directionalColor);\n    directionalLight.intensity = g.directionalIntensity;\n    directionalLight.position.set(g.directionalPosX, g.directionalPosY, g.directionalPosZ);\n  }\n  \/\/ Model scale\n  if (model) {\n    model.scale.set(s.modelScale, s.modelScale, s.modelScale);\n  }\n  \/\/ SpotLight\n  if (spotLight) {\n    spotLight.color.set(s.spotLightColor);\n    spotLight.intensity = s.spotLightIntensity;\n    spotLight.position.set(s.spotLightPosX, s.spotLightPosY, s.spotLightPosZ);\n    spotLight.angle = s.spotLightAngle;\n    spotLight.penumbra = s.spotLightPenumbra;\n    spotLight.decay = s.spotLightDecay;\n    spotLight.distance = s.spotLightDistance;\n    spotLight.shadow.focus = s.spotLightFocus;\n  }\n  \/\/ Helper\n  if (lightHelper) {\n    lightHelper.visible = s.showSpotlightHelper;\n    lightHelper.update();\n  }\n\n  render();\n}\n\n\/\/ MOUSE EVENTS for simple rotation\nfunction onMouseDown(event) {\n  isDragging = true;\n  previousMousePosition.x = event.clientX;\n  previousMousePosition.y = event.clientY;\n}\nfunction onMouseMove(event) {\n  if (!isDragging || !model) return;\n\n  const deltaX = event.clientX - previousMousePosition.x;\n  const deltaY = event.clientY - previousMousePosition.y;\n  const rotationSpeed = 0.005;\n\n  \/\/ Rotate the \"Device\" group\n  model.rotation.y += deltaX * rotationSpeed;\n  model.rotation.x += deltaY * rotationSpeed;\n\n  previousMousePosition.x = event.clientX;\n  previousMousePosition.y = event.clientY;\n  render();\n}\nfunction onMouseUp() {\n  isDragging = false;\n}\nfunction onScroll() {\n  scrollDelta += window.scrollY - previousMousePosition.y;\n  previousMousePosition.y = window.scrollY;\n}\nfunction onWindowResize() {\n  const container = document.getElementById('scene-container-6');\n  const width = container.offsetWidth;\n  const height = container.offsetHeight;\n  camera.aspect = width \/ height;\n  camera.updateProjectionMatrix();\n  renderer.setSize(width, height);\n  render();\n}\n\n\/\/ MAIN ANIMATE LOOP\nfunction animate() {\n  requestAnimationFrame(animate);\n\n  \/\/ Skip if container isn't visible\n  if (!inView) return;\n\n  \/\/ Start stats if debug enabled\n  window.mySystem.statsBegin?.();\n\n  const elapsedTime = clock.getElapsedTime();\n\n  \/\/ Bob, sway, scroll-based rotation, emissive fade\n  if (model) {\n    \/\/ Bob\n    const yMovement = Math.sin(elapsedTime * 1.5) * 0.05;\n    model.position.y = yMovement;\n\n    \/\/ Sway\n    const sway = Math.sin(elapsedTime * 0.8) * THREE.MathUtils.degToRad(2);\n    model.rotation.z = sway;\n\n    \/\/ Scroll-based rotation\n    model.rotation.x += scrollDelta * 0.000016;\n    scrollDelta *= 0.97;\n\n    \/\/ Emissive fade (applies if the mesh uses .emissiveMaterial)\n    if (emissiveMaterial) {\n      const rotationAngle = (THREE.MathUtils.radToDeg(model.rotation.y) + 360) % 360;\n      const startAngle = globalParams.emissiveStartAngle;\n      const endAngle = globalParams.emissiveEndAngle;\n\n      if (rotationAngle >= startAngle && rotationAngle <= endAngle) {\n        const normAngle = (rotationAngle - startAngle) \/ (endAngle - startAngle);\n        emissiveMaterial.emissiveIntensity = THREE.MathUtils.lerp(0, 1.0, normAngle);\n      } else {\n        emissiveMaterial.emissiveIntensity = 0.0;\n      }\n    }\n  }\n\n  \/\/ Keep spot helper up to date\n  if (lightHelper) lightHelper.update();\n\n  \/\/ End stats if debug enabled\n  window.mySystem.statsEnd?.();\n\n  render();\n}\n\n\/\/ ONE-OFF RENDER\nfunction render() {\n  if (renderer && scene && camera) {\n    renderer.render(scene, camera);\n  }\n}\n<\/script>\n\n<!-- Container for Scene #6 -->\n<div id=\"scene-container-6\" style=\"width: 100%; height: 95vh;\">\n<\/div>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-ad0dc10 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"ad0dc10\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-41583be\" data-id=\"41583be\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-39e6fa9 elementor-widget elementor-widget-slider_revolution\" data-id=\"39e6fa9\" data-element_type=\"widget\" data-widget_type=\"slider_revolution.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\n\t\t<div class=\"wp-block-themepunch-revslider\">\n\t\t\t<!-- START project highlights REVOLUTION SLIDER 6.5.24 --><p class=\"rs-p-wp-fix\"><\/p>\n\t\t\t<rs-module-wrap id=\"rev_slider_6_1_wrapper\" data-source=\"gallery\" style=\"visibility:hidden;background:transparent;padding:0;margin:0px auto;margin-top:0;margin-bottom:0;\">\n\t\t\t\t<rs-module id=\"rev_slider_6_1\" style=\"\" data-version=\"6.5.24\">\n\t\t\t\t\t<rs-slides>\n\t\t\t\t\t\t<rs-slide style=\"position: absolute;\" data-key=\"rs-7\" data-title=\"Slide\" data-in=\"o:0;\" data-out=\"a:false;\">\n\t\t\t\t\t\t\t<img src=\"\/\/lebanidze.com\/wp-content\/plugins\/revslider\/public\/assets\/assets\/dummy.png\" alt=\"Slide\" title=\"Home page\" class=\"rev-slidebg tp-rs-img rs-lazyload\" data-lazyload=\"\/\/lebanidze.com\/wp-content\/plugins\/revslider\/public\/assets\/assets\/transparent.png\" data-no-retina>\n<!--\n\t\t\t\t\t\t\t--><rs-layer\n\t\t\t\t\t\t\t\tid=\"slider-6-slide-7-layer-2\" \n\t\t\t\t\t\t\t\tclass=\"rs-layer-video intrinsic-ignore\"\n\t\t\t\t\t\t\t\tdata-type=\"video\"\n\t\t\t\t\t\t\t\tdata-rsp_ch=\"on\"\n\t\t\t\t\t\t\t\tdata-xy=\"x:c;xo:197px;y:c;\"\n\t\t\t\t\t\t\t\tdata-text=\"w:normal;\"\n\t\t\t\t\t\t\t\tdata-dim=\"w:569px;h:320px;\"\n\t\t\t\t\t\t\t\tdata-video=\"twa:false;ap:false;v:100;vd:100;l:false;ptimer:true;nse:f;\"\n\t\t\t\t\t\t\t\tdata-vimeoid=\"382584919\"\n\t\t\t\t\t\t\t\tdata-vatr=\"background=1&amp;title=0&amp;byline=0&amp;portrait=0&amp;api=1\"\n\t\t\t\t\t\t\t\tdata-frame_999=\"o:0;st:w;\"\n\t\t\t\t\t\t\t\tstyle=\"z-index:8;\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<\/rs-layer><!--\n\n\t\t\t\t\t\t\t--><rs-layer\n\t\t\t\t\t\t\t\tid=\"slider-6-slide-7-layer-3\" \n\t\t\t\t\t\t\t\tdata-type=\"text\"\n\t\t\t\t\t\t\t\tdata-color=\"#e5f9f9\"\n\t\t\t\t\t\t\t\tdata-rsp_ch=\"on\"\n\t\t\t\t\t\t\t\tdata-xy=\"x:18px;y:c;\"\n\t\t\t\t\t\t\t\tdata-text=\"w:normal;s:16;l:24;ls:2;a:right;\"\n\t\t\t\t\t\t\t\tdata-dim=\"w:390px;h:269px;minh:0px;\"\n\t\t\t\t\t\t\t\tdata-frame_999=\"o:0;st:w;\"\n\t\t\t\t\t\t\t\tstyle=\"z-index:9;font-family:'Open Sans';\"\n\t\t\t\t\t\t\t>Salt from Bonneville<br \/>\n<br \/>\nA film by Simon Mozgovyi <br \/>\nProduction: Mainstream Pictures, <br \/>\n<br \/>\nMusic by Zviad Mgebry, Luka Lebanidze <br \/>\n<br \/>\n(Sound and music: Postred) \n\t\t\t\t\t\t\t<\/rs-layer><!--\n-->\t\t\t\t\t\t<\/rs-slide>\n\t\t\t\t\t\t<rs-slide style=\"position: absolute;\" data-key=\"rs-6\" data-title=\"Slide\" data-anim=\"ms:1000;r:0;\" data-in=\"o:0;\" data-out=\"a:false;\">\n\t\t\t\t\t\t\t<img src=\"\/\/lebanidze.com\/wp-content\/plugins\/revslider\/public\/assets\/assets\/dummy.png\" alt=\"Slide\" title=\"Home page\" class=\"rev-slidebg tp-rs-img rs-lazyload\" data-lazyload=\"\/\/lebanidze.com\/wp-content\/plugins\/revslider\/public\/assets\/assets\/transparent.png\" data-no-retina>\n<!--\n\t\t\t\t\t\t\t--><rs-layer\n\t\t\t\t\t\t\t\tid=\"slider-6-slide-6-layer-0\" \n\t\t\t\t\t\t\t\tclass=\"rs-layer-video intrinsic-ignore\"\n\t\t\t\t\t\t\t\tdata-type=\"video\"\n\t\t\t\t\t\t\t\tdata-rsp_ch=\"on\"\n\t\t\t\t\t\t\t\tdata-xy=\"x:c;xo:192px;y:c;\"\n\t\t\t\t\t\t\t\tdata-text=\"w:normal;\"\n\t\t\t\t\t\t\t\tdata-dim=\"w:569px;h:320px;\"\n\t\t\t\t\t\t\t\tdata-video=\"twa:false;ap:false;v:100;vd:100;l:false;ptimer:true;nse:f;\"\n\t\t\t\t\t\t\t\tdata-vimeoid=\"288141754\"\n\t\t\t\t\t\t\t\tdata-vatr=\"background=1&amp;title=0&amp;byline=0&amp;portrait=0&amp;api=1\"\n\t\t\t\t\t\t\t\tdata-frame_999=\"o:0;st:w;\"\n\t\t\t\t\t\t\t\tstyle=\"z-index:8;\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<\/rs-layer><!--\n\n\t\t\t\t\t\t\t--><rs-layer\n\t\t\t\t\t\t\t\tid=\"slider-6-slide-6-layer-1\" \n\t\t\t\t\t\t\t\tdata-type=\"text\"\n\t\t\t\t\t\t\t\tdata-color=\"#e5f9f9\"\n\t\t\t\t\t\t\t\tdata-rsp_ch=\"on\"\n\t\t\t\t\t\t\t\tdata-xy=\"x:35px;y:c;\"\n\t\t\t\t\t\t\t\tdata-text=\"w:normal;s:16;l:24;ls:2;\"\n\t\t\t\t\t\t\t\tdata-dim=\"w:390px;h:240px;minh:0px;\"\n\t\t\t\t\t\t\t\tdata-frame_999=\"o:0;st:w;\"\n\t\t\t\t\t\t\t\tstyle=\"z-index:9;font-family:'Open Sans';\"\n\t\t\t\t\t\t\t>I love to tell stories with music. <br \/>\n<br \/>\nSomething that inspires me is that we do not have to perceive music consciously to be reached by it. \n\t\t\t\t\t\t\t<\/rs-layer><!--\n-->\t\t\t\t\t\t<\/rs-slide>\n\t\t\t\t\t\t<rs-slide style=\"position: absolute;\" data-key=\"rs-8\" data-title=\"Slide\" data-anim=\"ms:1000;r:0;\" data-in=\"o:0;\" data-out=\"a:false;\">\n\t\t\t\t\t\t\t<img src=\"\/\/lebanidze.com\/wp-content\/plugins\/revslider\/public\/assets\/assets\/dummy.png\" alt=\"Slide\" title=\"Home page\" class=\"rev-slidebg tp-rs-img rs-lazyload\" data-lazyload=\"\/\/lebanidze.com\/wp-content\/plugins\/revslider\/public\/assets\/assets\/transparent.png\" data-no-retina>\n<!--\n\t\t\t\t\t\t\t--><rs-layer\n\t\t\t\t\t\t\t\tid=\"slider-6-slide-8-layer-4\" \n\t\t\t\t\t\t\t\tclass=\"rs-layer-video intrinsic-ignore\"\n\t\t\t\t\t\t\t\tdata-type=\"video\"\n\t\t\t\t\t\t\t\tdata-rsp_ch=\"on\"\n\t\t\t\t\t\t\t\tdata-xy=\"x:r;xo:15px;y:c;\"\n\t\t\t\t\t\t\t\tdata-text=\"w:normal;\"\n\t\t\t\t\t\t\t\tdata-dim=\"w:569px;h:320px;\"\n\t\t\t\t\t\t\t\tdata-video=\"twa:false;ap:false;v:100;vd:100;l:false;ptimer:true;nse:f;\"\n\t\t\t\t\t\t\t\tdata-vimeoid=\"361487517\"\n\t\t\t\t\t\t\t\tdata-vatr=\"background=1&amp;title=0&amp;byline=0&amp;portrait=0&amp;api=1\"\n\t\t\t\t\t\t\t\tdata-frame_999=\"o:0;st:w;\"\n\t\t\t\t\t\t\t\tstyle=\"z-index:8;\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<\/rs-layer><!--\n\n\t\t\t\t\t\t\t--><rs-layer\n\t\t\t\t\t\t\t\tid=\"slider-6-slide-8-layer-5\" \n\t\t\t\t\t\t\t\tdata-type=\"text\"\n\t\t\t\t\t\t\t\tdata-color=\"#e5f9f9\"\n\t\t\t\t\t\t\t\tdata-rsp_ch=\"on\"\n\t\t\t\t\t\t\t\tdata-xy=\"x:55px;y:c;yo:3px;\"\n\t\t\t\t\t\t\t\tdata-text=\"w:normal;s:16;l:24;ls:2;a:right;\"\n\t\t\t\t\t\t\t\tdata-dim=\"w:390px;h:267px;minh:0px;\"\n\t\t\t\t\t\t\t\tdata-frame_999=\"o:0;st:w;\"\n\t\t\t\t\t\t\t\tstyle=\"z-index:9;font-family:'Open Sans';\"\n\t\t\t\t\t\t\t>Stealing the Sun \u2013 Rugby World Cup<br \/>\n<br \/>\nWe composed the music together with Zviad Mgebry for this short.<br \/>\n<br \/>\nAll audio is done by Postred.<br \/>\n \n\t\t\t\t\t\t\t<\/rs-layer><!--\n-->\t\t\t\t\t\t<\/rs-slide>\n\t\t\t\t\t\t<rs-slide style=\"position: absolute;\" data-key=\"rs-11\" data-title=\"Slide\" data-in=\"o:0;\" data-out=\"a:false;\">\n\t\t\t\t\t\t\t<img src=\"\/\/lebanidze.com\/wp-content\/plugins\/revslider\/public\/assets\/assets\/dummy.png\" alt=\"Slide\" title=\"Home page\" class=\"rev-slidebg tp-rs-img rs-lazyload\" data-lazyload=\"\/\/lebanidze.com\/wp-content\/plugins\/revslider\/public\/assets\/assets\/transparent.png\" data-no-retina>\n<!--\n\t\t\t\t\t\t\t--><rs-layer\n\t\t\t\t\t\t\t\tid=\"slider-6-slide-11-layer-2\" \n\t\t\t\t\t\t\t\tclass=\"rs-layer-video intrinsic-ignore\"\n\t\t\t\t\t\t\t\tdata-type=\"video\"\n\t\t\t\t\t\t\t\tdata-rsp_ch=\"on\"\n\t\t\t\t\t\t\t\tdata-xy=\"x:c;xo:197px;y:c;\"\n\t\t\t\t\t\t\t\tdata-text=\"w:normal;\"\n\t\t\t\t\t\t\t\tdata-dim=\"w:569px;h:320px;\"\n\t\t\t\t\t\t\t\tdata-video=\"twa:false;ap:false;v:100;vd:100;l:false;ptimer:true;nse:f;\"\n\t\t\t\t\t\t\t\tdata-vimeoid=\"935826596\"\n\t\t\t\t\t\t\t\tdata-vatr=\"background=1&amp;title=0&amp;byline=0&amp;portrait=0&amp;api=1\"\n\t\t\t\t\t\t\t\tdata-frame_999=\"o:0;st:w;\"\n\t\t\t\t\t\t\t\tstyle=\"z-index:8;\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<\/rs-layer><!--\n\n\t\t\t\t\t\t\t--><rs-layer\n\t\t\t\t\t\t\t\tid=\"slider-6-slide-11-layer-3\" \n\t\t\t\t\t\t\t\tdata-type=\"text\"\n\t\t\t\t\t\t\t\tdata-color=\"#e5f9f9\"\n\t\t\t\t\t\t\t\tdata-rsp_ch=\"on\"\n\t\t\t\t\t\t\t\tdata-xy=\"y:c;\"\n\t\t\t\t\t\t\t\tdata-text=\"w:normal;s:16;l:24;ls:2;a:right;\"\n\t\t\t\t\t\t\t\tdata-dim=\"w:409px;minh:0px;\"\n\t\t\t\t\t\t\t\tdata-frame_999=\"o:0;st:w;\"\n\t\t\t\t\t\t\t\tstyle=\"z-index:9;font-family:'Open Sans';\"\n\t\t\t\t\t\t\t>\"Project Echo\" was an immersive audiovisual experience by Beso Kacharava that premiered on UNESCO's World Book Capital 2021.<br \/>\n<br \/>\nI was the technical lead for the project where we developed a procedural audio-visual system. (Music by Zviad Mgebry) \n\t\t\t\t\t\t\t<\/rs-layer><!--\n-->\t\t\t\t\t\t<\/rs-slide>\n\t\t\t\t\t<\/rs-slides>\n\t\t\t\t<\/rs-module>\n\t\t\t\t<script>\n\t\t\t\t\tsetREVStartSize({c: 'rev_slider_6_1',rl:[1240,1024,778,480],el:[500],gw:[1100],gh:[500],type:'carousel',justify:'',layout:'fullwidth',mh:\"0\"});if (window.RS_MODULES!==undefined && window.RS_MODULES.modules!==undefined && window.RS_MODULES.modules[\"revslider61\"]!==undefined) {window.RS_MODULES.modules[\"revslider61\"].once = false;window.revapi6 = undefined;if (window.RS_MODULES.checkMinimal!==undefined) window.RS_MODULES.checkMinimal()}\n\t\t\t\t<\/script>\n\t\t\t<\/rs-module-wrap>\n\t\t\t<!-- END REVOLUTION SLIDER -->\n<\/div>\n\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-e886341 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"e886341\" data-element_type=\"section\">\n\t\t\t\t\t\t\t<div class=\"elementor-background-overlay\"><\/div>\n\t\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-ad3ea92\" data-id=\"ad3ea92\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-1230337 elementor-widget elementor-widget-spacer\" data-id=\"1230337\" data-element_type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<style>\/*! elementor - v3.6.6 - 08-06-2022 *\/\n.e-container.e-container--row .elementor-spacer-inner{width:var(--spacer-size)}.e-container.e-container--column .elementor-spacer-inner,.elementor-column .elementor-spacer-inner{height:var(--spacer-size)}<\/style>\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Welcome. I am Luka Lebanidze, an audio-visual artist &amp; a media entrepreneur. Currently, I lead the video game division at\u00a0POSTRED,\u00a0a creative audio services company. Contemplate the amber by rotating it. Through the rock&#8217;s rotation you may find the inscription. This rock does not have anything written on it&#8230; ABOUT I blend my lifelong passion for [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"open","template":"..\/public\/views\/revslider-page-template.php","meta":[],"_links":{"self":[{"href":"https:\/\/lebanidze.com\/index.php?rest_route=\/wp\/v2\/pages\/2"}],"collection":[{"href":"https:\/\/lebanidze.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/lebanidze.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/lebanidze.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/lebanidze.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2"}],"version-history":[{"count":1762,"href":"https:\/\/lebanidze.com\/index.php?rest_route=\/wp\/v2\/pages\/2\/revisions"}],"predecessor-version":[{"id":2492,"href":"https:\/\/lebanidze.com\/index.php?rest_route=\/wp\/v2\/pages\/2\/revisions\/2492"}],"wp:attachment":[{"href":"https:\/\/lebanidze.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}