PageSourceSearch

https://qraft-site.netlify.app/js/scene.js?v=14

js qraft-site.netlify.app collected 2026-10-03 11:18:31 UTC 4,407 bytes, 134 lines download raw bytes

1/* ============================================================
2   scene.js — 3D animated background using Three.js
3   A drifting field of neon particles + a slowly rotating
4   wireframe icosahedron. Degrades gracefully if WebGL/Three
5   isn't available, and respects "reduce motion".
6   ============================================================ */
7(function () {
8  const canvas = document.getElementById('bg-canvas');
9  if (!canvas) return;
10
11  // Three.js is loaded on demand by a small loader in index.html (skipped on
12  // mobile / reduced-motion / data-saver). It may not be present yet, so we
13  // define everything up front and only render once start() confirms THREE.
14
15  const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
16
17  let renderer, scene, camera, particles, shape, frameId;
18  const mouse = { x: 0, y: 0 };
19
20  function init() {
21    renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true });
22    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
23    renderer.setSize(window.innerWidth, window.innerHeight);
24
25    scene = new THREE.Scene();
26    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
27    camera.position.z = 60;
28
29    // ---- Particle field ----
30    const count = window.innerWidth < 768 ? 900 : 1800;
31    const positions = new Float32Array(count * 3);
32    const colors = new Float32Array(count * 3);
33    const palette = [
34      new THREE.Color('#7c3aed'),
35      new THREE.Color('#ec4899'),
36      new THREE.Color('#06b6d4'),
37    ];
38
39    for (let i = 0; i < count; i++) {
40      positions[i * 3]     = (Math.random() - 0.5) * 200;
41      positions[i * 3 + 1] = (Math.random() - 0.5) * 140;
42      positions[i * 3 + 2] = (Math.random() - 0.5) * 160;
43      const c = palette[Math.floor(Math.random() * palette.length)];
44      colors[i * 3] = c.r; colors[i * 3 + 1] = c.g; colors[i * 3 + 2] = c.b;
45    }
46
47    const geo = new THREE.BufferGeometry();
48    geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
49    geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
50
51    const mat = new THREE.PointsMaterial({
52      size: 1.1,
53      vertexColors: true,
54      transparent: true,
55      opacity: 0.85,
56      blending: THREE.AdditiveBlending,
57      depthWrite: false,
58    });
59    particles = new THREE.Points(geo, mat);
60    scene.add(particles);
61
62    // ---- Central wireframe icosahedron ----
63    const shapeGeo = new THREE.IcosahedronGeometry(18, 1);
64    const shapeMat = new THREE.MeshBasicMaterial({
65      color: '#7c3aed',
66      wireframe: true,
67      transparent: true,
68      opacity: 0.18,
69    });
70    shape = new THREE.Mesh(shapeGeo, shapeMat);
71    scene.add(shape);
72
73    window.addEventListener('resize', onResize);
74    window.addEventListener('mousemove', onMouseMove);
75
76    animate();
77  }
78
79  function onResize() {
80    camera.aspect = window.innerWidth / window.innerHeight;
81    camera.updateProjectionMatrix();
82    renderer.setSize(window.innerWidth, window.innerHeight);
83  }
84
85  function onMouseMove(e) {
86    mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
87    mouse.y = (e.clientY / window.innerHeight) * 2 - 1;
88  }
89
90  function animate() {
91    frameId = requestAnimationFrame(animate);
92    const t = Date.now() * 0.0001;
93
94    if (particles) {
95      particles.rotation.y = t * 1.4;
96      particles.rotation.x = t * 0.6;
97    }
98    if (shape) {
99      shape.rotation.x += 0.0012;
100      shape.rotation.y += 0.0018;
101    }
102
103    // Gentle parallax toward the cursor.
104    camera.position.x += (mouse.x * 8 - camera.position.x) * 0.03;
105    camera.position.y += (-mouse.y * 6 - camera.position.y) * 0.03;
106    camera.lookAt(scene.position);
107
108    renderer.render(scene, camera);
109  }
110
111  // Render a single static frame if the user prefers reduced motion.
112  function renderStatic() {
113    init();
114    if (frameId) cancelAnimationFrame(frameId);
115    renderer.render(scene, camera);
116  }
117
118  let started = false;
119  function start() {
120    if (started || typeof THREE === 'undefined') return;
121    started = true;
122    try {
123      if (reduceMotion) renderStatic();
124      else init();
125    } catch (err) {
126      console.warn('3D background failed to start:', err);
127    }
128  }
129
130  // The loader in index.html calls this once Three.js finishes downloading.
131  // If Three.js is somehow already present, start immediately.
132  window.__qraftStartScene = start;
133  start();
134})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.