All Prompts
All Prompts

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>