Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Quantum Nexus™ 3D Energy Matrix</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/gsap@3.11.4/dist/gsap.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/postprocessing/EffectComposer.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/postprocessing/RenderPass.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/postprocessing/ShaderPass.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/postprocessing/UnrealBloomPass.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/shaders/CopyShader.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/shaders/LuminosityHighPassShader.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/shaders/HorizontalBlurShader.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/shaders/VerticalBlurShader.js"></script> | |
| <style> | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| overflow: hidden; | |
| font-family: 'Inter', sans-serif; | |
| } | |
| #three-container { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: -1; | |
| } | |
| #ui-container { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| pointer-events: none; | |
| } | |
| .ui-panel { | |
| pointer-events: all; | |
| background: rgba(15, 23, 42, 0.7); | |
| backdrop-filter: blur(16px); | |
| -webkit-backdrop-filter: blur(16px); | |
| border: 1px solid rgba(255, 255, 255, 0.08); | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); | |
| border-radius: 16px; | |
| transition: all 0.3s ease; | |
| } | |
| .ui-panel:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4); | |
| } | |
| .energy-cell { | |
| background: linear-gradient(145deg, rgba(30, 41, 59, 0.8), rgba(15, 23, 42, 0.9)); | |
| border-radius: 12px; | |
| transition: all 0.3s ease; | |
| } | |
| .energy-cell:hover { | |
| transform: translateY(-3px) scale(1.02); | |
| box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4); | |
| } | |
| .flow-line { | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .flow-line::after { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient(90deg, transparent, currentColor, transparent); | |
| background-size: 200% 100%; | |
| animation: energy-flow 2s linear infinite; | |
| opacity: 0.5; | |
| } | |
| @keyframes energy-flow { | |
| 0% { background-position: 0% 50%; } | |
| 100% { background-position: 100% 50%; } | |
| } | |
| .pulse-glow { | |
| animation: pulse-glow 2s ease-in-out infinite; | |
| } | |
| @keyframes pulse-glow { | |
| 0% { opacity: 0.7; box-shadow: 0 0 5px currentColor; } | |
| 50% { opacity: 1; box-shadow: 0 0 15px currentColor; } | |
| 100% { opacity: 0.7; box-shadow: 0 0 5px currentColor; } | |
| } | |
| .gradient-text { | |
| background-clip: text; | |
| -webkit-background-clip: text; | |
| color: transparent; | |
| } | |
| .glow-border { | |
| position: relative; | |
| } | |
| .glow-border::after { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| border-radius: inherit; | |
| padding: 1px; | |
| background: linear-gradient(135deg, rgba(59, 130, 246, 0.5), rgba(14, 165, 233, 0.5)); | |
| -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); | |
| -webkit-mask-composite: xor; | |
| mask-composite: exclude; | |
| pointer-events: none; | |
| } | |
| .energy-badge { | |
| transition: all 0.3s ease; | |
| } | |
| .energy-badge:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 0 15px currentColor; | |
| } | |
| .grid-node { | |
| position: absolute; | |
| width: 60px; | |
| height: 60px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 24px; | |
| color: white; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| z-index: 10; | |
| } | |
| .grid-node:hover { | |
| transform: scale(1.2); | |
| } | |
| /* New styles for advanced effects */ | |
| .particle-trail { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| pointer-events: none; | |
| } | |
| .energy-pulse { | |
| position: absolute; | |
| border-radius: 50%; | |
| background: rgba(59, 130, 246, 0.5); | |
| transform: scale(0); | |
| animation: energy-pulse 3s infinite; | |
| pointer-events: none; | |
| } | |
| @keyframes energy-pulse { | |
| 0% { transform: scale(0); opacity: 1; } | |
| 100% { transform: scale(3); opacity: 0; } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-slate-950 text-slate-100"> | |
| <!-- Three.js Container --> | |
| <div id="three-container"></div> | |
| <!-- Particle Trails --> | |
| <div id="particle-trail" class="particle-trail"></div> | |
| <!-- UI Container --> | |
| <div id="ui-container"> | |
| <!-- Header --> | |
| <header class="flex flex-col md:flex-row justify-between items-start md:items-center p-6"> | |
| <div class="mb-6 md:mb-0"> | |
| <div class="flex items-center"> | |
| <div class="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-500 to-cyan-400 flex items-center justify-center mr-4 shadow-lg"> | |
| <i class="fas fa-bolt text-white text-xl"></i> | |
| </div> | |
| <div> | |
| <h1 class="text-4xl font-bold bg-gradient-to-r from-blue-400 to-cyan-300 gradient-text"> | |
| Quantum Nexus™ | |
| </h1> | |
| <p class="text-slate-400 font-medium">3D Energy Matrix Intelligence</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex flex-col md:flex-row items-start md:items-center space-y-4 md:space-y-0 md:space-x-6 w-full md:w-auto"> | |
| <div class="flex items-center space-x-4"> | |
| <div class="ui-panel p-3 rounded-xl shadow-sm flex items-center"> | |
| <div class="w-3 h-3 rounded-full bg-green-500 mr-2 pulse-glow"></div> | |
| <div> | |
| <p class="text-xs text-slate-400">Matrix Status</p> | |
| <p class="font-medium">Live: <span id="update-time" class="text-slate-100">Just now</span></p> | |
| </div> | |
| </div> | |
| <button class="ui-panel bg-gradient-to-r from-blue-600 to-cyan-500 hover:from-blue-700 hover:to-cyan-600 text-white px-6 py-3 rounded-xl flex items-center shadow-lg transition-all duration-300 hover:shadow-xl"> | |
| <i class="fas fa-network-wired mr-2"></i> Matrix Control | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Dashboard --> | |
| <div class="absolute bottom-0 left-0 right-0 p-6"> | |
| <div class="ui-panel p-6 rounded-2xl max-w-4xl mx-auto"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-2xl font-semibold flex items-center"> | |
| <i class="fas fa-project-diagram text-blue-400 mr-3 pulse-glow"></i> | |
| Quantum Energy Matrix | |
| </h2> | |
| <div class="bg-slate-800/70 px-4 py-1 rounded-full text-sm border border-slate-700/50"> | |
| <span class="text-green-400 font-medium">Optimal</span> Performance | |
| </div> | |
| </div> | |
| <!-- Grid Layout --> | |
| <div class="grid grid-cols-3 gap-4"> | |
| <!-- Solar --> | |
| <div class="col-span-1 flex flex-col items-center"> | |
| <div class="energy-cell w-full h-32 bg-yellow-900/20 border border-yellow-500/30 flex flex-col items-center justify-center p-4 glow-border" id="solar-cell"> | |
| <div class="w-16 h-16 rounded-full bg-gradient-to-br from-yellow-400 to-yellow-600 flex items-center justify-center mb-2 shadow-lg"> | |
| <i class="fas fa-solar-panel text-white text-2xl"></i> | |
| </div> | |
| <h3 class="font-bold text-yellow-400 text-center">Solar Array</h3> | |
| <p class="text-sm text-yellow-300" id="solar-value">5.2 kW</p> | |
| </div> | |
| </div> | |
| <!-- Battery --> | |
| <div class="col-span-1 flex flex-col items-center"> | |
| <div class="energy-cell w-full h-32 bg-orange-900/20 border border-orange-500/30 flex flex-col items-center justify-center p-4 glow-border" id="battery-cell"> | |
| <div class="w-16 h-16 rounded-lg bg-gradient-to-br from-orange-400 to-orange-600 flex items-center justify-center mb-2 shadow-lg"> | |
| <i class="fas fa-battery-three-quarters text-white text-2xl"></i> | |
| </div> | |
| <h3 class="font-bold text-orange-400 text-center">Battery Bank</h3> | |
| <p class="text-sm text-orange-300" id="battery-value">78%</p> | |
| </div> | |
| </div> | |
| <!-- Grid --> | |
| <div class="col-span-1 flex flex-col items-center"> | |
| <div class="energy-cell w-full h-32 bg-slate-800/20 border border-slate-500/30 flex flex-col items-center justify-center p-4 glow-border" id="grid-cell"> | |
| <div class="w-16 h-16 rounded-lg bg-gradient-to-br from-slate-400 to-slate-600 flex items-center justify-center mb-2 shadow-lg"> | |
| <i class="fas fa-plug text-white text-2xl"></i> | |
| </div> | |
| <h3 class="font-bold text-slate-300 text-center">Grid</h3> | |
| <p class="text-sm text-slate-300" id="grid-value">1.9 kW</p> | |
| </div> | |
| </div> | |
| <!-- Status --> | |
| <div class="col-span-3 mt-4 text-center"> | |
| <p class="text-slate-400 text-sm">Matrix Status</p> | |
| <p class="text-xl font-bold text-green-400" id="flow-mode">Solar Exporting (1.9 kW to grid)</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Side Panels --> | |
| <div class="absolute top-24 right-6 w-80 space-y-6"> | |
| <!-- System Overview --> | |
| <div class="ui-panel p-6 rounded-2xl"> | |
| <h2 class="text-xl font-semibold mb-6 flex items-center"> | |
| <i class="fas fa-chart-network text-blue-400 mr-3"></i> | |
| System Overview | |
| </h2> | |
| <div class="space-y-4"> | |
| <div class="flex justify-between items-center"> | |
| <div class="flex items-center"> | |
| <div class="w-10 h-10 rounded-lg bg-blue-900/50 flex items-center justify-center mr-3"> | |
| <i class="fas fa-solar-panel text-yellow-400"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm text-slate-400">Solar Production</p> | |
| <p class="font-bold text-yellow-400">5.2 kW</p> | |
| </div> | |
| </div> | |
| <div class="text-right"> | |
| <p class="text-sm text-slate-400">Today</p> | |
| <p class="font-bold text-yellow-400">24.8 kWh</p> | |
| </div> | |
| </div> | |
| <div class="flex justify-between items-center"> | |
| <div class="flex items-center"> | |
| <div class="w-10 h-10 rounded-lg bg-orange-900/50 flex items-center justify-center mr-3"> | |
| <i class="fas fa-battery-three-quarters text-orange-400"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm text-slate-400">Battery Storage</p> | |
| <p class="font-bold text-orange-400">78%</p> | |
| </div> | |
| </div> | |
| <div class="text-right"> | |
| <p class="text-sm text-slate-400">Capacity</p> | |
| <p class="font-bold text-orange-400">14.2 kWh</p> | |
| </div> | |
| </div> | |
| <div class="flex justify-between items-center"> | |
| <div class="flex items-center"> | |
| <div class="w-10 h-10 rounded-lg bg-blue-900/50 flex items-center justify-center mr-3"> | |
| <i class="fas fa-home text-blue-400"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm text-slate-400">Home Usage</p> | |
| <p class="font-bold text-blue-400">3.3 kW</p> | |
| </div> | |
| </div> | |
| <div class="text-right"> | |
| <p class="text-sm text-slate-400">Today</p> | |
| <p class="font-bold text-blue-400">18.6 kWh</p> | |
| </div> | |
| </div> | |
| <div class="flex justify-between items-center"> | |
| <div class="flex items-center"> | |
| <div class="w-10 h-10 rounded-lg bg-slate-700/50 flex items-center justify-center mr-3"> | |
| <i class="fas fa-plug text-slate-300"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm text-slate-400">Grid Export</p> | |
| <p class="font-bold text-green-400">1.9 kW</p> | |
| </div> | |
| </div> | |
| <div class="text-right"> | |
| <p class="text-sm text-slate-400">Today</p> | |
| <p class="font-bold text-green-400">6.2 kWh</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Efficiency Metrics --> | |
| <div class="ui-panel p-6 rounded-2xl"> | |
| <h2 class="text-xl font-semibold mb-6 flex items-center"> | |
| <i class="fas fa-tachometer-alt text-cyan-400 mr-3"></i> | |
| Efficiency Metrics | |
| </h2> | |
| <div class="space-y-6"> | |
| <div> | |
| <div class="flex justify-between mb-2"> | |
| <span class="text-sm text-slate-400">Solar Utilization</span> | |
| <span class="text-sm font-medium text-yellow-400">92%</span> | |
| </div> | |
| <div class="w-full bg-slate-800/50 rounded-full h-2"> | |
| <div class="bg-gradient-to-r from-yellow-500 to-yellow-300 h-2 rounded-full" style="width: 92%"></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between mb-2"> | |
| <span class="text-sm text-slate-400">Battery Efficiency</span> | |
| <span class="text-sm font-medium text-orange-400">88%</span> | |
| </div> | |
| <div class="w-full bg-slate-800/50 rounded-full h-2"> | |
| <div class="bg-gradient-to-r from-orange-500 to-orange-300 h-2 rounded-full" style="width: 88%"></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between mb-2"> | |
| <span class="text-sm text-slate-400">Energy Independence</span> | |
| <span class="text-sm font-medium text-green-400">94%</span> | |
| </div> | |
| <div class="w-full bg-slate-800/50 rounded-full h-2"> | |
| <div class="bg-gradient-to-r from-green-500 to-green-300 h-2 rounded-full" style="width: 94%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 3D Grid Nodes --> | |
| <div class="grid-node" style="top: 30%; left: 20%; background: rgba(245, 158, 11, 0.2); border: 2px solid rgba(245, 158, 11, 0.5);" data-type="solar"> | |
| <i class="fas fa-solar-panel text-yellow-400"></i> | |
| </div> | |
| <div class="grid-node" style="top: 40%; left: 70%; background: rgba(59, 130, 246, 0.2); border: 2px solid rgba(59, 130, 246, 0.5);" data-type="home"> | |
| <i class="fas fa-home text-blue-400"></i> | |
| </div> | |
| <div class="grid-node" style="top: 60%; left: 30%; background: rgba(249, 115, 22, 0.2); border: 2px solid rgba(249, 115, 22, 0.5);" data-type="battery"> | |
| <i class="fas fa-battery-three-quarters text-orange-400"></i> | |
| </div> | |
| <div class="grid-node" style="top: 70%; left: 80%; background: rgba(156, 163, 175, 0.2); border: 2px solid rgba(156, 163, 175, 0.5);" data-type="grid"> | |
| <i class="fas fa-plug text-slate-300"></i> | |
| </div> | |
| </div> | |
| <script> | |
| // Three.js Scene Setup | |
| let scene, camera, renderer, controls, composer; | |
| let energyLines = []; | |
| let energyParticles = []; | |
| let bloomPass; | |
| let energyNodes = []; | |
| let floatingTexts = []; | |
| let floatingIcons = []; | |
| let particleSystems = []; | |
| let holographicGrid; | |
| let energyPulseEffects = []; | |
| let floatingEnergyOrbs = []; | |
| // Post-processing effects | |
| function initPostProcessing() { | |
| const renderScene = new THREE.RenderPass(scene, camera); | |
| bloomPass = new THREE.UnrealBloomPass( | |
| new THREE.Vector2(window.innerWidth, window.innerHeight), | |
| 1.5, // strength | |
| 0.4, // radius | |
| 0.85 // threshold | |
| ); | |
| composer = new THREE.EffectComposer(renderer); | |
| composer.addPass(renderScene); | |
| composer.addPass(bloomPass); | |
| } | |
| // Create a holographic grid floor | |
| function createHolographicGrid() { | |
| const size = 50; | |
| const divisions = 50; | |
| const gridHelper = new THREE.GridHelper(size, divisions, 0x3b82f6, 0x1e293b); | |
| gridHelper.position.y = 0.01; | |
| gridHelper.material.transparent = true; | |
| gridHelper.material.opacity = 0.3; | |
| scene.add(gridHelper); | |
| holographicGrid = gridHelper; | |
| // Add pulsing center point | |
| const centerGeometry = new THREE.CircleGeometry(1, 32); | |
| const centerMaterial = new THREE.MeshBasicMaterial({ | |
| color: 0x3b82f6, | |
| transparent: true, | |
| opacity: 0.5, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| const centerCircle = new THREE.Mesh(centerGeometry, centerMaterial); | |
| centerCircle.rotation.x = -Math.PI / 2; | |
| centerCircle.position.y = 0.02; | |
| scene.add(centerCircle); | |
| // Animation | |
| gsap.to(centerCircle.scale, { | |
| x: 1.5, | |
| y: 1.5, | |
| z: 1.5, | |
| duration: 3, | |
| repeat: -1, | |
| yoyo: true, | |
| ease: "sine.inOut" | |
| }); | |
| gsap.to(centerCircle.material, { | |
| opacity: 0.8, | |
| duration: 3, | |
| repeat: -1, | |
| yoyo: true, | |
| ease: "sine.inOut" | |
| }); | |
| } | |
| // Create floating energy orbs | |
| function createFloatingEnergyOrbs() { | |
| const orbCount = 15; | |
| const orbGeometry = new THREE.SphereGeometry(0.3, 32, 32); | |
| for (let i = 0; i < orbCount; i++) { | |
| const color = new THREE.Color( | |
| Math.random() * 0.5 + 0.5, | |
| Math.random() * 0.5 + 0.5, | |
| Math.random() * 0.5 + 0.5 | |
| ); | |
| const orbMaterial = new THREE.MeshStandardMaterial({ | |
| color: color, | |
| emissive: color, | |
| emissiveIntensity: 0.5, | |
| roughness: 0.2, | |
| metalness: 0.7, | |
| transparent: true, | |
| opacity: 0.8 | |
| }); | |
| const orb = new THREE.Mesh(orbGeometry, orbMaterial); | |
| // Random position in a sphere | |
| const radius = 15; | |
| const theta = Math.random() * Math.PI * 2; | |
| const phi = Math.random() * Math.PI; | |
| orb.position.x = radius * Math.sin(phi) * Math.cos(theta); | |
| orb.position.y = radius * Math.cos(phi) + 5; | |
| orb.position.z = radius * Math.sin(phi) * Math.sin(theta); | |
| // Random velocity | |
| orb.userData = { | |
| velocity: new THREE.Vector3( | |
| (Math.random() - 0.5) * 0.02, | |
| (Math.random() - 0.5) * 0.02, | |
| (Math.random() - 0.5) * 0.02 | |
| ), | |
| origin: orb.position.clone() | |
| }; | |
| scene.add(orb); | |
| floatingEnergyOrbs.push(orb); | |
| // Add glow | |
| const glowGeometry = new THREE.SphereGeometry(0.5, 32, 32); | |
| const glowMaterial = new THREE.MeshBasicMaterial({ | |
| color: color, | |
| transparent: true, | |
| opacity: 0.3, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| const glow = new THREE.Mesh(glowGeometry, glowMaterial); | |
| glow.position.copy(orb.position); | |
| scene.add(glow); | |
| // Animation | |
| gsap.to(orb.scale, { | |
| x: 1.5, | |
| y: 1.5, | |
| z: 1.5, | |
| duration: 2 + Math.random() * 3, | |
| repeat: -1, | |
| yoyo: true, | |
| ease: "sine.inOut" | |
| }); | |
| } | |
| } | |
| // Create particle system | |
| function createParticleSystem() { | |
| const particleCount = 2000; | |
| const particles = new THREE.BufferGeometry(); | |
| const positions = new Float32Array(particleCount * 3); | |
| const colors = new Float32Array(particleCount * 3); | |
| const sizes = new Float32Array(particleCount); | |
| const radius = 30; | |
| for (let i = 0; i < particleCount; i++) { | |
| // Position particles in a sphere | |
| const theta = Math.random() * Math.PI * 2; | |
| const phi = Math.random() * Math.PI; | |
| positions[i * 3] = radius * Math.sin(phi) * Math.cos(theta); | |
| positions[i * 3 + 1] = radius * Math.cos(phi); | |
| positions[i * 3 + 2] = radius * Math.sin(phi) * Math.sin(theta); | |
| // Random colors | |
| colors[i * 3] = 0.5 + Math.random() * 0.5; // R | |
| colors[i * 3 + 1] = 0.5 + Math.random() * 0.5; // G | |
| colors[i * 3 + 2] = 0.5 + Math.random() * 0.5; // B | |
| // Random sizes | |
| sizes[i] = 0.1 + Math.random() * 0.5; | |
| } | |
| particles.setAttribute('position', new THREE.BufferAttribute(positions, 3)); | |
| particles.setAttribute('color', new THREE.BufferAttribute(colors, 3)); | |
| particles.setAttribute('size', new THREE.BufferAttribute(sizes, 1)); | |
| const particleMaterial = new THREE.PointsMaterial({ | |
| size: 0.2, | |
| vertexColors: true, | |
| transparent: true, | |
| opacity: 0.8, | |
| blending: THREE.AdditiveBlending, | |
| sizeAttenuation: true | |
| }); | |
| const particleSystem = new THREE.Points(particles, particleMaterial); | |
| scene.add(particleSystem); | |
| particleSystems.push(particleSystem); | |
| } | |
| // Create energy pulse effects | |
| function createEnergyPulseEffects() { | |
| const pulseCount = 5; | |
| for (let i = 0; i < pulseCount; i++) { | |
| const geometry = new THREE.SphereGeometry(1, 32, 32); | |
| const material = new THREE.MeshBasicMaterial({ | |
| color: 0x3b82f6, | |
| transparent: true, | |
| opacity: 0, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| const pulse = new THREE.Mesh(geometry, material); | |
| pulse.position.set( | |
| (Math.random() - 0.5) * 20, | |
| Math.random() * 5, | |
| (Math.random() - 0.5) * 20 | |
| ); | |
| scene.add(pulse); | |
| energyPulseEffects.push({ | |
| mesh: pulse, | |
| speed: 0.5 + Math.random(), | |
| maxSize: 5 + Math.random() * 10, | |
| delay: Math.random() * 5 | |
| }); | |
| } | |
| } | |
| // Create floating text | |
| function createFloatingText(text, position, color) { | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = 512; | |
| canvas.height = 256; | |
| const context = canvas.getContext('2d'); | |
| context.font = "Bold 48px Arial"; | |
| context.fillStyle = `rgba(${color.r * 255}, ${color.g * 255}, ${color.b * 255}, 0.8)`; | |
| context.textAlign = 'center'; | |
| context.textBaseline = 'middle'; | |
| context.fillText(text, canvas.width / 2, canvas.height / 2); | |
| const texture = new THREE.CanvasTexture(canvas); | |
| const material = new THREE.SpriteMaterial({ | |
| map: texture, | |
| transparent: true, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| const sprite = new THREE.Sprite(material); | |
| sprite.scale.set(5, 2.5, 1); | |
| sprite.position.copy(position); | |
| scene.add(sprite); | |
| floatingTexts.push({ | |
| sprite: sprite, | |
| originalPosition: position.clone(), | |
| offset: Math.random() * Math.PI * 2 | |
| }); | |
| } | |
| // Create floating icons | |
| function createFloatingIcons() { | |
| const iconTypes = [ | |
| { icon: 'fa-bolt', color: 0xf59e0b }, | |
| { icon: 'fa-charging-station', color: 0x3b82f6 }, | |
| { icon: 'fa-leaf', color: 0x10b981 }, | |
| { icon: 'fa-fire', color: 0xf97316 }, | |
| { icon: 'fa-water', color: 0x0ea5e9 } | |
| ]; | |
| const iconCount = 10; | |
| for (let i = 0; i < iconCount; i++) { | |
| const type = iconTypes[Math.floor(Math.random() * iconTypes.length)]; | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = 128; | |
| canvas.height = 128; | |
| const context = canvas.getContext('2d'); | |
| // Draw icon | |
| context.font = "Normal 72px FontAwesome"; | |
| context.fillStyle = `rgba(${(type.color >> 16) & 0xff}, ${(type.color >> 8) & 0xff}, ${type.color & 0xff}, 0.8)`; | |
| context.textAlign = 'center'; | |
| context.textBaseline = 'middle'; | |
| // Get icon character from FontAwesome | |
| const iconChar = String.fromCharCode(parseInt(getIconUnicode(type.icon), 16)); | |
| context.fillText(iconChar, canvas.width / 2, canvas.height / 2); | |
| const texture = new THREE.CanvasTexture(canvas); | |
| const material = new THREE.SpriteMaterial({ | |
| map: texture, | |
| transparent: true, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| const sprite = new THREE.Sprite(material); | |
| sprite.scale.set(2, 2, 1); | |
| // Position in a sphere | |
| const radius = 10 + Math.random() * 10; | |
| const theta = Math.random() * Math.PI * 2; | |
| const phi = Math.random() * Math.PI; | |
| sprite.position.set( | |
| radius * Math.sin(phi) * Math.cos(theta), | |
| radius * Math.cos(phi) + 5, | |
| radius * Math.sin(phi) * Math.sin(theta) | |
| ); | |
| scene.add(sprite); | |
| floatingIcons.push({ | |
| sprite: sprite, | |
| originalPosition: sprite.position.clone(), | |
| speed: 0.01 + Math.random() * 0.02, | |
| offset: Math.random() * Math.PI * 2 | |
| }); | |
| } | |
| } | |
| // Helper function to get FontAwesome unicode | |
| function getIconUnicode(iconName) { | |
| const icons = { | |
| 'fa-bolt': 'f0e7', | |
| 'fa-charging-station': 'f5e7', | |
| 'fa-leaf': 'f06c', | |
| 'fa-fire': 'f06d', | |
| 'fa-water': 'f773' | |
| }; | |
| return icons[iconName] || 'f111'; // default circle | |
| } | |
| function initThreeJS() { | |
| // Create scene | |
| scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x0f172a); | |
| scene.fog = new THREE.FogExp2(0x0f172a, 0.002); | |
| // Create camera | |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| camera.position.set(0, 10, 20); | |
| // Create renderer | |
| renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.shadowMap.enabled = true; | |
| renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
| renderer.outputEncoding = THREE.sRGBEncoding; | |
| document.getElementById('three-container').appendChild(renderer.domElement); | |
| // Add controls | |
| controls = new THREE.OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; | |
| controls.dampingFactor = 0.05; | |
| controls.minDistance = 5; | |
| controls.maxDistance = 50; | |
| controls.maxPolarAngle = Math.PI * 0.9; | |
| controls.minPolarAngle = Math.PI * 0.2; | |
| // Add lights | |
| const ambientLight = new THREE.AmbientLight(0x404040, 2); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 1); | |
| directionalLight.position.set(5, 10, 7); | |
| directionalLight.castShadow = true; | |
| directionalLight.shadow.mapSize.width = 2048; | |
| directionalLight.shadow.mapSize.height = 2048; | |
| scene.add(directionalLight); | |
| const pointLight = new THREE.PointLight(0x4fd1ff, 3, 20); | |
| pointLight.position.set(0, 5, 0); | |
| pointLight.castShadow = true; | |
| scene.add(pointLight); | |
| // Add hemisphere light for more natural lighting | |
| const hemisphereLight = new THREE.HemisphereLight(0x3b82f6, 0xf59e0b, 0.5); | |
| scene.add(hemisphereLight); | |
| // Initialize post-processing | |
| initPostProcessing(); | |
| // Create complex environment | |
| createHolographicGrid(); | |
| createParticleSystem(); | |
| createFloatingEnergyOrbs(); | |
| createEnergyPulseEffects(); | |
| createFloatingIcons(); | |
| // Create floating text | |
| createFloatingText("QUANTUM NEXUS", new THREE.Vector3(0, 15, 0), new THREE.Color(0x3b82f6)); | |
| createFloatingText("ENERGY MATRIX", new THREE.Vector3(0, 12, 0), new THREE.Color(0xf59e0b)); | |
| // Create energy nodes | |
| createEnergyNode('solar', new THREE.Vector3(-10, 2, -8), 0xf59e0b); | |
| createEnergyNode('battery', new THREE.Vector3(0, 2, 8), 0xf97316); | |
| createEnergyNode('home', new THREE.Vector3(10, 2, -8), 0x3b82f6); | |
| createEnergyNode('grid', new THREE.Vector3(0, 2, -15), 0x9ca3af); | |
| // Create energy connections | |
| createEnergyConnection('solar', 'home', 0xf59e0b); | |
| createEnergyConnection('solar', 'battery', 0xf59e0b); | |
| createEnergyConnection('battery', 'home', 0xf97316); | |
| createEnergyConnection('home', 'grid', 0x3b82f6); | |
| // Create secondary connections | |
| createEnergyConnection('solar', 'grid', 0xf59e0b); | |
| createEnergyConnection('battery', 'grid', 0xf97316); | |
| // Create decorative connections | |
| createDecorativeConnections(); | |
| // Handle window resize | |
| window.addEventListener('resize', onWindowResize); | |
| // Start animation loop | |
| animate(); | |
| } | |
| // Create decorative connections between floating orbs | |
| function createDecorativeConnections() { | |
| for (let i = 0; i < floatingEnergyOrbs.length; i++) { | |
| for (let j = i + 1; j < Math.min(i + 3, floatingEnergyOrbs.length); j++) { | |
| const fromOrb = floatingEnergyOrbs[i]; | |
| const toOrb = floatingEnergyOrbs[j]; | |
| // Only connect if within certain distance | |
| if (fromOrb.position.distanceTo(toOrb.position) < 15) { | |
| const lineGeometry = new THREE.BufferGeometry().setFromPoints([ | |
| fromOrb.position, | |
| toOrb.position | |
| ]); | |
| const lineMaterial = new THREE.LineBasicMaterial({ | |
| color: 0x3b82f6, | |
| transparent: true, | |
| opacity: 0.1, | |
| linewidth: 1 | |
| }); | |
| const line = new THREE.Line(lineGeometry, lineMaterial); | |
| scene.add(line); | |
| // Store reference to update positions | |
| energyLines.push({ | |
| line: line, | |
| from: fromOrb, | |
| to: toOrb | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| function createEnergyNode(type, position, color) { | |
| // Main sphere | |
| const geometry = new THREE.SphereGeometry(1.5, 32, 32); | |
| const material = new THREE.MeshStandardMaterial({ | |
| color: color, | |
| emissive: color, | |
| emissiveIntensity: 0.5, | |
| roughness: 0.2, | |
| metalness: 0.8 | |
| }); | |
| const sphere = new THREE.Mesh(geometry, material); | |
| sphere.position.copy(position); | |
| sphere.castShadow = true; | |
| sphere.receiveShadow = true; | |
| sphere.userData = { type: type }; | |
| scene.add(sphere); | |
| energyNodes.push(sphere); | |
| // Pulsing animation | |
| gsap.to(sphere.scale, { | |
| x: 1.3, | |
| y: 1.3, | |
| z: 1.3, | |
| duration: 2, | |
| repeat: -1, | |
| yoyo: true, | |
| ease: "sine.inOut" | |
| }); | |
| // Glow effect | |
| const glowGeometry = new THREE.SphereGeometry(2, 32, 32); | |
| const glowMaterial = new THREE.MeshBasicMaterial({ | |
| color: color, | |
| transparent: true, | |
| opacity: 0.3, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| const glow = new THREE.Mesh(glowGeometry, glowMaterial); | |
| glow.position.copy(position); | |
| scene.add(glow); | |
| // Ring around the sphere | |
| const ringGeometry = new THREE.TorusGeometry(2.2, 0.1, 16, 100); | |
| const ringMaterial = new THREE.MeshBasicMaterial({ | |
| color: color, | |
| transparent: true, | |
| opacity: 0.5, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| const ring = new THREE.Mesh(ringGeometry, ringMaterial); | |
| ring.position.copy(position); | |
| ring.rotation.x = Math.PI / 2; | |
| scene.add(ring); | |
| // Rotate ring | |
| gsap.to(ring.rotation, { | |
| y: Math.PI * 2, | |
| duration: 20, | |
| repeat: -1, | |
| ease: "none" | |
| }); | |
| // Floating particles around node | |
| createNodeParticles(position, color); | |
| return sphere; | |
| } | |
| // Create particles swirling around energy nodes | |
| function createNodeParticles(position, color) { | |
| const particleCount = 50; | |
| const particlesGeometry = new THREE.BufferGeometry(); | |
| const positions = new Float32Array(particleCount * 3); | |
| const colors = new Float32Array(particleCount * 3); | |
| for (let i = 0; i < particleCount; i++) { | |
| // Position particles in a sphere around the node | |
| const radius = 3; | |
| const theta = Math.random() * Math.PI * 2; | |
| const phi = Math.random() * Math.PI; | |
| positions[i * 3] = position.x + radius * Math.sin(phi) * Math.cos(theta); | |
| positions[i * 3 + 1] = position.y + radius * Math.cos(phi); | |
| positions[i * 3 + 2] = position.z + radius * Math.sin(phi) * Math.sin(theta); | |
| // Color particles | |
| colors[i * 3] = (color >> 16 & 255) / 255; | |
| colors[i * 3 + 1] = (color >> 8 & 255) / 255; | |
| colors[i * 3 + 2] = (color & 255) / 255; | |
| } | |
| particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); | |
| particlesGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); | |
| const particleMaterial = new THREE.PointsMaterial({ | |
| size: 0.2, | |
| vertexColors: true, | |
| transparent: true, | |
| opacity: 0.8, | |
| blending: THREE.AdditiveBlending, | |
| sizeAttenuation: true | |
| }); | |
| const particleSystem = new THREE.Points(particlesGeometry, particleMaterial); | |
| particleSystem.userData = { | |
| center: position.clone(), | |
| time: Math.random() * 100, | |
| speed: 0.01 + Math.random() * 0.02 | |
| }; | |
| scene.add(particleSystem); | |
| particleSystems.push(particleSystem); | |
| } | |
| function createEnergyConnection(fromType, toType, color) { | |
| const fromNode = scene.children.find(obj => obj.userData && obj.userData.type === fromType); | |
| const toNode = scene.children.find(obj => obj.userData && obj.userData.type === toType); | |
| if (!fromNode || !toNode) return; | |
| // Create curved line | |
| const curve = new THREE.QuadraticBezierCurve3( | |
| fromNode.position, | |
| new THREE.Vector3( | |
| (fromNode.position.x + toNode.position.x) / 2 + (Math.random() - 0.5) * 3, | |
| (fromNode.position.y + toNode.position.y) / 2 + 3 + Math.random() * 3, | |
| (fromNode.position.z + toNode.position.z) / 2 + (Math.random() - 0.5) * 3 | |
| ), | |
| toNode.position | |
| ); | |
| const points = curve.getPoints(50); | |
| const lineGeometry = new THREE.BufferGeometry().setFromPoints(points); | |
| // Create line material with gradient | |
| const lineMaterial = new THREE.LineBasicMaterial({ | |
| color: color, | |
| transparent: true, | |
| opacity: 0.5, | |
| linewidth: 3 | |
| }); | |
| const line = new THREE.Line(lineGeometry, lineMaterial); | |
| scene.add(line); | |
| // Create particles along the line | |
| const particleCount = 30; | |
| const particlesGeometry = new THREE.BufferGeometry(); | |
| const particlesMaterial = new THREE.PointsMaterial({ | |
| color: color, | |
| size: 0.3, | |
| transparent: true, | |
| opacity: 0.8, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| const particles = new Float32Array(particleCount * 3); | |
| for (let i = 0; i < particleCount; i++) { | |
| const t = i / (particleCount - 1); | |
| const point = curve.getPoint(t); | |
| particles[i * 3] = point.x; | |
| particles[i * 3 + 1] = point.y; | |
| particles[i * 3 + 2] = point.z; | |
| } | |
| particlesGeometry.setAttribute('position', new THREE.BufferAttribute(particles, 3)); | |
| const particleSystem = new THREE.Points(particlesGeometry, particlesMaterial); | |
| scene.add(particleSystem); | |
| // Store for animation | |
| energyLines.push({ | |
| line: line, | |
| curve: curve, | |
| from: fromNode.position, | |
| to: toNode.position | |
| }); | |
| energyParticles.push({ | |
| system: particleSystem, | |
| curve: curve, | |
| speed: 0.01 + Math.random() * 0.02, | |
| offset: Math.random() * 100 | |
| }); | |
| // Create energy pulses along the connection | |
| createConnectionPulses(curve, color); | |
| } | |
| // Create pulsing energy spheres along connections | |
| function createConnectionPulses(curve, color) { | |
| const pulseCount = 5; | |
| for (let i = 0; i < pulseCount; i++) { | |
| const geometry = new THREE.SphereGeometry(0.5, 16, 16); | |
| const material = new THREE.MeshBasicMaterial({ | |
| color: color, | |
| transparent: true, | |
| opacity: 0.8, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| const pulse = new THREE.Mesh(geometry, material); | |
| // Position along curve | |
| const t = i / pulseCount; | |
| const point = curve.getPoint(t); | |
| pulse.position.copy(point); | |
| scene.add(pulse); | |
| // Animation | |
| gsap.to(pulse.scale, { | |
| x: 1.5, | |
| y: 1.5, | |
| z: 1.5, | |
| duration: 2 + Math.random(), | |
| repeat: -1, | |
| yoyo: true, | |
| ease: "sine.inOut", | |
| delay: i * 0.3 | |
| }); | |
| gsap.to(pulse.position, { | |
| x: curve.getPoint((t + 0.1) % 1).x, | |
| y: curve.getPoint((t + 0.1) % 1).y, | |
| z: curve.getPoint((t + 0.1) % 1).z, | |
| duration: 5, | |
| repeat: -1, | |
| ease: "none", | |
| delay: i * 0.3 | |
| }); | |
| } | |
| } | |
| function onWindowResize() { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| bloomPass.setSize(window.innerWidth, window.innerHeight); | |
| composer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| // Update controls | |
| controls.update(); | |
| // Animate energy particles along connections | |
| energyParticles.forEach((particle) => { | |
| const positions = particle.system.geometry.attributes.position.array; | |
| const time = Date.now() * particle.speed + particle.offset; | |
| for (let i = 0; i < positions.length / 3; i++) { | |
| const t = (i / (positions.length / 3 - 1) + time * 0.001) % 1; | |
| const point = particle.curve.getPoint(t); | |
| positions[i * 3] = point.x; | |
| positions[i * 3 + 1] = point.y; | |
| positions[i * 3 + 2] = point.z; | |
| } | |
| particle.system.geometry.attributes.position.needsUpdate = true; | |
| }); | |
| // Animate decorative connections | |
| energyLines.forEach((connection) => { | |
| if (connection.curve && connection.line) { | |
| const points = connection.curve.getPoints(50); | |
| connection.line.geometry.setFromPoints(points); | |
| connection.line.geometry.attributes.position.needsUpdate = true; | |
| } else if (connection.from && connection.to && connection.line) { | |
| // Update line between moving objects | |
| connection.line.geometry.setFromPoints([connection.from, connection.to]); | |
| connection.line.geometry.attributes.position.needsUpdate = true; | |
| } | |
| }); | |
| // Animate floating energy orbs | |
| floatingEnergyOrbs.forEach((orb) => { | |
| // Move orb with slight randomness | |
| orb.position.x += orb.userData.velocity.x; | |
| orb.position.y += orb.userData.velocity.y; | |
| orb.position.z += orb.userData.velocity.z; | |
| // Add some randomness to movement | |
| orb.userData.velocity.x += (Math.random() - 0.5) * 0.001; | |
| orb.userData.velocity.y += (Math.random() - 0.5) * 0.001; | |
| orb.userData.velocity.z += (Math.random() - 0.5) * 0.001; | |
| // Keep orbs within bounds | |
| const distance = orb.position.distanceTo(orb.userData.origin); | |
| if (distance > 20) { | |
| orb.userData.velocity.x = -orb.userData.velocity.x * 0.5; | |
| orb.userData.velocity.y = -orb.userData.velocity.y * 0.5; | |
| orb.userData.velocity.z = -orb.userData.velocity.z * 0.5; | |
| } | |
| }); | |
| // Animate node particles | |
| particleSystems.forEach((system) => { | |
| if (system.userData && system.userData.center) { | |
| const positions = system.geometry.attributes.position.array; | |
| const center = system.userData.center; | |
| const time = system.userData.time += system.userData.speed; | |
| for (let i = 0; i < positions.length / 3; i++) { | |
| const radius = 2 + Math.sin(time + i * 0.1) * 0.5; | |
| const angle = time * 0.1 + i * 0.1; | |
| positions[i * 3] = center.x + radius * Math.cos(angle); | |
| positions[i * 3 + 1] = center.y + radius * Math.sin(angle * 0.7); | |
| positions[i * 3 + 2] = center.z + radius * Math.sin(angle); | |
| } | |
| system.geometry.attributes.position.needsUpdate = true; | |
| } | |
| }); | |
| // Animate energy pulse effects | |
| const now = Date.now(); | |
| energyPulseEffects.forEach((effect) => { | |
| const elapsed = (now * 0.001 - effect.delay) % 5; | |
| if (elapsed >= 0) { | |
| const progress = elapsed / 5; | |
| effect.mesh.scale.setScalar(progress * effect.maxSize); | |
| effect.mesh.material.opacity = 1 - progress; | |
| } | |
| }); | |
| // Animate floating text | |
| floatingTexts.forEach((text) => { | |
| text.sprite.position.y = text.originalPosition.y + Math.sin(now * 0.001 + text.offset) * 0.5; | |
| }); | |
| // Animate floating icons | |
| floatingIcons.forEach((icon) => { | |
| const time = now * 0.001 + icon.offset; | |
| icon.sprite.position.x = icon.originalPosition.x + Math.sin(time * icon.speed) * 2; | |
| icon.sprite.position.z = icon.originalPosition.z + Math.cos(time * icon.speed) * 2; | |
| icon.sprite.position.y = icon.originalPosition.y + Math.sin(time * icon.speed * 0.7) * 1; | |
| // Make icons face camera | |
| icon.sprite.lookAt(camera.position); | |
| }); | |
| // Rotate energy nodes | |
| energyNodes.forEach((node) => { | |
| node.rotation.y += 0.005; | |
| }); | |
| // Render with post-processing | |
| composer.render(); | |
| } | |
| // Update timestamp | |
| function updateTimestamp() { | |
| const now = new Date(); | |
| const timeString = now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); | |
| const dateString = now.toLocaleDateString([], {month: 'short', day: 'numeric'}); | |
| document.getElementById('update-time').textContent = `${timeString}`; | |
| } | |
| // Update energy grid visualization | |
| function updateEnergyGrid() { | |
| // Get current flow values (simulated) | |
| const solarProduction = 5.2; | |
| const homeConsumption = 3.3; | |
| const batteryLevel = 78; | |
| const gridExport = 1.9; | |
| const gridImport = 0; | |
| // Update numeric values with slight variations | |
| const solarVariation = (Math.random() * 0.5 - 0.25); | |
| const newSolarValue = Math.max(0, solarProduction + solarVariation).toFixed(1); | |
| document.getElementById('solar-value').textContent = `${newSolarValue} kW`; | |
| const homeVariation = (Math.random() * 0.3 - 0.15); | |
| const newHomeValue = Math.max(0, homeConsumption + homeVariation).toFixed(1); | |
| // Update battery percentage with small random change | |
| let batteryChange = (Math.random() * 3 - 1.5); | |
| if (solarProduction > homeConsumption && batteryLevel < 95) { | |
| batteryChange = Math.abs(batteryChange); // More likely to charge | |
| } else if (solarProduction < homeConsumption && batteryLevel > 5) { | |
| batteryChange = -Math.abs(batteryChange); // More likely to discharge | |
| } | |
| const newBatteryLevel = Math.max(0, Math.min(100, batteryLevel + batteryChange)); | |
| document.getElementById('battery-value').textContent = `${Math.round(newBatteryLevel)}%`; | |
| // Update grid value based on other changes | |
| const newGridValue = (newSolarValue - newHomeValue).toFixed(1); | |
| if (newGridValue > 0) { | |
| document.getElementById('grid-value').textContent = `${Math.abs(newGridValue)} kW`; | |
| } else { | |
| document.getElementById('grid-value').textContent = `${Math.abs(newGridValue)} kW`; | |
| } | |
| // Update flow mode text | |
| let flowMode = ''; | |
| if (newGridValue > 0) { | |
| flowMode = `Solar Exporting (${Math.abs(newGridValue)} kW to grid)`; | |
| document.getElementById('flow-mode').className = 'text-xl font-bold text-green-400'; | |
| } else if (newGridValue < 0) { | |
| flowMode = `Grid Importing (${Math.abs(newGridValue)} kW from grid)`; | |
| document.getElementById('flow-mode').className = 'text-xl font-bold text-yellow-400'; | |
| } else if (newSolarValue >= newHomeValue) { | |
| flowMode = 'Solar Powering Home'; | |
| document.getElementById('flow-mode').className = 'text-xl font-bold text-blue-400'; | |
| } else { | |
| flowMode = 'Mixed Power Sources'; | |
| document.getElementById('flow-mode').className = 'text-xl font-bold text-purple-400'; | |
| } | |
| document.getElementById('flow-mode').textContent = flowMode; | |
| // Visual feedback for UI elements | |
| if (newGridValue > 0) { | |
| gsap.to(document.getElementById('grid-cell'), { | |
| boxShadow: '0 0 20px rgba(16, 185, 129, 0.7)', | |
| duration: 0.5, | |
| yoyo: true, | |
| repeat: 1 | |
| }); | |
| } else { | |
| gsap.to(document.getElementById('grid-cell'), { | |
| boxShadow: '0 0 20px rgba(245, 158, 11, 0.7)', | |
| duration: 0.5, | |
| yoyo: true, | |
| repeat: 1 | |
| }); | |
| } | |
| } | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', () => { | |
| initThreeJS(); | |
| updateTimestamp(); | |
| // Initial energy grid setup | |
| updateEnergyGrid(); | |
| // Update data every 2 seconds | |
| setInterval(() => { | |
| updateTimestamp(); | |
| updateEnergyGrid(); | |
| }, 2000); | |
| // Add click handlers for grid nodes | |
| document.querySelectorAll('.grid-node').forEach(node => { | |
| node.addEventListener('click', () => { | |
| const type = node.dataset.type; | |
| // Focus camera on the corresponding 3D node | |
| const targetNode = scene.children.find(obj => obj.userData && obj.userData.type === type); | |
| if (targetNode) { | |
| gsap.to(camera.position, { | |
| x: targetNode.position.x * 1.5, | |
| y: targetNode.position.y + 5, | |
| z: targetNode.position.z * 1.5, | |
| duration: 1, | |
| ease: "power2.inOut" | |
| }); | |
| // Create pulse effect in the UI | |
| const pulse = document.createElement('div'); | |
| pulse.className = 'energy-pulse'; | |
| pulse.style.left = `${node.offsetLeft + node.offsetWidth / 2}px`; | |
| pulse.style.top = `${node.offsetTop + node.offsetHeight / 2}px`; | |
| document.getElementById('particle-trail').appendChild(pulse); | |
| // Remove after animation | |
| setTimeout(() => { | |
| pulse.remove(); | |
| }, 3000); | |
| } | |
| }); | |
| }); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=LukasBe/3d-effects" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |