Input
{
"patchText": "*** Begin Patch\n*** Update File: /tmp/opencode/rickroll-sip/leadfinder-saas/app.js\n@@\n+import * as THREE from \"three\";\n+import { GLTFLoader } from \"three/addons/loaders/GLTFLoader.js\";\n+\n const form = document.querySelector(\"#lead-form\");\n const results = document.querySelector(\"#results\");\n const copyButton = document.querySelector(\"#copy-button\");\n@@\n function startModelScene() {\n if (!modelCanvas) return;\n \n- const context = modelCanvas.getContext(\"2d\");\n- const models = [\n- makeCube(1.35, [-1.15, -0.1, 0], \"#7dd3fc\"),\n- makePyramid(1.28, [1.05, -0.2, 0.25], \"#c084fc\"),\n- makeOctahedron(1.05, [0.1, 0.82, -0.2], \"#86efac\"),\n- makeRing(0.95, [0.05, -1.02, 0.15], \"#fde68a\"),\n- ];\n+ const scene = new THREE.Scene();\n+ const camera = new THREE.PerspectiveCamera(42, 1, 0.1, 100);\n+ const renderer = new THREE.WebGLRenderer({ canvas: modelCanvas, alpha: true, antialias: true });\n+ const loader = new GLTFLoader();\n+ const clock = new THREE.Clock();\n+ const modelGroup = new THREE.Group();\n+\n+ camera.position.set(0, 1.2, 7.2);\n+ scene.add(modelGroup);\n+ scene.add(new THREE.AmbientLight(0x7dd3fc, 1.5));\n+\n+ const cyan = new THREE.PointLight(0x7dd3fc, 48, 16);\n+ cyan.position.set(-3.8, 2.6, 4.5);\n+ scene.add(cyan);\n+\n+ const violet = new THREE.PointLight(0xc084fc, 42, 16);\n+ violet.position.set(4, -0.8, 3.2);\n+ scene.add(violet);\n+\n+ const lime = new THREE.PointLight(0x86efac, 18, 11);\n+ lime.position.set(0, 3.5, -2);\n+ scene.add(lime);\n+\n+ addStarfield(scene);\n+ addScanGrid(scene);\n+ loadRealModels(loader, modelGroup);\n@@\n function resize() {\n const ratio = window.devicePixelRatio || 1;\n const bounds = modelCanvas.getBoundingClientRect();\n- modelCanvas.width = bounds.width * ratio;\n- modelCanvas.height = bounds.height * ratio;\n- context.setTransform(ratio, 0, 0, ratio, 0, 0);\n+ renderer.setPixelRatio(Math.min(ratio, 2));\n+ renderer.setSize(bounds.width, bounds.height, false);\n+ camera.aspect = bounds.width / bounds.height;\n+ camera.updateProjectionMatrix();\n }\n \n- function draw(time) {\n- const width = modelCanvas.clientWidth;\n- const height = modelCanvas.clientHeight;\n- const tick = time * 0.001;\n-\n- context.clearRect(0, 0, width, height);\n- drawGrid(context, width, height, tick);\n-\n- for (const [index, model] of models.entries()) {\n- const points = model.points.map((point) => rotatePoint(point, tick, index));\n- const projected = points.map((point) => project(point, model.offset, width, height));\n- drawModel(context, projected, model.edges, model.color, tick + index);\n- }\n-\n- requestAnimationFrame(draw);\n+ function draw() {\n+ const tick = clock.getElapsedTime();\n+\n+ modelGroup.rotation.y = Math.sin(tick * 0.32) * 0.22;\n+ camera.position.x = Math.sin(tick * 0.24) * 0.55;\n+ camera.position.y = 1.15 + Math.cos(tick * 0.28) * 0.18;\n+ camera.lookAt(0, 0.2, 0);\n+\n+ for (const child of modelGroup.children) {\n+ child.rotation.y += child.userData.spinY || 0;\n+ child.rotation.x += child.userData.spinX || 0;\n+ child.position.y = child.userData.baseY + Math.sin(tick * child.userData.floatSpeed + child.userData.phase) * 0.14;\n+ }\n+\n+ renderer.render(scene, camera);\n+ requestAnimationFrame(draw);\n }\n@@\n requestAnimationFrame(draw);\n }\n \n-function makeCube(size, offset, color) {\n- const s = size / 2;\n- const points = [\n- [-s, -s, -s], [s, -s, -s], [s, s, -s], [-s, s, -s],\n- [-s, -s, s], [s, -s, s], [s, s, s], [-s, s, s],\n- ];\n- const edges = [[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7]];\n- return { points, edges, offset, color };\n-}\n-\n-function makePyramid(size, offset, color) {\n- const s = size / 2;\n- const points = [[-s, s, -s], [s, s, -s], [s, s, s], [-s, s, s], [0, -s, 0]];\n- const edges = [[0, 1], [1, 2], [2, 3], [3, 0], [0, 4], [1, 4], [2, 4], [3, 4]];\n- return { points, edges, offset, color };\n-}\n-\n-function makeOctahedron(size, offset, color) {\n- const points = [[0, -size, 0], [size, 0, 0], [0, 0, size], [-size, 0, 0], [0, 0, -size], [0, size, 0]];\n- const edges = [[0, 1], [0, 2], [0, 3], [0, 4], [5, 1], [5, 2], [5, 3], [5, 4], [1, 2], [2, 3], [3, 4], [4, 1]];\n- return { points, edges, offset, color };\n-}\n-\n-function makeRing(size, offset, color) {\n- const points = [];\n- const edges = [];\n- const segments = 24;\n-\n- for (let index = 0; index < segments; index += 1) {\n- const angle = (index / segments) * Math.PI * 2;\n- points.push([Math.cos(angle) * size, Math.sin(angle) * size * 0.35, Math.sin(angle) * size]);\n- edges.push([index, (index + 1) % segments]);\n- if (index % 3 === 0) edges.push([index, (index + 6) % segments]);\n- }\n-\n- return { points, edges, offset, color };\n-}\n-\n-function rotatePoint(point, tick, index) {\n- const [x, y, z] = point;\n- const ax = tick * (0.44 + index * 0.08);\n- const ay = tick * (0.58 + index * 0.05);\n- const az = tick * (0.2 + index * 0.04);\n- const cy = Math.cos(ay);\n- const sy = Math.sin(ay);\n- const cx = Math.cos(ax);\n- const sx = Math.sin(ax);\n- const cz = Math.cos(az);\n- const sz = Math.sin(az);\n- const x1 = x * cy - z * sy;\n- const z1 = x * sy + z * cy;\n- const y1 = y * cx - z1 * sx;\n- const z2 = y * sx + z1 * cx;\n-\n- return [x1 * cz - y1 * sz, x1 * sz + y1 * cz, z2];\n-}\n-\n-function project(point, offset, width, height) {\n- const depth = 4.4 + point[2] + offset[2];\n- const scale = Math.min(width, height) * 0.42 / depth;\n-\n- return {\n- x: width / 2 + (point[0] + offset[0]) * scale,\n- y: height / 2 + (point[1] + offset[1]) * scale,\n- z: depth,\n- };\n-}\n-\n-function drawGrid(context, width, height, tick) {\n- context.save();\n- context.globalAlpha = 0.2;\n- context.strokeStyle = \"#7dd3fc\";\n- context.lineWidth = 1;\n-\n- for (let x = -width; x < width * 2; x += 34) {\n- context.beginPath();\n- context.moveTo(x + Math.sin(tick) * 18, height);\n- context.lineTo(width / 2 + (x - width / 2) * 0.16, height * 0.54);\n- context.stroke();\n- }\n-\n- for (let y = height * 0.55; y < height; y += 22) {\n- context.beginPath();\n- context.moveTo(0, y);\n- context.lineTo(width, y);\n- context.stroke();\n- }\n-\n- context.restore();\n-}\n-\n-function drawModel(context, points, edges, color, tick) {\n- context.save();\n- context.shadowColor = color;\n- context.shadowBlur = 18;\n- context.strokeStyle = color;\n- context.lineWidth = 1.8;\n-\n- for (const [start, end] of edges) {\n- context.globalAlpha = 0.42 + Math.sin(tick + points[start].z) * 0.2;\n- context.beginPath();\n- context.moveTo(points[start].x, points[start].y);\n- context.lineTo(points[end].x, points[end].y);\n- context.stroke();\n- }\n-\n- for (const point of points) {\n- context.globalAlpha = 0.72;\n- context.beginPath();\n- context.arc(point.x, point.y, 2.6, 0, Math.PI * 2);\n- context.fillStyle = color;\n- context.fill();\n- }\n-\n- context.restore();\n+function loadRealModels(loader, group) {\n+ const assets = [\n+ {\n+ url: \"https://threejs.org/examples/models/gltf/DamagedHelmet/glTF-Binary/DamagedHelmet.glb\",\n+ position: [-1.55, 0.05, 0],\n+ scale: 1.18,\n+ color: 0x7dd3fc,\n+ },\n+ {\n+ url: \"https://threejs.org/examples/models/gltf/Flamingo.glb\",\n+ position: [1.45, 0.2, -0.2],\n+ scale: 0.012,\n+ color: 0xc084fc,\n+ },\n+ {\n+ url: \"https://threejs.org/examples/models/gltf/Parrot.glb\",\n+ position: [0.05, -1.05, 0.35],\n+ scale: 0.014,\n+ color: 0x86efac,\n+ },\n+ ];\n+\n+ assets.forEach((asset, index) => {\n+ loader.load(asset.url, (gltf) => {\n+ const model = gltf.scene;\n+ model.position.set(...asset.position);\n+ model.scale.setScalar(asset.scale);\n+ model.userData = {\n+ baseY: asset.position[1],\n+ floatSpeed: 1.2 + index * 0.28,\n+ phase: index * 1.7,\n+ spinX: index === 0 ? 0.002 : 0.001,\n+ spinY: 0.006 + index * 0.002,\n+ };\n+\n+ model.traverse((child) => {\n+ if (!child.isMesh) return;\n+ child.castShadow = true;\n+ child.material = new THREE.MeshStandardMaterial({\n+ color: asset.color,\n+ emissive: asset.color,\n+ emissiveIntensity: index === 0 ? 0.16 : 0.34,\n+ metalness: 0.72,\n+ roughness: 0.2,\n+ transparent: true,\n+ opacity: 0.86,\n+ });\n+ });\n+\n+ group.add(model);\n+ }, undefined, () => {\n+ group.add(makeFallbackModel(asset, index));\n+ });\n+ });\n+}\n+\n+function addStarfield(scene) {\n+ const geometry = new THREE.BufferGeometry();\n+ const vertices = [];\n+\n+ for (let index = 0; index < 420; index += 1) {\n+ vertices.push((Math.random() - 0.5) * 10, (Math.random() - 0.5) * 7, (Math.random() - 0.5) * 8 - 2);\n+ }\n+\n+ geometry.setAttribute(\"position\", new THREE.Float32BufferAttribute(vertices, 3));\n+ const material = new THREE.PointsMaterial({ color: 0x7dd3fc, size: 0.018, transparent: true, opacity: 0.72 });\n+ scene.add(new THREE.Points(geometry, material));\n+}\n+\n+function addScanGrid(scene) {\n+ const grid = new THREE.GridHelper(8, 28, 0x7dd3fc, 0xc084fc);\n+ grid.position.y = -1.65;\n+ grid.rotation.x = 0.18;\n+ grid.material.transparent = true;\n+ grid.material.opacity = 0.28;\n+ scene.add(grid);\n+}\n+\n+function makeFallbackModel(asset, index) {\n+ const geometry = index === 0\n+ ? new THREE.IcosahedronGeometry(0.9, 2)\n+ : new THREE.TorusKnotGeometry(0.62, 0.18, 120, 16);\n+ const material = new THREE.MeshStandardMaterial({\n+ color: asset.color,\n+ emissive: asset.color,\n+ emissiveIntensity: 0.28,\n+ metalness: 0.78,\n+ roughness: 0.18,\n+ wireframe: true,\n+ });\n+ const model = new THREE.Mesh(geometry, material);\n+ model.position.set(...asset.position);\n+ model.userData = {\n+ baseY: asset.position[1],\n+ floatSpeed: 1.2 + index * 0.28,\n+ phase: index * 1.7,\n+ spinX: 0.002,\n+ spinY: 0.008,\n+ };\n+ return model;\n }\n*** End Patch"
}