1/* MICELINK íì´ë¡ ë°°ê²½ â Three.js "MICE ìí° í í´ë¡ì§" (ES module, CDN ë¡ë) 2 물결ì¹ë ìì´ì´ 그리ë ì§í ìì 컨벤ì ì¼í°Â·í¸í ·ìì¡ íë¸ë¥¼ ì¡ê° í리ì¦ì¼ë¡ ì¸ì°ê³ , 3 íë¸ ì¬ì´ë¥¼ ìí¬ë¡ ì°ê²°í´ ìì½ í¨í·ì´ í른ë¤. ë§ì°ì¤ í¨ë´ëì¤ + ëëê·¸ íì . */ 4import * as THREE from "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js"; 5 6/* ê·¸ë¼ëì¸í¸ ì¤ì¹´ì´ ìì íì´í¸ ë¼ì¸ ëìë¡ ë ë (Micro ì¤íì¼) */ 7const WHITE = 0xffffff; 8const INK = 0x221f1c; 9const PRIMARY = WHITE; 10const PRIMARY_DARK = WHITE; 11const PRIMARY_LIGHT = WHITE; 12const PRIMARY_SOFT = WHITE; 13 14/* íë¸: 컨벤ì ì¼í°(ì¤ì) + í¸í /ìì¡ íë¸ (x, z, ëì´, ì) */ 15const HUBS = [ 16 { x: 0, z: 0, h: 96, color: PRIMARY_DARK }, // 컨벤ì ì¼í° 17 { x: 120, z: -80, h: 76, color: PRIMARY }, // í¸í A 18 { x: -130, z: -60, h: 68, color: PRIMARY }, // í¸í B 19 { x: 170, z: 90, h: 60, color: PRIMARY_LIGHT }, // ìì¡ í°ë¯¸ë 20 { x: -160, z: 120, h: 50, color: PRIMARY_LIGHT }, // ê³µí ì í íë¸ 21 { x: 60, z: 160, h: 56, color: PRIMARY }, // í¸í C 22 { x: -70, z: -160, h: 58, color: PRIMARY_SOFT }, // ë¦¬ì¡°í¸ 23]; 24 25function init(host) { 26 const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; 27 const isMobile = window.matchMedia("(max-width: 640px)").matches; 28 29 const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: "low-power" }); 30 renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); 31 renderer.setClearColor(0x000000, 0); 32 host.appendChild(renderer.domElement); 33 34 const scene = new THREE.Scene(); 35 36 const camera = new THREE.PerspectiveCamera(45, 1, 1, 3000); 37 camera.position.set(0, 160, 480); 38 39 scene.add(new THREE.AmbientLight(0xffffff, 0.9)); 40 const key = new THREE.DirectionalLight(0xffffff, 0.9); 41 key.position.set(200, 400, 200); 42 scene.add(key); 43 const fill = new THREE.DirectionalLight(0xcff2ef, 0.5); 44 fill.position.set(-200, 250, -100); 45 scene.add(fill); 46 47 const group = new THREE.Group(); 48 group.rotation.x = 0.38; 49 scene.add(group); 50 51 /* ---- 물결 그리ë ì§í ---- */ 52 const SIZE = 900; 53 const SEG = isMobile ? 32 : 48; 54 const gridGeo = new THREE.PlaneGeometry(SIZE, SIZE, SEG, SEG); 55 gridGeo.rotateX(-Math.PI / 2); 56 const gridOrig = new Float32Array(gridGeo.attributes.position.array); 57 const grid = new THREE.Mesh( 58 gridGeo, 59 new THREE.MeshBasicMaterial({ color: WHITE, wireframe: true, transparent: true, opacity: 0.28 }) 60 ); 61 group.add(grid); 62 63 const groundGeo = new THREE.PlaneGeometry(SIZE, SIZE); 64 groundGeo.rotateX(-Math.PI / 2); 65 const ground = new THREE.Mesh(groundGeo, new THREE.MeshBasicMaterial({ color: WHITE, transparent: true, opacity: 0.1 })); 66 ground.position.y = -6; 67 group.add(ground); 68 69 /* ---- íë¸ ê±´ë¬¼ (ì¡ê° íë¦¬ì¦ + í° ì£ì§ + ë¹ì½ + ì§ë©´ ë§) ---- */ 70 const nodes = []; 71 HUBS.forEach((hub) => { 72 const prismGeo = new THREE.CylinderGeometry(14, 18, hub.h, 6); 73 const prism = new THREE.Mesh( 74 prismGeo, 75 new THREE.MeshStandardMaterial({ color: WHITE, roughness: 0.35, metalness: 0, transparent: true, opacity: 0.92 }) 76 ); 77 prism.position.set(hub.x, hub.h / 2, hub.z); 78 prism.add(new THREE.LineSegments(new THREE.EdgesGeometry(prismGeo), new THREE.LineBasicMaterial({ color: INK, transparent: true, opacity: 0.35 }))); 79 group.add(prism); 80 81 const beacon = new THREE.Mesh(new THREE.SphereGeometry(3.5, 16, 16), new THREE.MeshBasicMaterial({ color: hub.color })); 82 beacon.position.set(hub.x, hub.h + 8, hub.z); 83 group.add(beacon); 84 85 const ringGeo = new THREE.RingGeometry(22, 25, 40); 86 ringGeo.rotateX(-Math.PI / 2); 87 const ring = new THREE.Mesh(ringGeo, new THREE.MeshBasicMaterial({ color: hub.color, side: THREE.DoubleSide, transparent: true, opacity: 0.35 })); 88 ring.position.set(hub.x, 0.8, hub.z); 89 group.add(ring); 90 91 nodes.push({ pos: new THREE.Vector3(hub.x, hub.h + 8, hub.z), ring }); 92 }); 93 94 /* ---- íë¸ ì°ê²° ìí¬ + ì´ë í¨í· ---- */ 95 const packets = []; 96 const center = nodes[0].pos; 97 for (let i = 1; i < nodes.length; i++) { 98 const target = nodes[i].pos; 99 const mid = new THREE.Vector3().addVectors(center, target).multiplyScalar(0.5); 100 mid.y += 50; 101 const curve = new THREE.QuadraticBezierCurve3(center, mid, target); 102 const line = new THREE.Line( 103 new THREE.BufferGeometry().setFromPoints(curve.getPoints(40)), 104 new THREE.LineBasicMaterial({ color: WHITE, transparent: true, op
104acity: 0.7 }) 105 ); 106 group.add(line); 107 108 const packet = new THREE.Mesh(new THREE.SphereGeometry(3, 12, 12), new THREE.MeshBasicMaterial({ color: INK })); 109 group.add(packet); 110 packets.push({ mesh: packet, curve, t: Math.random(), speed: 0.12 + Math.random() * 0.08, dir: i % 2 ? 1 : -1 }); 111 } 112 113 /* ---- ë ì´ìì (í ì¤í¸ê° ì¢ì¸¡ì´ë¼ ëì를 ì°ì¸¡ì¼ë¡) ---- */ 114 function layout() { 115 const w = host.clientWidth || 1; 116 const h = host.clientHeight || 1; 117 renderer.setSize(w, h, false); 118 camera.aspect = w / h; 119 camera.updateProjectionMatrix(); 120 if (w >= 960) group.position.set(150, -50, 0); 121 else group.position.set(0, -70, 0); 122 } 123 layout(); 124 if ("ResizeObserver" in window) new ResizeObserver(layout).observe(host); 125 else window.addEventListener("resize", layout); 126 127 /* ---- ì¸í°ëì : ë§ì°ì¤ í¨ë´ëì¤ + ëëê·¸ íì ---- */ 128 const mouse = { x: 0, y: 0 }; 129 const eased = { x: 0, y: 0 }; 130 let dragRot = 0; 131 let dragging = false; 132 let lastX = 0; 133 134 if (!reduceMotion) { 135 window.addEventListener("pointermove", (e) => { 136 mouse.x = (e.clientX / window.innerWidth - 0.5) * 0.9; 137 mouse.y = (e.clientY / window.innerHeight - 0.5) * 0.9; 138 if (dragging) { 139 dragRot += (e.clientX - lastX) * 0.004; 140 lastX = e.clientX; 141 } 142 }, { passive: true }); 143 } 144 const canvas = renderer.domElement; 145 canvas.addEventListener("pointerdown", (e) => { 146 dragging = true; 147 lastX = e.clientX; 148 host.classList.add("is-dragging"); 149 }); 150 window.addEventListener("pointerup", () => { 151 dragging = false; 152 host.classList.remove("is-dragging"); 153 }); 154 155 /* ---- ë ë 루í ---- */ 156 let visible = true; 157 if ("IntersectionObserver" in window) { 158 new IntersectionObserver((entries) => { visible = entries[0].isIntersecting; }, { threshold: 0 }).observe(host); 159 } 160 161 const clock = new THREE.Clock(); 162 const pos = gridGeo.attributes.position; 163 const arr = pos.array; 164 165 function render(time) { 166 if (!reduceMotion) { 167 group.rotation.y = Math.sin(time * 0.25) * 0.18 + dragRot + eased.x * 0.6; 168 169 for (let i = 0; i < arr.length; i += 3) { 170 const ox = gridOrig[i]; 171 const oz = gridOrig[i + 2]; 172 arr[i + 1] = Math.sin(ox * 0.015 + time * 1.5) * Math.cos(oz * 0.015 + time * 1.2) * 6.5; 173 } 174 pos.needsUpdate = true; 175 176 packets.forEach((p) => { 177 p.t = (p.t + p.speed * 0.016) % 1; 178 p.mesh.position.copy(p.curve.getPointAt(p.dir > 0 ? p.t : 1 - p.t)); 179 }); 180 nodes.forEach((n, idx) => { 181 const s = 1 + Math.sin(time * 2.5 + idx) * 0.08; 182 n.ring.scale.set(s, s, 1); 183 }); 184 } else { 185 group.rotation.y = dragRot; 186 packets.forEach((p) => p.mesh.position.copy(p.curve.getPointAt(0.5))); 187 } 188 189 eased.x += (mouse.x - eased.x) * 0.04; 190 eased.y += (mouse.y - eased.y) * 0.04; 191 camera.position.x = eased.x * 130; 192 camera.position.y = 160 - eased.y * 80; 193 camera.lookAt(group.position.x * 0.3, 20, 0); 194 195 renderer.render(scene, camera); 196 } 197 198 function frame() { 199 requestAnimationFrame(frame); 200 if (!visible || document.hidden) return; 201 render(clock.getElapsedTime()); 202 } 203 204 render(0); 205 host.classList.add("is-ready"); 206 if (!reduceMotion) frame(); 207 else canvas.addEventListener("pointermove", () => render(0)); 208} 209 210/* ì§ì ì â ìë¨ ìì ì´ê¸°í ì´í ì¤í */ 211const host = document.querySelector("[data-hero-bg]"); 212if (host) { 213 try { 214 init(host); 215 } catch (err) { 216 // WebGL 미ì§ì/ì°¨ë¨ íê²½: ë°°ê²½ ìì´ ê·¸ë¼ëì¸í¸ë§ ì ì§ 217 console.warn("[hero-bg] WebGL ì´ê¸°í ì¤í¨, ì ì ë°°ê²½ì¼ë¡ ëì²´í©ëë¤.", err); 218 } 219}
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.