<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sudarshana Chakra - Enhanced</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;600&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
font-family: 'Rajdhani', sans-serif;
color: #ffffff;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
.info-box {
position: absolute;
top: 10px;
left: 10px;
padding: 10px 20px;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.2);
font-size: 1.2rem;
text-shadow: 1px 1px 2px #000;
transition: opacity 0.3s;
}
.info-box.fade-out {
opacity: 0;
}
.controls {
position: absolute;
bottom: 10px;
right: 10px;
padding: 10px 15px;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.2);
font-size: 0.9rem;
}
.controls label {
display: block;
margin-bottom: 5px;
}
.controls input[type="range"] {
width: 150px;
}
</style>
</head>
<body>
<div id="infoBox" class="info-box">
Drag to rotate • Use controls to adjust
</div>
<div class="controls hidden sm:block">
<label>
Rotation Speed: <span id="speedValue">0.05</span>
<input type="range" id="speedControl" min="0" max="0.2" step="0.01" value="0.05">
</label>
<label>
<input type="checkbox" id="bloomToggle" checked> Bloom Effect
</label>
</div>
<script>
// --- CONFIGURATION ---
const CONFIG = {
chakraSize: window.innerWidth < 768 ? 60 : 100,
particleCount: window.innerWidth < 768 ? 10000 : 20000,
cameraDistance: null, // Calculated based on chakraSize
rotationSpeed: 0.05,
bloomEnabled: true
};
CONFIG.cameraDistance = CONFIG.chakraSize * 3.5;
// --- GLSL SHADER CODE ---
// Vertex shader for the Sudarshana Chakra's main disk and spikes
const chakraVertexShader = `
varying vec3 vNormal;
varying vec2 vUv;
varying vec3 vPosition;
void main() {
vNormal = normal;
vUv = uv;
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
// Fragment shader for the chakra's main disk with enhanced glow
const chakraFragmentShader = `
uniform float time;
uniform vec3 color1;
uniform vec3 color2;
uniform float bloomIntensity;
varying vec3 vNormal;
varying vec2 vUv;
varying vec3 vPosition;
vec3 mod289(vec3 x) { return x - floor(x / 289.0) * 289.0; }
vec2 mod289(vec2 x) { return x - floor(x / 289.0) * 289.0; }
vec3 permute(vec3 x) { return mod289((x * 34.0 + 1.0) * x); }
float snoise(vec2 v) {
const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
vec2 i = floor(v + dot(v, C.yy));
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod289(i);
vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
m = m * m;
m = m * m;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.y = a0.y * x12.x + h.y * x12.y;
g.z = a0.z * x12.z + h.z * x12.w;
return 130.0 * dot(m, g);
}
void main() {
float dist = length(vUv - 0.5);
float radialGlow = smoothstep(0.5, 0.0, dist);
vec2 st = vUv * 5.0;
float noiseVal = snoise(st + time * 0.2);
noiseVal += snoise(st * 2.0 - time * 0.1) * 0.5;
noiseVal += snoise(st * 4.0 + time * 0.3) * 0.25;
float finalNoise = (noiseVal + radialGlow) * 0.5 + sin(time * 3.0) * 0.1;
vec3 finalColor = mix(color1, color2, finalNoise);
float glow = pow(radialGlow, 2.0) * 2.0 + pow(finalNoise, 3.0) * 1.5;
vec3 glowColor = finalColor + vec3(glow * bloomIntensity);
gl_FragColor = vec4(glowColor, 1.0);
}
`;
// Shader for spikes with consistent glow
const spikeFragmentShader = `
uniform float time;
uniform vec3 color1;
uniform vec3 color2;
uniform float bloomIntensity;
varying vec3 vPosition;
void main() {
float pulse = sin(time * 2.0) * 0.5 + 0.5;
vec3 baseColor = mix(color1, color2, pulse);
vec3 glowColor = baseColor * (1.0 + bloomIntensity * 0.5);
gl_FragColor = vec4(glowColor, 1.0);
}
`;
// --- Global Variables ---
let scene, camera, renderer;
let chakra, particleVortex, spikesGroup;
let coreMaterial, spikeMaterial;
let animationFrameId = null;
let angleX = 0, angleY = 0;
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
let clock = new THREE.Clock();
let infoBox;
let hideInfoTimeout;
/**
* @function init
* @description Initializes the Three.js scene, camera, and renderer.
*/
function init() {
// Get info box element
infoBox = document.getElementById('infoBox');
// Scene setup
scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x000000, 100, 500);
// Camera setup
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = CONFIG.cameraDistance;
// Renderer setup
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // Cap at 2x for performance
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// --- Lighting ---
const ambientLight = new THREE.AmbientLight(0x404040, 1.5);
scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xFFD700, 2, 500);
pointLight.position.set(0, 0, 0);
scene.add(pointLight);
// --- Create the Sudarshana Chakra with enhanced shaders ---
chakra = new THREE.Object3D();
// Create shader materials
coreMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0.0 },
color1: { value: new THREE.Color(0x8A2BE2) }, // Deep purple
color2: { value: new THREE.Color(0xFFD700) }, // Glowing gold
bloomIntensity: { value: CONFIG.bloomEnabled ? 1.0 : 0.5 }
},
vertexShader: chakraVertexShader,
fragmentShader: chakraFragmentShader,
side: THREE.DoubleSide
});
// Main central disk
const coreGeometry = new THREE.TorusGeometry(CONFIG.chakraSize, CONFIG.chakraSize * 0.5, 32, 100);
const coreMesh = new THREE.Mesh(coreGeometry, coreMaterial);
chakra.add(coreMesh);
// Spikes with matching shader material
spikeMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0.0 },
color1: { value: new THREE.Color(0xFFD700) },
color2: { value: new THREE.Color(0xEE82EE) },
bloomIntensity: { value: CONFIG.bloomEnabled ? 1.0 : 0.5 }
},
vertexShader: chakraVertexShader,
fragmentShader: spikeFragmentShader
});
spikesGroup = new THREE.Object3D();
const spikeCount = 108;
const spokeGeometry = new THREE.BoxGeometry(CONFIG.chakraSize * 0.05, 5, 2);
for (let i = 0; i < spikeCount; i++) {
const spokeMesh = new THREE.Mesh(spokeGeometry, spikeMaterial);
spokeMesh.position.x = CONFIG.chakraSize * 0.7;
spokeMesh.rotation.z = Math.PI / 2;
const spike = new THREE.Object3D();
spike.add(spokeMesh);
const angle = (i / spikeCount) * Math.PI * 2;
spike.rotation.z = angle;
spike.rotation.x = Math.PI / 2;
spikesGroup.add(spike);
}
chakra.add(spikesGroup);
scene.add(chakra);
// --- Particle Vortex/Aura with enhanced color transition ---
const particleGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(CONFIG.particleCount * 3);
const colors = new Float32Array(CONFIG.particleCount * 3);
const initialColor = new THREE.Color(0xADD8E6); // Light blue
for (let i = 0; i < CONFIG.particleCount; i++) {
const i3 = i * 3;
positions[i3 + 0] = (Math.random() - 0.5) * CONFIG.chakraSize * 4;
positions[i3 + 1] = (Math.random() - 0.5) * CONFIG.chakraSize * 4;
positions[i3 + 2] = (Math.random() - 0.5) * 500;
colors[i3 + 0] = initialColor.r;
colors[i3 + 1] = initialColor.g;
colors[i3 + 2] = initialColor.b;
}
particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
particleGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const particleMaterial = new THREE.PointsMaterial({
size: window.innerWidth < 768 ? 1.5 : 2,
vertexColors: true,
blending: THREE.AdditiveBlending,
transparent: true,
opacity: 0.8
});
particleVortex = new THREE.Points(particleGeometry, particleMaterial);
scene.add(particleVortex);
// Event listeners
window.addEventListener('resize', onWindowResize, false);
document.addEventListener('mousedown', onDocumentMouseDown, false);
document.addEventListener('mouseup', onDocumentMouseUp, false);
document.addEventListener('mousemove', onDocumentMouseMove, false);
document.addEventListener('touchstart', onDocumentTouchStart, false);
document.addEventListener('touchmove', onDocumentTouchMove, false);
document.addEventListener('touchend', onDocumentTouchEnd, false);
// Controls
setupControls();
animate();
}
/**
* @function setupControls
* @description Sets up UI controls for adjusting visualization parameters
*/
function setupControls() {
const speedControl = document.getElementById('speedControl');
const speedValue = document.getElementById('speedValue');
const bloomToggle = document.getElementById('bloomToggle');
if (speedControl) {
speedControl.addEventListener('input', (e) => {
CONFIG.rotationSpeed = parseFloat(e.target.value);
speedValue.textContent = CONFIG.rotationSpeed.toFixed(2);
});
}
if (bloomToggle) {
bloomToggle.addEventListener('change', (e) => {
CONFIG.bloomEnabled = e.target.checked;
const intensity = CONFIG.bloomEnabled ? 1.0 : 0.5;
coreMaterial.uniforms.bloomIntensity.value = intensity;
spikeMaterial.uniforms.bloomIntensity.value = intensity;
});
}
}
/**
* @function animate
* @description Main animation loop with optimizations
*/
function animate() {
animationFrameId = requestAnimationFrame(animate);
const elapsedTime = clock.getElapsedTime();
// Update shader uniforms
coreMaterial.uniforms.time.value = elapsedTime;
spikeMaterial.uniforms.time.value = elapsedTime;
// Continuous rotation of the chakra
chakra.rotation.z += CONFIG.rotationSpeed;
// Animate the particle vortex with improved color transitions
animateParticles();
updateCamera();
renderer.render(scene, camera);
}
/**
* @function animateParticles
* @description Handles particle animation with optimized calculations
*/
function animateParticles() {
const particlePositions = particleVortex.geometry.attributes.position.array;
const particleColors = particleVortex.geometry.attributes.color.array;
const initialColor = new THREE.Color(0xADD8E6);
const midColor = new THREE.Color(0xFF69B4); // Hot pink for transition
const finalColor = new THREE.Color(0xFFD700);
const effectRadius = CONFIG.chakraSize * 2;
const speed = 0.005;
const invEffectRadius = 1 / effectRadius; // Pre-calculate for performance
for (let i = 0; i < CONFIG.particleCount; i++) {
const i3 = i * 3;
let x = particlePositions[i3];
let y = particlePositions[i3 + 1];
let z = particlePositions[i3 + 2];
const radius = Math.sqrt(x * x + y * y);
let angle = Math.atan2(y, x);
// Swirl effect within influence radius
if (radius < effectRadius) {
const swirlStrength = 0.01 * (1 - (radius * invEffectRadius));
angle += swirlStrength * CONFIG.rotationSpeed;
}
angle += speed;
x = radius * Math.cos(angle);
y = radius * Math.sin(angle);
particlePositions[i3] = x;
particlePositions[i3 + 1] = y;
// Move through the Z axis
z += 2;
if (z > 200) {
x = (Math.random() - 0.5) * CONFIG.chakraSize * 4;
y = (Math.random() - 0.5) * CONFIG.chakraSize * 4;
z = -500;
}
particlePositions[i3 + 2] = z;
// Enhanced color transition over a wider range
if (z > -50 && z < 50) {
const progress = (z + 50) / 100; // 0 to 1
if (progress < 0.5) {
// Transition from initial to mid color
const t = progress * 2;
particleColors[i3 + 0] = THREE.MathUtils.lerp(initialColor.r, midColor.r, t);
particleColors[i3 + 1] = THREE.MathUtils.lerp(initialColor.g, midColor.g, t);
particleColors[i3 + 2] = THREE.MathUtils.lerp(initialColor.b, midColor.b, t);
} else {
// Transition from mid to final color
const t = (progress - 0.5) * 2;
particleColors[i3 + 0] = THREE.MathUtils.lerp(midColor.r, finalColor.r, t);
particleColors[i3 + 1] = THREE.MathUtils.lerp(midColor.g, finalColor.g, t);
particleColors[i3 + 2] = THREE.MathUtils.lerp(midColor.b, finalColor.b, t);
}
} else if (z >= 50) {
// Keep final color after passing through
particleColors[i3 + 0] = finalColor.r;
particleColors[i3 + 1] = finalColor.g;
particleColors[i3 + 2] = finalColor.b;
} else {
// Keep initial color before entering
particleColors[i3 + 0] = initialColor.r;
particleColors[i3 + 1] = initialColor.g;
particleColors[i3 + 2] = initialColor.b;
}
}
particleVortex.geometry.attributes.position.needsUpdate = true;
particleVortex.geometry.attributes.color.needsUpdate = true;
}
/**
* @function updateCamera
* @description Updates the camera position based on mouse/touch drag.
*/
function updateCamera() {
camera.position.x = CONFIG.cameraDistance * Math.sin(angleY) * Math.cos(angleX);
camera.position.y = CONFIG.cameraDistance * Math.sin(angleX);
camera.position.z = CONFIG.cameraDistance * Math.cos(angleY) * Math.cos(angleX);
camera.lookAt(scene.position);
}
// --- Event Handlers for Interaction ---
function onWindowResize() {
const width = window.innerWidth;
const height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
function showInfoBox() {
if (infoBox) {
infoBox.classList.remove('fade-out');
clearTimeout(hideInfoTimeout);
hideInfoTimeout = setTimeout(() => {
infoBox.classList.add('fade-out');
}, 3000);
}
}
function onDocumentMouseDown(event) {
isDragging = true;
previousMousePosition = { x: event.clientX, y: event.clientY };
}
function onDocumentMouseUp() {
isDragging = false;
}
function onDocumentMouseMove(event) {
if (isDragging) {
const deltaX = event.clientX - previousMousePosition.x;
const deltaY = event.clientY - previousMousePosition.y;
angleY += deltaX * 0.005;
angleX += deltaY * 0.005;
angleX = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, angleX));
previousMousePosition = { x: event.clientX, y: event.clientY };
}
}
function onDocumentTouchStart(event) {
if (event.touches.length === 1) {
event.preventDefault();
isDragging = true;
previousMousePosition = { x: event.touches[0].pageX, y: event.touches[0].pageY };
showInfoBox();
}
}
function onDocumentTouchMove(event) {
if (event.touches.length === 1 && isDragging) {
event.preventDefault();
const deltaX = event.touches[0].pageX - previousMousePosition.x;
const deltaY = event.touches[0].pageY - previousMousePosition.y;
angleY += deltaX * 0.005;
angleX += deltaY * 0.005;
angleX = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, angleX));
previousMousePosition = { x: event.touches[0].pageX, y: event.touches[0].pageY };
}
}
function onDocumentTouchEnd() {
isDragging = false;
}
/**
* @function cleanup
* @description Properly disposes of Three.js resources
*/
function cleanup() {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
// Dispose geometries and materials
scene.traverse((object) => {
if (object.geometry) {
object.geometry.dispose();
}
if (object.material) {
if (Array.isArray(object.material)) {
object.material.forEach(material => material.dispose());
} else {
object.material.dispose();
}
}
});
renderer.dispose();
}
// Handle page visibility changes
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
} else {
if (!animationFrameId) {
animate();
}
}
});
// Cleanup on page unload
window.addEventListener('beforeunload', cleanup);
// Start the Simulation on Window Load
window.onload = function() {
init();
};
</script>
</body>
</html>