{"project":{"id":"1ok6Rqg","userId":"davidyarham@gmail.com","username":null,"userPicture":null,"name":"Neon Dash","visible":true,"contributors":"","githubRepo":null,"forkedFrom":null,"isTemplate":false,"tags":"","files":{"folder":"","files":[{"name":"index.html","content":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Untitled</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js\"></script>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n</head>\n<body>\n<div id=\"game-ui\">\n\t<div id=\"start-screen\" class=\"overlay\">\n\t\t<h1>NEON DASH</h1>\n\t\t<p>TAP SIDES TO MOVE</p>\n\t\t<button id=\"start-btn\">START ENGINE</button>\n\t</div>\n\n\t<div id=\"game-over\" class=\"overlay\" style=\"display:none\">\n\t\t<h1>CRASHED</h1>\n\t\t<p id=\"final-score\">Score: 0</p>\n\t\t<p id=\"high-score\" style=\"font-size: 0.8rem; opacity: 0.7\">Best: 0</p>\n\t\t<button id=\"restart-btn\">RETRY</button>\n\t</div>\n\t<div id=\"score-board\">0</div>\n</div>\n<div id=\"canvas-container\"></div>\n  <script type=\"module\" src=\"main.js\"></script>\n</body>\n</html>"},{"name":"main.js","content":"let scene, camera, renderer, player, obstacles = [],\n\tclock, isPlaying = false,\n\tscore = 0,\n\thighScore = localStorage.getItem('neonDashHigh') || 0;\nlet audioCtx, spawnTimer = 0;\nconst LANES = [-2, 0, 2];\nlet currentLane = 1;\n\nfunction playSound(freq, type, duration, vol = 0.1, slide = 0) {\n\tif (!audioCtx) audioCtx = new(window.AudioContext || window.webkitAudioContext)();\n\tif (audioCtx.state === 'suspended') audioCtx.resume();\n\tconst osc = audioCtx.createOscillator();\n\tconst gain = audioCtx.createGain();\n\tosc.type = type;\n\tosc.frequency.setValueAtTime(freq, audioCtx.currentTime);\n\tif (slide) osc.frequency.exponentialRampToValueAtTime(slide, audioCtx.currentTime + duration);\n\tgain.gain.setValueAtTime(vol, audioCtx.currentTime);\n\tgain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + duration);\n\tosc.connect(gain);\n\tgain.connect(audioCtx.destination);\n\tosc.start();\n\tosc.stop(audioCtx.currentTime + duration);\n}\n\nfunction init() {\n\tscene = new THREE.Scene();\n\tscene.fog = new THREE.FogExp2(0x050505, 0.08);\n\n\tcamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);\n\tcamera.position.set(0, 3, 6);\n\n\trenderer = new THREE.WebGLRenderer({\n\t\tantialias: true\n\t});\n\trenderer.setSize(window.innerWidth, window.innerHeight);\n\trenderer.setPixelRatio(window.devicePixelRatio);\n\tdocument.getElementById('canvas-container').appendChild(renderer.domElement);\n\n\tscene.add(new THREE.AmbientLight(0xffffff, 0.4));\n\tconst point = new THREE.PointLight(0x00ffff, 1, 20);\n\tpoint.position.set(0, 5, 2);\n\tscene.add(point);\n\n\tconst grid = new THREE.GridHelper(200, 50, 0xff00ff, 0x333333);\n\tgrid.position.z = -40;\n\tscene.add(grid);\n\n\tconst barGeo = new THREE.BoxGeometry(0.2, 0.5, 100);\n\tconst barMat = new THREE.MeshPhongMaterial({\n\t\tcolor: 0x00ffff,\n\t\temissive: 0x00ffff\n\t});\n\t[-4, 4].forEach(x => {\n\t\tconst bar = new THREE.Mesh(barGeo, barMat);\n\t\tbar.position.set(x, 0.25, -40);\n\t\tscene.add(bar);\n\t});\n\n\tplayer = new THREE.Mesh(\n\t\tnew THREE.BoxGeometry(1, 0.5, 1.5),\n\t\tnew THREE.MeshPhongMaterial({\n\t\t\tcolor: 0x00ffff,\n\t\t\temissive: 0x00ffff,\n\t\t\temissiveIntensity: 0.5\n\t\t})\n\t);\n\tplayer.position.y = 0.5;\n\tscene.add(player);\n\n\tclock = new THREE.Clock();\n\twindow.addEventListener('resize', onWindowResize);\n\twindow.addEventListener('touchstart', handleTouch);\n\tanimate();\n}\n\nfunction handleTouch(e) {\n\tif (!isPlaying) return;\n\tconst oldLane = currentLane;\n\tif (e.touches[0].clientX < window.innerWidth / 2) {\n\t\tif (currentLane > 0) currentLane--;\n\t} else {\n\t\tif (currentLane < 2) currentLane++;\n\t}\n\tif (oldLane !== currentLane) playSound(440 + (currentLane * 110), 'square', 0.1, 0.05, 880);\n}\n\nfunction spawnObstacle() {\n\tif (!isPlaying) return;\n\tconst numToBlock = Math.random() > 0.6 ? 2 : 1;\n\tconst availableIndices = [0, 1, 2].sort(() => Math.random() - 0.5);\n\n\tfor (let i = 0; i < numToBlock; i++) {\n\t\tconst obs = new THREE.Mesh(\n\t\t\tnew THREE.BoxGeometry(1.2, 1.2, 1.2),\n\t\t\tnew THREE.MeshPhongMaterial({\n\t\t\t\tcolor: 0xff00ff,\n\t\t\t\temissive: 0xff00ff\n\t\t\t})\n\t\t);\n\t\tobs.position.set(LANES[availableIndices[i]], 0.6, -60);\n\t\tscene.add(obs);\n\t\tobstacles.push(obs);\n\t}\n}\n\nfunction animate() {\n\trequestAnimationFrame(animate);\n\tconst delta = clock.getDelta();\n\tif (isPlaying) {\n\t\tscore += delta * 10;\n\t\tdocument.getElementById('score-board').innerText = Math.floor(score);\n\t\tplayer.position.x = THREE.MathUtils.lerp(player.position.x, LANES[currentLane], 0.2);\n\n\t\tconst speed = 18 + (score / 150);\n\t\tspawnTimer -= delta;\n\t\tif (spawnTimer <= 0) {\n\t\t\tspawnObstacle();\n\t\t\tspawnTimer = Math.max(0.4, 0.8 - (score / 2000));\n\t\t}\n\n\t\tfor (let i = obstacles.length - 1; i >= 0; i--) {\n\t\t\tconst obs = obstacles[i];\n\t\t\tobs.position.z += speed * delta;\n\t\t\tobs.rotation.x += delta * 2;\n\t\t\tif (obs.position.z > -1 && obs.position.z < 1) {\n\t\t\t\tif (Math.abs(obs.position.x - player.position.x) < 0.8) gameOver();\n\t\t\t}\n\t\t\tif (obs.position.z > 10) {\n\t\t\t\tscene.remove(obs);\n\t\t\t\tobstacles.splice(i, 1);\n\t\t\t}\n\t\t}\n\t}\n\trenderer.render(scene, camera);\n}\n\nfunction startGame() {\n\tisPlaying = true;\n\tscore = 0;\n\tcurrentLane = 1;\n\tspawnTimer = 0;\n\tobstacles.forEach(o => scene.remove(o));\n\tobstacles = [];\n\tdocument.getElementById('start-screen').style.display = 'none';\n\tdocument.getElementById('game-over').style.display = 'none';\n\tplaySound(220, 'sawtooth', 0.5, 0.1, 440);\n}\n\nfunction gameOver() {\n\tisPlaying = false;\n\tplaySound(100, 'sawtooth', 0.8, 0.2, 20);\n\tif (score > highScore) {\n\t\thighScore = Math.floor(score);\n\t\tlocalStorage.setItem('neonDashHigh', highScore);\n\t}\n\tdocument.getElementById('game-over').style.display = 'block';\n\tdocument.getElementById('final-score').innerText = \"Final Score: \" + Math.floor(score);\n\tdocument.getElementById('high-score').innerText = \"Best: \" + highScore;\n}\n\nfunction onWindowResize() {\n\tcamera.aspect = window.innerWidth / window.innerHeight;\n\tcamera.updateProjectionMatrix();\n\trenderer.setSize(window.innerWidth, window.innerHeight);\n}\n\ndocument.getElementById('start-btn').onclick = startGame;\ndocument.getElementById('restart-btn').onclick = startGame;\ninit();"},{"name":"style.css","content":"body {\n\tmargin: 0;\n\toverflow: hidden;\n\tbackground: #050505;\n\tfont-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\n}\n\n#game-ui {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tpointer-events: none;\n\tz-index: 10;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.overlay {\n\tpointer-events: auto;\n\tbackground: rgba(0, 0, 0, 0.85);\n\tpadding: 2rem;\n\tborder: 2px solid #0ff;\n\tborder-radius: 15px;\n\ttext-align: center;\n\tbox-shadow: 0 0 20px #0ff;\n\tcolor: #0ff;\n}\n\nh1 {\n\tmargin-top: 0;\n\tletter-spacing: 5px;\n\ttext-shadow: 0 0 10px #0ff;\n}\n\nbutton {\n\tbackground: #0ff;\n\tborder: none;\n\tpadding: 15px 30px;\n\tfont-weight: bold;\n\tfont-size: 1.2rem;\n\tcursor: pointer;\n\tborder-radius: 5px;\n\tmargin-top: 20px;\n\ttransition: transform 0.1s;\n}\n\nbutton:active {\n\ttransform: scale(0.95);\n}\n\n#score-board {\n\tposition: absolute;\n\ttop: 20px;\n\tright: 20px;\n\tfont-size: 2rem;\n\tfont-weight: bold;\n\tcolor: #ff00ff;\n\ttext-shadow: 0 0 10px #ff00ff;\n}"}],"folders":[]},"variants":null,"createdAt":"2026-02-27T12:35:28.915Z","updatedAt":"2026-03-02T00:30:40.781Z","hasThumbnail":true}}