| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| (function () {
|
| "use strict";
|
|
|
|
|
|
|
|
|
|
|
| const CONFIG = {
|
|
|
| lightning: {
|
| enabled: true,
|
| minInterval: 4000,
|
| maxInterval: 12000,
|
| duration: 200,
|
| branches: 3,
|
| color: 0x3b82f6,
|
| glowColor: 0x60a5fa,
|
| intensity: 2.5
|
| },
|
|
|
| particles: {
|
| enabled: true,
|
| count: 120,
|
| size: 2.5,
|
| baseSpeed: 0.15,
|
| color: 0x3b82f6,
|
| opacity: 0.5
|
| },
|
|
|
| palettes: {
|
| focus: {
|
| primary: 0x3b82f6,
|
| secondary: 0x8b5cf6,
|
| accent: 0x06b6d4,
|
| glow: 0x60a5fa
|
| },
|
| short: {
|
| primary: 0x10b981,
|
| secondary: 0x34d399,
|
| accent: 0x6ee7b7,
|
| glow: 0x34d399
|
| },
|
| long: {
|
| primary: 0x8b5cf6,
|
| secondary: 0xa855f7,
|
| accent: 0xc084fc,
|
| glow: 0xa855f7
|
| }
|
| },
|
|
|
| audio: {
|
| enabled: true,
|
| beatThreshold: 0.5,
|
| particleReactivity: 2.0,
|
| colorShift: true
|
| },
|
|
|
| effects: {
|
| floatingOrbs: true,
|
| trailParticles: true,
|
| pulsingGlow: true,
|
| rainbowMode: false
|
| },
|
|
|
| pixelRatio: Math.min(window.devicePixelRatio, 2)
|
| };
|
|
|
|
|
| let currentPalette = CONFIG.palettes.focus;
|
| let currentMode = "focus";
|
|
|
|
|
| window.setVisualizerMode = function (mode) {
|
| currentMode = mode;
|
| currentPalette = CONFIG.palettes[mode] || CONFIG.palettes.focus;
|
| updateSceneColors();
|
| console.log(`✨ Visualizer mode: ${mode}`);
|
| };
|
|
|
| function updateSceneColors() {
|
|
|
| if (particles && particles.material) {
|
| particles.material.color.setHex(currentPalette.primary);
|
| }
|
|
|
| if (orbs) {
|
| orbs.forEach((orb, i) => {
|
| const colors = [currentPalette.primary, currentPalette.secondary, currentPalette.accent];
|
| orb.material.color.setHex(colors[i % 3]);
|
| });
|
| }
|
|
|
| if (stars) {
|
| stars.forEach(star => {
|
| star.material.color.setHex(currentPalette.accent);
|
| });
|
| }
|
|
|
| if (halos) {
|
| halos.forEach(halo => {
|
| halo.material.color.setHex(currentPalette.glow);
|
| });
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
| let audioAnalyzer = {
|
| context: null,
|
| analyser: null,
|
| source: null,
|
| frequencyData: null,
|
| timeDomainData: null,
|
| isConnected: false,
|
| lastBeatTime: 0,
|
| bassLevel: 0,
|
| midLevel: 0,
|
| highLevel: 0,
|
| averageLevel: 0
|
| };
|
|
|
|
|
| const mediaSourceCache = new WeakMap();
|
|
|
|
|
| window.connectAudioVisualizer = function (audioElement) {
|
| if (!audioElement) return;
|
|
|
| try {
|
|
|
| if (audioAnalyzer.source && audioAnalyzer.analyser) {
|
| try {
|
| audioAnalyzer.source.disconnect(audioAnalyzer.analyser);
|
| } catch (e) {}
|
| }
|
|
|
|
|
| if (!audioAnalyzer.context) {
|
| audioAnalyzer.context = new (window.AudioContext || window.webkitAudioContext)();
|
| }
|
|
|
|
|
| if (audioAnalyzer.context.state === "suspended") {
|
| audioAnalyzer.context.resume();
|
| }
|
|
|
|
|
| if (!audioAnalyzer.analyser) {
|
| audioAnalyzer.analyser = audioAnalyzer.context.createAnalyser();
|
| audioAnalyzer.analyser.fftSize = 256;
|
| audioAnalyzer.analyser.smoothingTimeConstant = 0.8;
|
| audioAnalyzer.analyser.connect(audioAnalyzer.context.destination);
|
| }
|
|
|
|
|
| let source = mediaSourceCache.get(audioElement);
|
| if (!source) {
|
| source = audioAnalyzer.context.createMediaElementSource(audioElement);
|
| mediaSourceCache.set(audioElement, source);
|
| console.log("🎵 Created new MediaElementSource for audio element");
|
| }
|
|
|
|
|
| source.connect(audioAnalyzer.analyser);
|
| audioAnalyzer.source = source;
|
|
|
|
|
| if (!audioAnalyzer.frequencyData) {
|
| audioAnalyzer.frequencyData = new Uint8Array(audioAnalyzer.analyser.frequencyBinCount);
|
| audioAnalyzer.timeDomainData = new Uint8Array(audioAnalyzer.analyser.fftSize);
|
| }
|
|
|
| audioAnalyzer.isConnected = true;
|
| console.log("🎵 Audio visualizer connected! Particles will dance ⚡");
|
| } catch (e) {
|
| console.log("🎵 Could not connect audio visualizer:", e.message);
|
| }
|
| };
|
|
|
| window.disconnectAudioVisualizer = function () {
|
| if (audioAnalyzer.source && audioAnalyzer.analyser) {
|
| try {
|
|
|
|
|
| audioAnalyzer.source.disconnect(audioAnalyzer.analyser);
|
| } catch (e) {}
|
| }
|
| audioAnalyzer.isConnected = false;
|
| audioAnalyzer.source = null;
|
| console.log("🎵 Audio visualizer disconnected");
|
| };
|
|
|
| function updateAudioAnalysis() {
|
| if (!audioAnalyzer.isConnected || !audioAnalyzer.analyser) {
|
| audioAnalyzer.bassLevel = 0;
|
| audioAnalyzer.midLevel = 0;
|
| audioAnalyzer.highLevel = 0;
|
| audioAnalyzer.averageLevel = 0;
|
| return;
|
| }
|
|
|
| audioAnalyzer.analyser.getByteFrequencyData(audioAnalyzer.frequencyData);
|
|
|
| const bins = audioAnalyzer.frequencyData.length;
|
| let bass = 0,
|
| mid = 0,
|
| high = 0;
|
|
|
|
|
| for (let i = 0; i < bins; i++) {
|
| const value = audioAnalyzer.frequencyData[i] / 255;
|
| if (i < bins * 0.15) {
|
| bass += value;
|
| } else if (i < bins * 0.5) {
|
| mid += value;
|
| } else {
|
| high += value;
|
| }
|
| }
|
|
|
|
|
| audioAnalyzer.bassLevel = bass / (bins * 0.15);
|
| audioAnalyzer.midLevel = mid / (bins * 0.35);
|
| audioAnalyzer.highLevel = high / (bins * 0.5);
|
| audioAnalyzer.averageLevel = (audioAnalyzer.bassLevel + audioAnalyzer.midLevel + audioAnalyzer.highLevel) / 3;
|
|
|
|
|
| const now = Date.now();
|
| if (audioAnalyzer.bassLevel > CONFIG.audio.beatThreshold && now - audioAnalyzer.lastBeatTime > 500) {
|
| audioAnalyzer.lastBeatTime = now;
|
| if (CONFIG.audio.enabled) {
|
| createLightningBolt();
|
| }
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
| const canvas = document.getElementById("lightning-canvas");
|
| if (!canvas) return;
|
|
|
|
|
| const scene = new THREE.Scene();
|
|
|
|
|
| const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
| camera.position.z = 50;
|
|
|
|
|
| const renderer = new THREE.WebGLRenderer({
|
| canvas: canvas,
|
| alpha: true,
|
| antialias: true
|
| });
|
| renderer.setSize(window.innerWidth, window.innerHeight);
|
| renderer.setPixelRatio(CONFIG.pixelRatio);
|
|
|
|
|
|
|
|
|
|
|
| let particles;
|
| let orbs = [];
|
|
|
| function createParticles() {
|
| const geometry = new THREE.BufferGeometry();
|
| const positions = new Float32Array(CONFIG.particles.count * 3);
|
| const colors = new Float32Array(CONFIG.particles.count * 3);
|
| const velocities = [];
|
|
|
| for (let i = 0; i < CONFIG.particles.count; i++) {
|
|
|
| positions[i * 3] = (Math.random() - 0.5) * 100;
|
| positions[i * 3 + 1] = (Math.random() - 0.5) * 100;
|
| positions[i * 3 + 2] = (Math.random() - 0.5) * 50;
|
|
|
|
|
| const hue = (i / CONFIG.particles.count) * 0.3 + 0.5;
|
| const color = new THREE.Color().setHSL(hue, 0.8, 0.6);
|
| colors[i * 3] = color.r;
|
| colors[i * 3 + 1] = color.g;
|
| colors[i * 3 + 2] = color.b;
|
|
|
|
|
| velocities.push({
|
| x: (Math.random() - 0.5) * CONFIG.particles.baseSpeed,
|
| y: (Math.random() - 0.5) * CONFIG.particles.baseSpeed,
|
| z: (Math.random() - 0.5) * CONFIG.particles.baseSpeed * 0.5
|
| });
|
| }
|
|
|
| geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
| geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
|
|
|
| const material = new THREE.PointsMaterial({
|
| size: CONFIG.particles.size,
|
| transparent: true,
|
| opacity: CONFIG.particles.opacity,
|
| blending: THREE.AdditiveBlending,
|
| vertexColors: true
|
| });
|
|
|
| particles = new THREE.Points(geometry, material);
|
| particles.userData.velocities = velocities;
|
| scene.add(particles);
|
| }
|
|
|
|
|
|
|
|
|
|
|
| let stars = [];
|
|
|
| function createStarShape() {
|
| const shape = new THREE.Shape();
|
| const outerRadius = 1;
|
| const innerRadius = 0.4;
|
| const points = 5;
|
|
|
| for (let i = 0; i < points * 2; i++) {
|
| const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
| const angle = (i * Math.PI) / points;
|
| const x = Math.cos(angle) * radius;
|
| const y = Math.sin(angle) * radius;
|
|
|
| if (i === 0) shape.moveTo(x, y);
|
| else shape.lineTo(x, y);
|
| }
|
| shape.closePath();
|
|
|
| return new THREE.ShapeGeometry(shape);
|
| }
|
|
|
| function createStars() {
|
| const starCount = 15;
|
| const starGeometry = createStarShape();
|
|
|
| for (let i = 0; i < starCount; i++) {
|
| const material = new THREE.MeshBasicMaterial({
|
| color: currentPalette.accent,
|
| transparent: true,
|
| opacity: 0.6,
|
| side: THREE.DoubleSide,
|
| blending: THREE.AdditiveBlending
|
| });
|
|
|
| const star = new THREE.Mesh(starGeometry, material);
|
| star.position.set((Math.random() - 0.5) * 80, (Math.random() - 0.5) * 60, (Math.random() - 0.5) * 30 - 10);
|
| star.rotation.z = Math.random() * Math.PI * 2;
|
| star.userData = {
|
| twinkleSpeed: 0.5 + Math.random() * 1.5,
|
| twinklePhase: Math.random() * Math.PI * 2,
|
| rotationSpeed: (Math.random() - 0.5) * 0.01
|
| };
|
|
|
| scene.add(star);
|
| stars.push(star);
|
| }
|
| }
|
|
|
| function updateStars() {
|
| if (!stars.length) return;
|
|
|
| const time = Date.now() * 0.001;
|
| const audioBoost = 1 + audioAnalyzer.averageLevel * 0.5;
|
|
|
| stars.forEach(star => {
|
|
|
| const twinkle = Math.sin(time * star.userData.twinkleSpeed + star.userData.twinklePhase);
|
| star.material.opacity = 0.3 + twinkle * 0.3 + audioAnalyzer.highLevel * 0.4;
|
|
|
|
|
| star.rotation.z += star.userData.rotationSpeed * audioBoost;
|
|
|
|
|
| const scale = 1 + audioAnalyzer.bassLevel * 0.3;
|
| star.scale.setScalar(scale);
|
| });
|
| }
|
|
|
|
|
|
|
|
|
|
|
| let halos = [];
|
|
|
| function createHalos() {
|
| const haloCount = 3;
|
|
|
| for (let i = 0; i < haloCount; i++) {
|
| const geometry = new THREE.RingGeometry(8 + i * 5, 9 + i * 5, 32);
|
| const material = new THREE.MeshBasicMaterial({
|
| color: currentPalette.glow,
|
| transparent: true,
|
| opacity: 0.1,
|
| side: THREE.DoubleSide,
|
| blending: THREE.AdditiveBlending
|
| });
|
|
|
| const halo = new THREE.Mesh(geometry, material);
|
| halo.position.set(0, 0, -20 - i * 5);
|
| halo.userData = {
|
| baseScale: 1,
|
| pulseSpeed: 0.8 + i * 0.3,
|
| pulsePhase: i * Math.PI * 0.5
|
| };
|
|
|
| scene.add(halo);
|
| halos.push(halo);
|
| }
|
| }
|
|
|
| function updateHalos() {
|
| if (!halos.length) return;
|
|
|
| const time = Date.now() * 0.001;
|
| const bassBoost = 1 + audioAnalyzer.bassLevel * 2;
|
|
|
| halos.forEach((halo, i) => {
|
|
|
| const pulse = Math.sin(time * halo.userData.pulseSpeed + halo.userData.pulsePhase);
|
| const scale = halo.userData.baseScale + pulse * 0.15 * bassBoost;
|
| halo.scale.setScalar(scale);
|
|
|
|
|
| halo.material.opacity = 0.05 + pulse * 0.05 + audioAnalyzer.averageLevel * 0.15;
|
|
|
|
|
| halo.rotation.z += 0.001 * (i + 1);
|
| });
|
| }
|
|
|
|
|
|
|
|
|
|
|
| function createOrbs() {
|
| if (!CONFIG.effects.floatingOrbs) return;
|
|
|
| const orbCount = 5;
|
| const colors = [
|
| currentPalette.primary,
|
| currentPalette.secondary,
|
| currentPalette.accent,
|
| currentPalette.secondary,
|
| currentPalette.primary
|
| ];
|
|
|
| for (let i = 0; i < orbCount; i++) {
|
| const geometry = new THREE.SphereGeometry(1 + Math.random() * 2, 16, 16);
|
| const material = new THREE.MeshBasicMaterial({
|
| color: colors[i],
|
| transparent: true,
|
| opacity: 0.15,
|
| blending: THREE.AdditiveBlending
|
| });
|
|
|
| const orb = new THREE.Mesh(geometry, material);
|
| orb.position.set((Math.random() - 0.5) * 60, (Math.random() - 0.5) * 40, (Math.random() - 0.5) * 20 - 10);
|
| orb.userData = {
|
| baseY: orb.position.y,
|
| speed: 0.5 + Math.random() * 0.5,
|
| phase: Math.random() * Math.PI * 2
|
| };
|
|
|
| scene.add(orb);
|
| orbs.push(orb);
|
| }
|
| }
|
|
|
| function updateOrbs() {
|
| if (!orbs.length) return;
|
|
|
| const time = Date.now() * 0.001;
|
| const audioBoost = 1 + audioAnalyzer.averageLevel * 2;
|
|
|
| orbs.forEach((orb, i) => {
|
|
|
| orb.position.y = orb.userData.baseY + Math.sin(time * orb.userData.speed + orb.userData.phase) * 5;
|
| orb.position.x += Math.sin(time * 0.3 + i) * 0.02;
|
|
|
|
|
| const scale = (1 + Math.sin(time * 2 + i) * 0.2) * audioBoost;
|
| orb.scale.setScalar(scale);
|
|
|
|
|
| orb.material.opacity = 0.1 + Math.sin(time + i) * 0.05 + audioAnalyzer.bassLevel * 0.1;
|
| });
|
| }
|
|
|
| function updateParticles() {
|
| if (!particles) return;
|
|
|
| const positions = particles.geometry.attributes.position.array;
|
| const colors = particles.geometry.attributes.color.array;
|
| const velocities = particles.userData.velocities;
|
|
|
|
|
| const audioBoost = 1 + audioAnalyzer.averageLevel * CONFIG.audio.particleReactivity;
|
| const bassBoost = 1 + audioAnalyzer.bassLevel * 3;
|
|
|
|
|
| if (audioAnalyzer.isConnected) {
|
| particles.material.size = CONFIG.particles.size * bassBoost;
|
| particles.material.opacity = Math.min(0.8, CONFIG.particles.opacity + audioAnalyzer.midLevel * 0.4);
|
|
|
|
|
| if (CONFIG.audio.colorShift && audioAnalyzer.highLevel > 0.3) {
|
| const hue = (Date.now() * 0.001 + audioAnalyzer.highLevel) % 1;
|
| particles.material.color.setHSL(0.6 + hue * 0.2, 0.8, 0.6);
|
| } else {
|
| particles.material.color.setHex(CONFIG.particles.color);
|
| }
|
| }
|
|
|
| for (let i = 0; i < CONFIG.particles.count; i++) {
|
|
|
| const speedMultiplier = audioBoost;
|
| positions[i * 3] += velocities[i].x * speedMultiplier;
|
| positions[i * 3 + 1] += velocities[i].y * speedMultiplier;
|
| positions[i * 3 + 2] += velocities[i].z * speedMultiplier;
|
|
|
|
|
| if (audioAnalyzer.isConnected && CONFIG.audio.colorShift) {
|
| const time = Date.now() * 0.0005;
|
| const hue = (i / CONFIG.particles.count + time) % 1;
|
| const color = new THREE.Color().setHSL(hue, 0.8, 0.5 + audioAnalyzer.averageLevel * 0.3);
|
| colors[i * 3] = color.r;
|
| colors[i * 3 + 1] = color.g;
|
| colors[i * 3 + 2] = color.b;
|
| }
|
|
|
|
|
| if (positions[i * 3] > 50) positions[i * 3] = -50;
|
| if (positions[i * 3] < -50) positions[i * 3] = 50;
|
| if (positions[i * 3 + 1] > 50) positions[i * 3 + 1] = -50;
|
| if (positions[i * 3 + 1] < -50) positions[i * 3 + 1] = 50;
|
| }
|
|
|
| particles.geometry.attributes.position.needsUpdate = true;
|
| if (audioAnalyzer.isConnected) {
|
| particles.geometry.attributes.color.needsUpdate = true;
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
| let activeLightning = [];
|
| let lightningPool = [];
|
| const MAX_POOL_SIZE = 20;
|
|
|
| function createLightningBolt() {
|
|
|
| const boltColor = currentPalette.primary;
|
| const glowColor = currentPalette.glow;
|
|
|
|
|
| const startX = (Math.random() - 0.5) * 80;
|
| const startY = 40 + Math.random() * 20;
|
|
|
|
|
| const endX = startX + (Math.random() - 0.5) * 40;
|
| const endY = -40 - Math.random() * 20;
|
|
|
|
|
| const points = generateBoltPath(startX, startY, endX, endY, 8);
|
|
|
|
|
| const mainBolt = createBoltMesh(points, boltColor, 2);
|
| scene.add(mainBolt);
|
| activeLightning.push(mainBolt);
|
|
|
|
|
| const glowBolt = createBoltMesh(points, glowColor, 6, 0.3);
|
| scene.add(glowBolt);
|
| activeLightning.push(glowBolt);
|
|
|
|
|
| for (let i = 0; i < CONFIG.lightning.branches; i++) {
|
| const branchStart = Math.floor(Math.random() * (points.length - 2)) + 1;
|
| const branchPoint = points[branchStart];
|
|
|
| const branchEndX = branchPoint.x + (Math.random() - 0.5) * 30;
|
| const branchEndY = branchPoint.y - 10 - Math.random() * 20;
|
|
|
| const branchPoints = generateBoltPath(branchPoint.x, branchPoint.y, branchEndX, branchEndY, 4);
|
|
|
| const branch = createBoltMesh(branchPoints, CONFIG.lightning.color, 1, 0.7);
|
| scene.add(branch);
|
| activeLightning.push(branch);
|
| }
|
|
|
|
|
| flashScreen();
|
|
|
|
|
| setTimeout(() => {
|
| activeLightning.forEach(bolt => {
|
| scene.remove(bolt);
|
|
|
| if (lightningPool.length < MAX_POOL_SIZE) {
|
| bolt.visible = false;
|
| lightningPool.push(bolt);
|
| } else {
|
|
|
| bolt.geometry.dispose();
|
| bolt.material.dispose();
|
| }
|
| });
|
| activeLightning = [];
|
| }, CONFIG.lightning.duration);
|
|
|
| console.log("⚡ Lightning strike!");
|
| }
|
|
|
| function generateBoltPath(startX, startY, endX, endY, segments) {
|
| const points = [];
|
| points.push(new THREE.Vector3(startX, startY, 0));
|
|
|
| const dx = (endX - startX) / segments;
|
| const dy = (endY - startY) / segments;
|
|
|
| for (let i = 1; i < segments; i++) {
|
| const x = startX + dx * i + (Math.random() - 0.5) * 15;
|
| const y = startY + dy * i + (Math.random() - 0.5) * 5;
|
| const z = (Math.random() - 0.5) * 5;
|
| points.push(new THREE.Vector3(x, y, z));
|
| }
|
|
|
| points.push(new THREE.Vector3(endX, endY, 0));
|
| return points;
|
| }
|
|
|
| function createBoltMesh(points, color, lineWidth, opacity = 1) {
|
| const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
|
|
| const material = new THREE.LineBasicMaterial({
|
| color: color,
|
| linewidth: lineWidth,
|
| transparent: true,
|
| opacity: opacity,
|
| blending: THREE.AdditiveBlending
|
| });
|
|
|
| return new THREE.Line(geometry, material);
|
| }
|
|
|
|
|
| let flashElement = null;
|
|
|
| function flashScreen() {
|
|
|
| if (!flashElement) {
|
| flashElement = document.createElement("div");
|
| flashElement.style.cssText = `
|
| position: fixed;
|
| inset: 0;
|
| background: rgba(59, 130, 246, 0.1);
|
| pointer-events: none;
|
| z-index: 9999;
|
| opacity: 0;
|
| transition: opacity 0.15s ease-out;
|
| `;
|
| document.body.appendChild(flashElement);
|
| }
|
|
|
|
|
| flashElement.style.opacity = "1";
|
| requestAnimationFrame(() => {
|
| requestAnimationFrame(() => {
|
| flashElement.style.opacity = "0";
|
| });
|
| });
|
| }
|
|
|
|
|
| const style = document.createElement("style");
|
| style.textContent = `
|
| @keyframes flashFade {
|
| 0% { opacity: 1; }
|
| 100% { opacity: 0; }
|
| }
|
| `;
|
| document.head.appendChild(style);
|
|
|
|
|
|
|
|
|
|
|
| function scheduleLightning() {
|
| if (!CONFIG.lightning.enabled) return;
|
|
|
| const delay =
|
| CONFIG.lightning.minInterval +
|
| Math.random() * (CONFIG.lightning.maxInterval - CONFIG.lightning.minInterval);
|
|
|
| setTimeout(() => {
|
| createLightningBolt();
|
| scheduleLightning();
|
| }, delay);
|
| }
|
|
|
|
|
|
|
|
|
|
|
| let isAnimating = true;
|
| let animationFrameId = null;
|
|
|
| function animate() {
|
| if (!isAnimating) return;
|
|
|
| animationFrameId = requestAnimationFrame(animate);
|
|
|
|
|
| updateAudioAnalysis();
|
|
|
|
|
| if (CONFIG.particles.enabled) {
|
| updateParticles();
|
| }
|
|
|
|
|
| if (CONFIG.effects.floatingOrbs) {
|
| updateOrbs();
|
| }
|
|
|
|
|
| updateStars();
|
|
|
|
|
| updateHalos();
|
|
|
|
|
| const audioSway = audioAnalyzer.isConnected ? audioAnalyzer.bassLevel * 2 : 0;
|
| camera.position.x = Math.sin(Date.now() * 0.0001) * (2 + audioSway);
|
| camera.position.y = Math.cos(Date.now() * 0.00015) * (1 + audioSway * 0.5);
|
|
|
| renderer.render(scene, camera);
|
| }
|
|
|
|
|
| window.pauseVisualEffects = function () {
|
| isAnimating = false;
|
| if (animationFrameId) {
|
| cancelAnimationFrame(animationFrameId);
|
| animationFrameId = null;
|
| }
|
| console.log("⚡ Visual effects paused (CPU saved!)");
|
| };
|
|
|
| window.resumeVisualEffects = function () {
|
| if (!isAnimating) {
|
| isAnimating = true;
|
| animate();
|
| console.log("⚡ Visual effects resumed");
|
| }
|
| };
|
|
|
|
|
|
|
|
|
|
|
| function onResize() {
|
| camera.aspect = window.innerWidth / window.innerHeight;
|
| camera.updateProjectionMatrix();
|
| renderer.setSize(window.innerWidth, window.innerHeight);
|
| }
|
|
|
| window.addEventListener("resize", onResize);
|
|
|
|
|
|
|
|
|
|
|
| function init() {
|
| if (CONFIG.particles.enabled) {
|
| createParticles();
|
| }
|
|
|
| if (CONFIG.effects.floatingOrbs) {
|
| createOrbs();
|
| }
|
|
|
|
|
| createStars();
|
|
|
|
|
| createHalos();
|
|
|
| if (CONFIG.lightning.enabled) {
|
|
|
| setTimeout(createLightningBolt, 2000);
|
| scheduleLightning();
|
| }
|
|
|
| animate();
|
| console.log("⚡ Lightning effects initialized!");
|
| console.log("💙 Particles floating... lo-fi vibes activated");
|
| console.log("✨ Floating orbs created!");
|
| console.log("⭐ Twinkling stars added!");
|
| console.log("🔮 Pulsing halos activated!");
|
| console.log("🎵 Audio visualizer ready — connect radio to make particles dance!");
|
| console.log("🎨 Color palettes: focus (blue), short (green), long (purple)");
|
| }
|
|
|
| init();
|
| })();
|
|
|