EliteDevs

Template Showcase: 3D Crystal Field

A 3D Animated, modern ART with Graphics for Fun.

Complete Project Code

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Interactive Crystal Field</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000511; /* Deep dark blue */
            cursor: none;
        }
        canvas {
            display: block;
        }
        #intro-screen {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            background-color: #000511;
            z-index: 100;
            cursor: default;
        }
        #intro-screen h1 {
            font-family: 'Helvetica Neue', sans-serif;
            font-weight: 200;
            font-size: 4rem;
            color: #fff;
            text-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff;
            margin: 0;
        }
        #start-btn {
            font-family: 'Helvetica Neue', sans-serif;
            font-size: 1.5rem;
            color: #00ffff;
            background-color: transparent;
            border: 2px solid #00ffff;
            padding: 10px 30px;
            border-radius: 5px;
            cursor: pointer;
            margin-top: 2.5rem;
            transition: all 0.3s ease;
            box-shadow: 0 0 15px rgba(0, 255, 255, 0.5);
        }
        #start-btn:hover {
            background-color: #00ffff;
            color: #000511;
            box-shadow: 0 0 25px #00ffff, 0 0 50px #00ffff;
        }
        #info {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: rgba(255, 255, 255, 0.7);
            font-family: 'Helvetica Neue', sans-serif;
            font-size: 1.5rem;
            text-align: center;
            pointer-events: none;
            display: none; /* Hidden by default */
            animation: fadeInOut 6s ease-in-out forwards;
        }
        @keyframes fadeInOut {
            0% { opacity: 0; }
            20% { opacity: 1; }
            80% { opacity: 1; }
            100% { opacity: 0; }
        }
    </style>
</head>
<body>
    <div id="intro-screen">
        <h1>Crystal Field</h1>
        <button id="start-btn">Enter</button>
    </div>

    <div id="info">Move your cursor to illuminate the scene</div>

    <!-- three.js library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

    <script>
        // --- Global Variables ---
        let scene, camera, renderer;
        let crystals = [];
        let mouseLight;
        let introScreen, startButton, infoDiv, canvasElement;

        // Mouse coordinates
        const mouse = { x: 0, y: 0 };

        function init() {
            // Scene
            scene = new THREE.Scene();
            
            // Camera
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.z = 50;

            // Renderer
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(window.devicePixelRatio);
            canvasElement = renderer.domElement;
            canvasElement.style.display = 'none'; // Hide canvas initially
            document.body.appendChild(canvasElement);

            // Get DOM elements
            introScreen = document.getElementById('intro-screen');
            startButton = document.getElementById('start-btn');
            infoDiv = document.getElementById('info');

            // --- Lighting ---
            const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
            scene.add(ambientLight);

            mouseLight = new THREE.PointLight(0x00ffff, 2, 100, 2);
            mouseLight.position.set(0, 0, 20);
            scene.add(mouseLight);

            // --- Object Creation (Crystals) ---
            const crystalGeometry = [
                new THREE.IcosahedronGeometry(1, 0),
                new THREE.OctahedronGeometry(1, 0),
                new THREE.DodecahedronGeometry(1, 0)
            ];

            for (let i = 0; i < 150; i++) {
                const material = new THREE.MeshStandardMaterial({
                    color: 0xffffff,
                    metalness: 1.0,
                    roughness: 0.2,
                    flatShading: true
                });
                const geometry = crystalGeometry[Math.floor(Math.random() * crystalGeometry.length)];
                const crystal = new THREE.Mesh(geometry, material);
                crystal.position.set((Math.random() - 0.5) * 100, (Math.random() - 0.5) * 100, (Math.random() - 0.5) * 100);
                crystal.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI);
                const scale = Math.random() * 2 + 1;
                crystal.scale.set(scale, scale, scale);
                crystal.userData.rotationSpeed = { x: (Math.random() - 0.5) * 0.01, y: (Math.random() - 0.5) * 0.01 };
                crystals.push(crystal);
                scene.add(crystal);
            }

            // --- Event Listeners ---
            startButton.addEventListener('click', startExperience);
            canvasElement.addEventListener('click', endExperience);
            window.addEventListener('resize', onWindowResize, false);
            window.addEventListener('mousemove', onMouseMove, false);

            animate();
        }

        // --- Experience Control Functions ---
        function startExperience() {
            introScreen.style.display = 'none';
            canvasElement.style.display = 'block';
            infoDiv.style.display = 'block';
            
            // Reset the fade-out animation for the info text
            infoDiv.style.animation = 'none';
            void infoDiv.offsetWidth; // Trigger reflow to restart animation
            infoDiv.style.animation = 'fadeInOut 6s ease-in-out forwards';
        }

        function endExperience() {
            introScreen.style.display = 'flex';
            canvasElement.style.display = 'none';
            infoDiv.style.display = 'none';
        }

        // --- Event Handlers ---
        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function onMouseMove(event) {
            mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
        }

        // --- Animation Loop ---
        function animate() {
            requestAnimationFrame(animate);

            crystals.forEach(crystal => {
                crystal.rotation.x += crystal.userData.rotationSpeed.x;
                crystal.rotation.y += crystal.userData.rotationSpeed.y;
            });

            const vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
            vector.unproject(camera);
            const dir = vector.sub(camera.position).normalize();
            const distance = -camera.position.z / dir.z;
            const pos = camera.position.clone().add(dir.multiplyScalar(distance));
            mouseLight.position.copy(pos);

            renderer.render(scene, camera);
        }

        init();
    </script>
</body>
</html>