VibeCoderzVibeCoderz
Telegram
All Prompts
Fullscreen Hero with Interactive Three.js Topology preview
herosectionthreejstailwindinteractiveresponsive

Fullscreen Hero with Interactive Three.js Topology

Полноэкранный hero-раздел с интерактивной 3D-топологией Three.js, реагирующей на движение мыши. Идеально для иммерсивных лендингов.

Prompt

<div
  class="bg-[#0A0A0A] text-[#F3F0EA] h-screen w-screen overflow-hidden flex flex-col justify-between p-6 md:p-10 relative selection:bg-[#FF4A22] selection:text-white"
  style="font-family: 'Inter', sans-serif;">

  <script src="https://cdn.tailwindcss.com"></script>
  <script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300&display=swap" rel="stylesheet">

  <!-- Header -->
  <header class="w-full text-center relative z-10 pt-2">
    <p class="text-xs md:text-sm font-thin tracking-wide">Form. Dictated by Interaction.</p>
  </header>

  <!-- WebGL Canvas Container -->
  <div id="webgl-container" class="absolute inset-0 z-0 pointer-events-none flex items-center justify-center"></div>

  <!-- Footer -->
  <footer
    class="w-full flex flex-col md:flex-row justify-between items-center md:items-end relative z-10 pb-2 md:pb-0 gap-8 md:gap-0">
    <div class="w-full md:w-1/3 text-center md:text-left order-2 md:order-1">
      <p class="text-xs md:text-sm font-thin leading-relaxed">Dynamic architectures,<br class="hidden md:block">visualized
      </p>
    </div>

    <div class="w-full md:w-1/3 text-center order-1 md:order-2">
      <h1 class="text-6xl md:text-7xl lg:text-8xl font-extralight tracking-tight">Explore Vertex</h1>
    </div>

    <div class="w-full md:w-1/3 text-center md:text-right order-3 flex justify-center md:justify-end items-center">
      <a href="#"
        class="text-xs md:text-sm font-thin hover:opacity-60 transition-opacity duration-300 flex items-center gap-1 group">
        vertex.io
        <iconify-icon icon="solar:arrow-right-up-linear" stroke-width="1.5"
          class="text-sm transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform"></iconify-icon>
      </a>
    </div>
  </footer>

  <script>
    // WebGL Setup
        const container = document.getElementById('webgl-container');
        const scene = new THREE.Scene();
        
        scene.fog = new THREE.FogExp2(0x0A0A0A, 0.03);

        const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.z = 14;

        const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        container.appendChild(renderer.domElement);

        // Geometry (Reduced segments for performance optimization)
        const tubeRadius = 1.3;
        const baseGeometry = new THREE.TorusKnotGeometry(3.2, tubeRadius, 120, 6);
        baseGeometry.computeVertexNormals();
        
        const count = baseGeometry.attributes.position.count;
        const originalPositions = new Float32Array(baseGeometry.attributes.position.array);
        const normals = baseGeometry.attributes.normal.array;
        
        const colors = new Float32Array(count * 3);
        const linePositions = new Float32Array(count * 6);
        const lineColors = new Float32Array(count * 6);
        
        const colorOrange = new THREE.Color('#FF4A22');
        const colorLight = new THREE.Color('#EBE6DD');
        const colorShadow = new THREE.Color('#1A1A1A'); 

        for (let i = 0; i < count; i++) {
            const ix = i * 3;
            const x = originalPositions[ix];
            const y = originalPositions[ix+1];
            const z = originalPositions[ix+2];

            const angle = Math.atan2(y, x);
            const normalizedAngle = (angle + Math.PI) / (Math.PI * 2);

            let vertexColor = colorLight.clone();

            if (normalizedAngle > 0.05 && normalizedAngle < 0.35) {
                vertexColor = colorOrange.clone();
                const depth = (z + tubeRadius) / (tubeRadius * 2);
                vertexColor.lerp(new THREE.Color('#992000'), 1 - depth * 0.6);
            } else {
                const depth = Math.max(0, Math.min(1, (z + tubeRadius) / (tubeRadius * 2)));
                vertexColor.lerp(colorShadow, (1 - depth) * 0.85);
            }

            // Dot colors
            colors[ix] = vertexColor.r;
            colors[ix+1] = vertexColor.g;
            colors[ix+2] = vertexColor.b;

            // Line colors: Base matches the dot, tip fades to shadow
            const lix = i * 6;
            lineColors[lix] = vertexColor.r;
            lineColors[lix+1] = vertexColor.g;
            lineColors[lix+2] = vertexColor.b;
            
            lineColors[lix+3] = colorShadow.r;
            lineColors[lix+4] = colorShadow.g;
            lineColors[lix+5] = colorShadow.b;
        }

        baseGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

        // Create Particles (Dots)
        const particleMaterial = new THREE.PointsMaterial({
            size: 0.05,
            vertexColors: true,
            transparent: true,
            opacity: 0.95,
            sizeAttenuation: true
        });
        const particles = new THREE.Points(baseGeometry, particleMaterial);

        // Create Shooting Lines
        const lineGeometry = new THREE.BufferGeometry();
        lineGeometry.setAttribute('position', new THREE.BufferAttribute(linePositions, 3));
        lineGeometry.setAttribute('color', new THREE.BufferAttribute(lineColors, 3));

        const lineMaterial = new THREE.LineBasicMaterial({
            vertexColors: true,
            transparent: true,
            opacity: 0.4
        });
        const lines = new THREE.LineSegments(lineGeometry, lineMaterial);

        const webglGroup = new THREE.Group();
        webglGroup.add(particles);
        webglGroup.add(lines);
        
        webglGroup.rotation.x = Math.PI * 0.15;
        webglGroup.rotation.y = -Math.PI * 0.1;
        scene.add(webglGroup);

        // Interaction Variables
        let mouseX = 0;
        let mouseY = 0;
        let targetX = 0;
        let targetY = 0;
        const mouse = new THREE.Vector2(-100, -100);
        const windowHalfX = window.innerWidth / 2;
        const windowHalfY = window.innerHeight / 2;

        document.addEventListener('mousemove', (event) => {
            mouseX = (event.clientX - windowHalfX) * 0.0005;
            mouseY = (event.clientY - windowHalfY) * 0.0005;
            mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
        });

        const clock = new THREE.Clock();
        const raycaster = new THREE.Raycaster();
        const plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
        const targetMouse3D = new THREE.Vector3();
        const currentMouse3D = new THREE.Vector3(0, 0, 1000);

        function animate() {
            requestAnimationFrame(animate);
            const elapsedTime = clock.getElapsedTime();

            targetX = mouseX;
            targetY = mouseY;
            webglGroup.rotation.y += 0.05 * (targetX - webglGroup.rotation.y - Math.PI * 0.1);
            webglGroup.rotation.x += 0.05 * (targetY - webglGroup.rotation.x + Math.PI * 0.15);
            webglGroup.rotation.z = elapsedTime * 0.15;
            webglGroup.position.y = Math.sin(elapsedTime * 0.8) * 0.15;

            raycaster.setFromCamera(mouse, camera);
            if (raycaster.ray.intersectPlane(plane, targetMouse3D)) {
                currentMouse3D.lerp(targetMouse3D, 0.08);
                
                const localMouse = currentMouse3D.clone();
                webglGroup.worldToLocal(localMouse);

                const posArray = baseGeometry.attributes.position.array;
                const linePosArray = lineGeometry.attributes.position.array;
                
                const lx = localMouse.x;
                const ly = localMouse.y;
                const lz = localMouse.z;
                
                const maxDist = 2.8;
                const maxDistSq = maxDist * maxDist;
                const lineLen = 0.45;

                // Highly optimized raw array math loop (Zero allocations)
                for (let i = 0; i < count; i++) {
                    const ix = i * 3;
                    const ox = originalPositions[ix];
                    const oy = originalPositions[ix+1];
                    const oz = originalPositions[ix+2];

                    const dx = ox - lx;
                    const dy = oy - ly;
                    const dz = oz - lz;
                    const distSq = dx*dx + dy*dy + dz*dz;

                    let nx = ox, ny = oy, nz = oz;

                    if (distSq < maxDistSq && distSq > 0.0001) {
                        const dist = Math.sqrt(distSq);
                        const force = Math.pow((maxDist - dist) / maxDist, 2) * 1.8;
                        const invDist = 1.0 / dist;
                        
                        nx += dx * invDist * force;
                        ny += dy * invDist * force;
                        nz += dz * invDist * force;
                    }

                    // Update dot position
                    posArray[ix] = nx;
                    posArray[ix+1] = ny;
                    posArray[ix+2] = nz;

                    // Update line segment (Base -> Tip shooting outward)
                    const lix = i * 6;
                    linePosArray[lix] = nx;
                    linePosArray[lix+1] = ny;
                    linePosArray[lix+2] = nz;
                    
                    linePosArray[lix+3] = nx + normals[ix] * lineLen;
                    linePosArray[lix+4] = ny + normals[ix+1] * lineLen;
                    linePosArray[lix+5] = nz + normals[ix+2] * lineLen;
                }
                
                baseGeometry.attributes.position.needsUpdate = true;
                lineGeometry.attributes.position.needsUpdate = true;
            }

            renderer.render(scene, camera);
        }

        animate();

        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
  </script>
</div>
All Prompts