groove-dev 0.27.112 → 0.27.113

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/TRAINING_DATA_v3.md +11 -0
  2. package/codex-test/offroad-nitro-racer/dist/assets/index-CuvdKK6U.js +44 -0
  3. package/codex-test/offroad-nitro-racer/dist/assets/index-DvHn2Thu.css +1 -0
  4. package/codex-test/offroad-nitro-racer/dist/index.html +23 -0
  5. package/codex-test/offroad-nitro-racer/index.html +21 -0
  6. package/codex-test/offroad-nitro-racer/package-lock.json +841 -0
  7. package/codex-test/offroad-nitro-racer/package.json +15 -0
  8. package/codex-test/offroad-nitro-racer/src/game/AI.ts +28 -0
  9. package/codex-test/offroad-nitro-racer/src/game/Audio.ts +63 -0
  10. package/codex-test/offroad-nitro-racer/src/game/Car.ts +247 -0
  11. package/codex-test/offroad-nitro-racer/src/game/Effects.ts +62 -0
  12. package/codex-test/offroad-nitro-racer/src/game/Game.ts +229 -0
  13. package/codex-test/offroad-nitro-racer/src/game/Input.ts +45 -0
  14. package/codex-test/offroad-nitro-racer/src/game/Renderer.ts +224 -0
  15. package/codex-test/offroad-nitro-racer/src/game/Track.ts +158 -0
  16. package/codex-test/offroad-nitro-racer/src/game/UI.ts +96 -0
  17. package/codex-test/offroad-nitro-racer/src/game/math.ts +42 -0
  18. package/codex-test/offroad-nitro-racer/src/main.ts +24 -0
  19. package/codex-test/offroad-nitro-racer/src/style.css +291 -0
  20. package/codex-test/offroad-nitro-racer/src/vite-env.d.ts +1 -0
  21. package/codex-test/offroad-nitro-racer/tsconfig.json +18 -0
  22. package/codex-test/offroad-nitro-racer/vite.config.ts +7 -0
  23. package/moe-training/client/parsers/codex.js +3 -3
  24. package/moe-training/client/parsers/gemini.js +2 -2
  25. package/moe-training/client/step-classifier.js +2 -2
  26. package/moe-training/test/client/step-classifier.test.js +63 -7
  27. package/node_modules/@groove-dev/cli/package.json +1 -1
  28. package/node_modules/@groove-dev/daemon/package.json +1 -1
  29. package/node_modules/@groove-dev/daemon/src/api.js +51 -15
  30. package/node_modules/@groove-dev/daemon/src/index.js +22 -8
  31. package/node_modules/@groove-dev/gui/dist/assets/{index-CHu5w3i3.js → index-BYh6iHqL.js} +3 -3
  32. package/node_modules/@groove-dev/gui/dist/index.html +1 -1
  33. package/node_modules/@groove-dev/gui/package.json +1 -1
  34. package/node_modules/@groove-dev/gui/src/components/preview/preview-workspace.jsx +3 -1
  35. package/node_modules/@groove-dev/gui/src/stores/groove.js +15 -0
  36. package/node_modules/moe-training/client/parsers/codex.js +3 -3
  37. package/node_modules/moe-training/client/parsers/gemini.js +2 -2
  38. package/node_modules/moe-training/client/step-classifier.js +2 -2
  39. package/node_modules/moe-training/test/client/step-classifier.test.js +63 -7
  40. package/package.json +1 -1
  41. package/packages/cli/package.json +1 -1
  42. package/packages/daemon/package.json +1 -1
  43. package/packages/daemon/src/api.js +51 -15
  44. package/packages/daemon/src/index.js +22 -8
  45. package/packages/gui/dist/assets/{index-CHu5w3i3.js → index-BYh6iHqL.js} +3 -3
  46. package/packages/gui/dist/index.html +1 -1
  47. package/packages/gui/package.json +1 -1
  48. package/packages/gui/src/components/preview/preview-workspace.jsx +3 -1
  49. package/packages/gui/src/stores/groove.js +15 -0
  50. package/TRAINING_DATA_v2.md +0 -9
@@ -0,0 +1,44 @@
1
+ (function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=Math.PI*2;function t(e,t,n){return Math.max(t,Math.min(n,e))}function n(e,t,n){return e+(t-e)*n}function r(e,t){return Math.hypot(e,t)}function i(e,t){return Math.hypot(e.x-t.x,e.y-t.y)}function a(t){for(;t>Math.PI;)t-=e;for(;t<-Math.PI;)t+=e;return t}function o(e,t){return Math.atan2(t.y-e.y,t.x-e.x)}function s(e,t,n,r){return e*n+t*r}function c(e,t){return e+Math.random()*(t-e)}function l(e,t,n,r,i,a){e.beginPath(),e.roundRect(t,n,r,i,a)}var u=class{aggression;lookahead;constructor(e){this.aggression=.84+e*.06,this.lookahead=1+e%2}update(e,n,r,i){let s=(e.nextCheckpoint+this.lookahead)%n.checkpoints.length,c=a(o(e,n.nextCheckpoint(s))-e.angle),l=t(c*2.2,-1,1),u=Math.abs(c)>.8?.45:Math.abs(c)>.45?.7:1,d=r<e.rank?1.08:.95;return{throttle:t(this.aggression*u*d+i*.04,.35,1),brake:Math.abs(c)>1.15&&e.speed>115?.65:0,steer:l,nitro:e.nitroCharges>0&&Math.abs(c)<.22&&e.speed>135&&Math.random()<.015+i*.004}}},d=class{ctx=null;master=null;engineOsc=null;engineGain=null;ensure(){if(this.ctx)return;let e=window.AudioContext||window.webkitAudioContext;this.ctx=new e,this.master=this.ctx.createGain(),this.master.gain.value=.18,this.master.connect(this.ctx.destination),this.engineOsc=this.ctx.createOscillator(),this.engineGain=this.ctx.createGain(),this.engineOsc.type=`sawtooth`,this.engineOsc.frequency.value=70,this.engineGain.gain.value=0,this.engineOsc.connect(this.engineGain),this.engineGain.connect(this.master),this.engineOsc.start()}updateEngine(e,n,r){if(!this.ctx||!this.engineOsc||!this.engineGain)return;let i=this.ctx.currentTime;this.engineOsc.frequency.setTargetAtTime(65+t(e,0,240)*1.2+(r?70:0),i,.04),this.engineGain.gain.setTargetAtTime(.025+Math.abs(n)*.05+(r?.035:0),i,.05)}beep(e){if(this.ensure(),!this.ctx||!this.master)return;let t=this.ctx.currentTime,n=this.ctx.createOscillator(),r=this.ctx.createGain(),[i,a,o]={count:[440,.12,`square`],go:[780,.35,`triangle`],pickup:[920,.18,`sine`],boost:[120,.42,`sawtooth`],hit:[80,.18,`square`],finish:[660,.7,`triangle`]}[e];n.type=o,n.frequency.setValueAtTime(i,t),e===`boost`&&n.frequency.exponentialRampToValueAtTime(240,t+a),e===`hit`&&n.frequency.exponentialRampToValueAtTime(45,t+a),r.gain.setValueAtTime(e===`hit`?.13:.18,t),r.gain.exponentialRampToValueAtTime(.001,t+a),n.connect(r),r.connect(this.master),n.start(t),n.stop(t+a+.02)}},f=class{name;color;accent;isPlayer;x;y;vx=0;vy=0;angle;radius=24;width=34;length=54;nitroCharges=2;maxNitro=3;nitroTimer=0;nextCheckpoint=0;lap=1;finished=!1;finishTime=0;airborne=0;verticalVelocity=0;driftAmount=0;collectedPickup=!1;lastProgress=0;totalProgress=0;rank=1;wrongWayTimer=0;constructor(e,t){this.name=e.name,this.color=e.color,this.accent=e.accent,this.isPlayer=!!e.isPlayer,this.x=t.x,this.y=t.y,this.angle=t.angle}get speed(){return Math.hypot(this.vx,this.vy)}reset(e){this.x=e.x,this.y=e.y,this.vx=0,this.vy=0,this.angle=e.angle,this.nitroCharges=this.isPlayer?2:1,this.nitroTimer=0,this.nextCheckpoint=0,this.lap=1,this.finished=!1,this.finishTime=0,this.airborne=0,this.verticalVelocity=0,this.totalProgress=0,this.lastProgress=0,this.rank=1}update(e,n,r,i,o,c=1){if(this.finished){this.vx*=1-e*2,this.vy*=1-e*2;return}this.collectedPickup=!1;let l=r.sample(this.x,this.y),u=Math.cos(this.angle),d=Math.sin(this.angle),f=-d,p=u,m=s(this.vx,this.vy,u,d),h=s(this.vx,this.vy,f,p),g=t(Math.abs(m)/90,.25,1),_=(2.15+c*.08)*l.grip*g;this.angle=a(this.angle+n.steer*_*e*(m>=-15?1:-1));let v=n.throttle*(230+c*12)-n.brake*320;n.nitro&&this.nitroCharges>0&&this.nitroTimer<=0&&n.throttle>0&&(--this.nitroCharges,this.nitroTimer=1.15,i.add(`boost`,this.x,this.y,this.angle,12),i.impact(this.isPlayer?5:2)),this.nitroTimer>0&&(this.nitroTimer-=e,v+=360,Math.random()<.8&&i.add(`boost`,this.x-u*26,this.y-d*26,this.angle,1));let y=(this.nitroTimer>0?330:235+c*8)*l.speedMultiplier;this.vx+=u*v*e,this.vy+=d*v*e;let b=t(s(this.vx,this.vy,u,d),-95,y),x=h*(1-t(e*(6.5*(this.airborne>0?.02:t(l.grip*(n.brake>0?.72:1),.08,1))),0,.92));this.vx=u*b+f*x,this.vy=d*b+p*x;let S=1-e*(l.terrain===`rough`?1.4:.65);this.vx*=S,this.vy*=S,this.x+=this.vx*e,this.y+=this.vy*e,this.driftAmount=Math.abs(x)/90,this.speed>45&&l.terrain!==`track`&&Math.random()<.7&&i.add(`dust`,this.x-u*24,this.y-d*24,this.angle,1),this.driftAmount>.42&&this.airborne<=0&&Math.random()<.6&&i.add(`skid`,this.x-u*18,this.y-d*18,this.angle,1),this.updateAir(e,r,i),this.updateCheckpoints(r,o)}collideWith(e,t){let n=this.x-e.x,i=this.y-e.y,a=r(n,i),o=this.radius+e.radius;if(a<=0||a>=o)return;let c=n/a,l=i/a,u=o-a;this.x+=c*u*.5,this.y+=l*u*.5,e.x-=c*u*.5,e.y-=l*u*.5;let d=s(this.vx-e.vx,this.vy-e.vy,c,l)*.55;this.vx-=c*d,this.vy-=l*d,e.vx+=c*d,e.vy+=l*d,t.add(`spark`,(this.x+e.x)/2,(this.y+e.y)/2,this.angle,5),t.impact(this.isPlayer||e.isPlayer?6:2)}collideObstacles(e,t){for(let n of e.obstacles){let e=this.x-n.x,i=this.y-n.y,a=r(e,i);if(a>=this.radius+n.radius)continue;if(n.kind===`bump`){if(this.airborne<=0&&this.speed>65)return this.verticalVelocity=180+this.speed*.45,t.add(`dust`,this.x,this.y,this.angle,10),t.impact(this.isPlayer?5:1),`bump`;continue}let o=e/Math.max(a,.01),c=i/Math.max(a,.01),l=this.radius+n.radius-a;this.x+=o*l,this.y+=c*l;let u=s(this.vx,this.vy,o,c);return this.vx-=o*u*1.55,this.vy-=c*u*1.55,this.vx*=.72,this.vy*=.72,t.add(`spark`,this.x-o*this.radius,this.y-c*this.radius,this.angle,8),t.impact(this.isPlayer?9:2),`hit`}return null}updateAir(e,t,n){(this.airborne>0||this.verticalVelocity>0)&&(this.airborne+=this.verticalVelocity*e,this.verticalVelocity-=520*e,this.airborne<=0&&(this.airborne=0,this.verticalVelocity=0,this.speed>75&&(n.add(`dust`,this.x,this.y,this.angle,14),n.impact(this.isPlayer?7:2)))),t.consumePickup({x:this.x,y:this.y})&&(this.nitroCharges=Math.min(this.maxNitro,this.nitroCharges+1),this.collectedPickup=!0,n.add(`boost`,this.x,this.y,this.angle,10))}updateCheckpoints(e,t){let n=e.sample(this.x,this.y).progress,r=e.nextCheckpoint(this.nextCheckpoint);Math.hypot(this.x-r.x,this.y-r.y)<155&&(this.nextCheckpoint+=1,this.nextCheckpoint>=e.checkpoints.length&&(this.nextCheckpoint=0,this.lap+=1,this.lap>e.lapsToWin&&(this.finished=!0,this.finishTime=t,this.lap=e.lapsToWin)));let i=n<.15&&this.lastProgress>.85?n+1:n,a=Math.max(0,this.lap-1);this.totalProgress=a+i,this.lastProgress=n}},p=class{particles=[];shake=0;add(e,t,n,r=0,i=1){for(let a=0;a<i;a++){let i=e===`boost`?c(80,180):c(10,95),a=r+Math.PI+c(-.75,.75);this.particles.push({x:t+c(-8,8),y:n+c(-8,8),vx:Math.cos(a)*i+c(-20,20),vy:Math.sin(a)*i+c(-20,20),life:e===`skid`?1.4:c(.25,.75),maxLife:e===`skid`?1.4:.75,size:e===`spark`?c(2,5):c(6,18),kind:e})}}impact(e){this.shake=Math.max(this.shake,e)}update(e){this.shake=Math.max(0,this.shake-e*18);for(let t of this.particles)t.x+=t.vx*e,t.y+=t.vy*e,t.vx*=1-e*2.2,t.vy*=1-e*2.2,t.life-=e;this.particles=this.particles.filter(e=>e.life>0)}render(e){for(let t of this.particles){let n=Math.max(0,t.life/t.maxLife);e.globalAlpha=n*(t.kind===`skid`?.55:.8),e.fillStyle=t.kind===`spark`?`#ffd166`:t.kind===`boost`?`#6ff7ff`:t.kind===`skid`?`#282018`:`#c89b66`,e.beginPath(),e.ellipse(t.x,t.y,t.size*(1.2-n*.3),t.size*.45,0,0,Math.PI*2),e.fill()}e.globalAlpha=1}},m=class{keys=new Set;pressed=new Set;constructor(){window.addEventListener(`keydown`,e=>{let t=this.normalize(e.key);this.keys.has(t)||this.pressed.add(t),this.keys.add(t),[`ArrowUp`,`ArrowDown`,`ArrowLeft`,`ArrowRight`,` `,`Space`].includes(e.key)&&e.preventDefault()}),window.addEventListener(`keyup`,e=>{this.keys.delete(this.normalize(e.key))}),window.addEventListener(`blur`,()=>{this.keys.clear(),this.pressed.clear()})}isDown(...e){return e.some(e=>this.keys.has(this.normalize(e)))}consume(e){let t=this.normalize(e);return this.pressed.has(t)?(this.pressed.delete(t),!0):!1}endFrame(){this.pressed.clear()}normalize(e){return e===` `?`space`:e.toLowerCase()}},h=class{canvas;ctx;pixelRatio=1;constructor(e){this.canvas=e;let t=e.getContext(`2d`);if(!t)throw Error(`Canvas 2D context unavailable`);this.ctx=t,this.resize(),window.addEventListener(`resize`,()=>this.resize())}get width(){return this.canvas.clientWidth}get height(){return this.canvas.clientHeight}resize(){this.pixelRatio=Math.min(window.devicePixelRatio||1,2);let e=window.innerWidth,t=window.innerHeight;this.canvas.width=Math.floor(e*this.pixelRatio),this.canvas.height=Math.floor(t*this.pixelRatio),this.canvas.style.width=`${e}px`,this.canvas.style.height=`${t}px`,this.ctx.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0)}render(e,t,n,r,i){let a=this.ctx;a.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0),a.clearRect(0,0,this.width,this.height);let o=a.createLinearGradient(0,0,this.width,this.height);o.addColorStop(0,`#70451f`),o.addColorStop(.45,`#b67635`),o.addColorStop(1,`#6c3b1f`),a.fillStyle=o,a.fillRect(0,0,this.width,this.height),a.save(),a.translate(this.width/2+i.shakeX,this.height/2+i.shakeY),a.scale(i.zoom,i.zoom*.86),a.translate(-i.x,-i.y),this.drawGroundNoise(a,i),this.drawTrack(a,e),this.drawPickups(a,e),this.drawObstacles(a,e),r.render(a);let s=[...t].sort((e,t)=>e.y+e.airborne-(t.y+t.airborne));for(let e of s)this.drawCar(a,e,e===n);a.restore()}drawGroundNoise(e,t){e.save(),e.globalAlpha=.22;for(let n=0;n<130;n++){let r=Math.floor((t.x-900)/90)*90+n*277%1900,i=Math.floor((t.y-650)/70)*70+n*163%1400;e.fillStyle=n%6==0?`#5c341f`:`#d29554`,e.beginPath(),e.ellipse(r,i,18+n%4*8,5,n%9*.5,0,Math.PI*2),e.fill()}e.restore()}drawTrack(e,t){e.lineJoin=`round`,e.lineCap=`round`,e.strokeStyle=`#5b3d2c`,e.lineWidth=t.roughWidth,this.strokeLoop(e,t.points),e.strokeStyle=`#9a6131`,e.lineWidth=t.width,this.strokeLoop(e,t.points),e.setLineDash([34,34]),e.strokeStyle=`rgba(255, 220, 137, 0.34)`,e.lineWidth=5,this.strokeLoop(e,t.points),e.setLineDash([]);for(let n=0;n<t.checkpoints.length;n++){let r=t.checkpoints[n];e.save(),e.translate(r.x,r.y),e.fillStyle=n===0?`rgba(80,255,190,0.35)`:`rgba(255,255,255,0.14)`,e.fillRect(-95,-5,190,10),e.restore()}}drawPickups(e,t){for(let n of t.pickups)n.active&&(e.save(),e.translate(n.x,n.y),e.rotate(performance.now()*.004),e.fillStyle=`#32f5ff`,e.shadowColor=`#32f5ff`,e.shadowBlur=18,e.beginPath(),e.moveTo(0,-26),e.lineTo(20,0),e.lineTo(0,26),e.lineTo(-20,0),e.closePath(),e.fill(),e.shadowBlur=0,e.fillStyle=`#06343b`,e.font=`bold 20px sans-serif`,e.textAlign=`center`,e.textBaseline=`middle`,e.fillText(`N`,0,1),e.restore())}drawObstacles(e,t){for(let n of t.obstacles)e.save(),e.translate(n.x,n.y),n.kind===`bump`?(e.fillStyle=`#c88a4c`,e.strokeStyle=`#5f3925`,e.lineWidth=5,e.beginPath(),e.ellipse(0,0,n.radius,n.radius*.36,0,0,Math.PI*2),e.fill(),e.stroke()):n.kind===`cactus`?(e.fillStyle=`#216941`,e.fillRect(-8,-34,16,68),e.fillRect(-25,-12,50,13)):n.kind===`barrel`?(e.fillStyle=`#c14c32`,e.strokeStyle=`#621f1c`,e.lineWidth=5,e.beginPath(),e.arc(0,0,n.radius,0,Math.PI*2),e.fill(),e.stroke()):(e.fillStyle=`#4b4038`,e.beginPath(),e.ellipse(0,0,n.radius,n.radius*.8,.4,0,Math.PI*2),e.fill()),e.restore()}drawCar(e,t,n){e.save(),e.translate(t.x,t.y),e.globalAlpha=.35,e.fillStyle=`#000`,e.beginPath(),e.ellipse(0,18+t.airborne*.18,t.width*.76,t.length*.42,t.angle,0,Math.PI*2),e.fill(),e.globalAlpha=1,e.translate(0,-t.airborne*.2),e.rotate(t.angle),e.fillStyle=`#171717`,l(e,-t.length*.46,-t.width*.72,t.length*.92,t.width*1.44,8),e.fill(),e.fillStyle=t.color,l(e,-t.length*.5,-t.width*.5,t.length,t.width,10),e.fill(),e.fillStyle=t.accent,l(e,-2,-t.width*.42,t.length*.26,t.width*.84,6),e.fill(),e.fillStyle=`#dbeafe`,l(e,-t.length*.2,-t.width*.32,t.length*.22,t.width*.64,5),e.fill(),e.fillStyle=`#24211f`;for(let n of[-t.width*.62,t.width*.62])l(e,-t.length*.38,n-5,22,10,3),e.fill(),l(e,t.length*.14,n-5,22,10,3),e.fill();n&&(e.strokeStyle=`#ffffff`,e.lineWidth=4,e.strokeRect(-t.length*.58,-t.width*.58,t.length*1.16,t.width*1.16)),e.restore()}strokeLoop(e,t){e.beginPath(),e.moveTo(t[0].x,t[0].y);for(let n=1;n<t.length;n++)e.lineTo(t[n].x,t[n].y);e.closePath(),e.stroke()}},g=12,_=class{width=230;roughWidth=390;lapsToWin=3;center={x:0,y:0};points=[];checkpoints=[];obstacles=[];pickups=[];constructor(){for(let t=0;t<220;t++){let n=t/220*e,r=Math.sin(n*3)*55+Math.cos(n*5)*24;this.points.push({x:Math.cos(n)*(890+r)+Math.cos(n*2.2)*75,y:Math.sin(n)*(550+Math.sin(n*4)*50)+Math.sin(n*1.5)*60})}for(let e=0;e<g;e++)this.checkpoints.push(this.pointAt(e/g));this.obstacles.push({...this.offsetPoint(.11,-170),radius:32,kind:`rock`},{...this.offsetPoint(.19,145),radius:30,kind:`cactus`},{...this.offsetPoint(.27,-60),radius:58,kind:`bump`},{...this.offsetPoint(.36,150),radius:34,kind:`barrel`},{...this.offsetPoint(.48,-160),radius:36,kind:`rock`},{...this.offsetPoint(.57,60),radius:60,kind:`bump`},{...this.offsetPoint(.68,165),radius:34,kind:`cactus`},{...this.offsetPoint(.78,-145),radius:36,kind:`barrel`},{...this.offsetPoint(.88,75),radius:54,kind:`bump`});for(let e of[.14,.31,.45,.62,.82])this.pickups.push({...this.offsetPoint(e,0),radius:28,active:!0,respawn:0})}update(e){for(let t of this.pickups)t.active||(t.respawn-=e,t.respawn<=0&&(t.active=!0))}startPosition(e){let t=this.pointAt(.985),n=this.pointAt(.995),r=Math.atan2(n.y-t.y,n.x-t.x),i=e%2==0?-1:1;return{x:t.x-Math.sin(r)*i*45-Math.cos(r)*e*42,y:t.y+Math.cos(r)*i*45-Math.sin(r)*e*42,angle:r}}sample(e,t){let n=this.points[0],r=0,i=1/0;for(let a=0;a<this.points.length;a++){let o=Math.hypot(e-this.points[a].x,t-this.points[a].y);o<i&&(i=o,n=this.points[a],r=a)}let a=Math.sin((e+260)*.006)*Math.cos((t-180)*.008)>.72,o=i<this.width/2?a&&i>46?`mud`:`track`:i<this.roughWidth/2?`sand`:`rough`,s={track:{grip:1,speedMultiplier:1},mud:{grip:.62,speedMultiplier:.72},sand:{grip:.72,speedMultiplier:.82},rough:{grip:.46,speedMultiplier:.56}}[o];return{terrain:o,grip:s.grip,speedMultiplier:s.speedMultiplier,nearest:n,distanceFromCenter:i,progress:r/this.points.length}}nextCheckpoint(e){return this.checkpoints[e%this.checkpoints.length]}consumePickup(e){for(let t of this.pickups)if(t.active&&i(e,t)<t.radius+26)return t.active=!1,t.respawn=9,!0;return!1}pointAt(e){let t=(e%1+1)%1*this.points.length,n=Math.floor(t)%this.points.length,r=(n+1)%this.points.length,i=t-Math.floor(t);return{x:this.points[n].x+(this.points[r].x-this.points[n].x)*i,y:this.points[n].y+(this.points[r].y-this.points[n].y)*i}}offsetPoint(e,t){let n=this.pointAt(e),r=this.pointAt(e+.006),i=Math.atan2(r.y-n.y,r.x-n.x);return{x:n.x-Math.sin(i)*t,y:n.y+Math.cos(i)*t}}},v=class{root;constructor(e){this.root=e}render(e){if(e.state===`menu`){this.root.className=`overlay overlay--active`,this.root.innerHTML=`
2
+ <section class="panel hero">
3
+ <p class="eyebrow">Desert cup</p>
4
+ <h1>Offroad Nitro Racer</h1>
5
+ <p class="lede">Chunky arcade dirt racing with drifting, boosts, bumps, and ruthless AI trucks.</p>
6
+ <div class="controls-grid">
7
+ <span>Drive</span><strong>Arrows / WASD</strong>
8
+ <span>Nitro</span><strong>Space</strong>
9
+ <span>Pause</span><strong>Esc</strong>
10
+ <span>Restart</span><strong>R</strong>
11
+ </div>
12
+ <button class="primary">Press Enter to Race</button>
13
+ </section>
14
+ `;return}if(e.state===`paused`){this.root.className=`overlay overlay--active`,this.root.innerHTML=`
15
+ <section class="panel compact">
16
+ <p class="eyebrow">Pit stop</p>
17
+ <h2>Paused</h2>
18
+ <p>Press Esc to resume or R to restart.</p>
19
+ </section>
20
+ `;return}if(e.state===`finished`){let t=e.winner===e.player.name;this.root.className=`overlay overlay--active`,this.root.innerHTML=`
21
+ <section class="panel hero">
22
+ <p class="eyebrow">Race complete</p>
23
+ <h1>${t?`You Win!`:`You Lose`}</h1>
24
+ <p class="lede">${t?`Nitro glory across the desert.`:`${e.winner} reached the flag first.`}</p>
25
+ <div class="result-row"><span>Final position</span><strong>${b(e.player.rank)}</strong></div>
26
+ <div class="result-row"><span>Time</span><strong>${y(e.elapsed)}</strong></div>
27
+ <button class="primary">Press R to Restart</button>
28
+ </section>
29
+ `;return}this.root.className=`overlay`,this.root.innerHTML=`
30
+ <div class="hud hud--top">
31
+ <div class="hud-card"><span>Lap</span><strong>${Math.min(e.player.lap,e.track.lapsToWin)} / ${e.track.lapsToWin}</strong></div>
32
+ <div class="hud-card"><span>Position</span><strong>${b(e.player.rank)}</strong></div>
33
+ <div class="hud-card"><span>Speed</span><strong>${Math.round(e.player.speed*.42)} mph</strong></div>
34
+ <div class="hud-card"><span>Timer</span><strong>${y(e.elapsed)}</strong></div>
35
+ </div>
36
+ <div class="nitro-stack">${Array.from({length:e.player.maxNitro},(t,n)=>`<i class="${n<e.player.nitroCharges?`full`:``}"></i>`).join(``)}</div>
37
+ ${e.state===`countdown`?`<div class="countdown">${e.countdown>.25?Math.ceil(e.countdown):`GO!`}</div>`:``}
38
+ ${e.message?`<div class="toast">${e.message}</div>`:``}
39
+ `}};function y(e){return`${Math.floor(e/60)}:${Math.floor(e%60).toString().padStart(2,`0`)}.${Math.floor(e%1*10)}`}function b(e){return`${e}${e===1?`st`:e===2?`nd`:e===3?`rd`:`th`}`}var x=class{renderer;input=new m;audio=new d;ui;track=new _;effects=new p;cars=[];ai=[];player;camera={x:0,y:0,zoom:.72,shakeX:0,shakeY:0};state=`menu`;lastTime=0;raceTime=0;countdown=3.4;message=``;messageTimer=0;winner=``;countdownBeep=4;pausedFrom=`racing`;constructor(e,t){this.renderer=new h(e),this.ui=new v(t),this.resetRace()}start(){this.ui.render(this.uiData()),requestAnimationFrame(e=>this.loop(e))}loop(e){let t=Math.min((e-this.lastTime)/1e3||0,.033);this.lastTime=e,this.handleGlobalInput(),this.update(t),this.render(),this.input.endFrame(),requestAnimationFrame(e=>this.loop(e))}handleGlobalInput(){this.input.consume(`enter`)&&this.state===`menu`&&(this.audio.ensure(),this.beginCountdown()),this.input.consume(`escape`)&&(this.state===`racing`||this.state===`countdown`?(this.pausedFrom=this.state,this.state=`paused`):this.state===`paused`&&(this.state=this.pausedFrom)),this.input.consume(`r`)&&(this.resetRace(),this.beginCountdown())}beginCountdown(){this.state=`countdown`,this.countdown=3.4,this.countdownBeep=4,this.raceTime=0,this.audio.beep(`count`)}update(e){if(this.state===`menu`||this.state===`paused`||this.state===`finished`){this.updateCamera(e);return}if(this.track.update(e),this.effects.update(e),this.state===`countdown`){this.countdown-=e;let t=Math.ceil(this.countdown);t>0&&t<this.countdownBeep&&(this.countdownBeep=t,this.audio.beep(`count`)),this.countdown<=0&&(this.state=`racing`,this.audio.beep(`go`)),this.updateCamera(e);return}this.raceTime+=e;let t=1+(this.player.lap-1)*.12+this.raceTime/190,n=this.readPlayerInput(),r=this.player.nitroTimer>0;this.player.update(e,n,this.track,this.effects,this.raceTime,t),!r&&this.player.nitroTimer>0&&this.audio.beep(`boost`);for(let n=1;n<this.cars.length;n++){let r=this.cars[n];r.update(e,this.ai[n-1].update(r,this.track,this.player.rank,t),this.track,this.effects,this.raceTime,t)}this.resolveCollisions(),this.updateRanks(),this.updateMessages(e),this.updateFinishState(),this.updateCamera(e),this.audio.updateEngine(this.player.speed,n.throttle-n.brake,this.player.nitroTimer>0)}readPlayerInput(){return{throttle:+!!this.input.isDown(`arrowup`,`w`),brake:+!!this.input.isDown(`arrowdown`,`s`),steer:!!this.input.isDown(`arrowright`,`d`)-+!!this.input.isDown(`arrowleft`,`a`),nitro:this.input.isDown(`space`)}}resolveCollisions(){for(let e of this.cars){let t=e.collideObstacles(this.track,this.effects);e===this.player&&t===`hit`&&(this.audio.beep(`hit`),this.flashMessage(`Ouch! Watch the rocks.`)),e===this.player&&t===`bump`&&this.flashMessage(`Big air!`),e===this.player&&e.collectedPickup&&(this.audio.beep(`pickup`),this.flashMessage(`Nitro recharged!`))}for(let e=0;e<this.cars.length;e++)for(let t=e+1;t<this.cars.length;t++)this.cars[e].collideWith(this.cars[t],this.effects)}updateRanks(){[...this.cars].sort((e,t)=>t.totalProgress-e.totalProgress||t.speed-e.speed).forEach((e,t)=>{e.rank=t+1})}updateMessages(e){this.messageTimer>0&&(this.messageTimer-=e,this.messageTimer<=0&&(this.message=``))}updateFinishState(){let e=this.cars.filter(e=>e.finished).sort((e,t)=>e.finishTime-t.finishTime);e.length&&(this.winner=e[0].name,(this.player.finished||e.length>=2)&&(this.state=`finished`,this.audio.beep(`finish`)))}updateCamera(e){let r=this.player.x+this.player.vx*.55,i=this.player.y+this.player.vy*.55;this.camera.x=n(this.camera.x,r,1-.001**e),this.camera.y=n(this.camera.y,i,1-.001**e),this.camera.zoom=n(this.camera.zoom,t(.78-this.player.speed/950,.56,.78),.04),this.camera.shakeX=this.effects.shake?c(-this.effects.shake,this.effects.shake):0,this.camera.shakeY=this.effects.shake?c(-this.effects.shake,this.effects.shake):0}render(){this.renderer.render(this.track,this.cars,this.player,this.effects,this.camera),this.ui.render(this.uiData())}uiData(){return{state:this.state,countdown:this.countdown,player:this.player,track:this.track,elapsed:this.raceTime,message:this.message,winner:this.winner}}resetRace(){this.track=new _,this.effects=new p,this.raceTime=0,this.winner=``,this.message=``,this.cars=[new f({name:`You`,color:`#f43f5e`,accent:`#fecdd3`,isPlayer:!0},this.track.startPosition(0)),new f({name:`Baja Bob`,color:`#f59e0b`,accent:`#fde68a`},this.track.startPosition(1)),new f({name:`Dusty Diaz`,color:`#22c55e`,accent:`#bbf7d0`},this.track.startPosition(2)),new f({name:`Nitro Nell`,color:`#3b82f6`,accent:`#bfdbfe`},this.track.startPosition(3))],this.player=this.cars[0],this.ai=[new u(0),new u(1),new u(2)],this.camera.x=this.player.x,this.camera.y=this.player.y,this.state=this.state===`menu`?`menu`:`countdown`}flashMessage(e){this.message=e,this.messageTimer=1.6}},S=document.querySelector(`#app`);if(!S)throw Error(`Missing #app root`);S.innerHTML=`
40
+ <div class="shell">
41
+ <canvas id="game" aria-label="Offroad Nitro Racer game canvas"></canvas>
42
+ <div id="overlay" class="overlay"></div>
43
+ </div>
44
+ `;var C=document.querySelector(`#game`),w=document.querySelector(`#overlay`);if(!C||!w)throw Error(`Missing game canvas or overlay`);new x(C,w).start();
@@ -0,0 +1 @@
1
+ :root{color:#fff7ed;font-synthesis:none;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;--amber:#f6b23c;--cyan:#32f5ff;--red:#f43f5e;--panel:#1e120cd1;--panel-border:#fff0d238;background:#140c08;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}*{box-sizing:border-box}html,body,#app{width:100%;height:100%;margin:0;overflow:hidden}body{min-width:320px}button{font:inherit}.shell{isolation:isolate;background:radial-gradient(circle,#a7642c 0%,#3b2115 72%);width:100vw;height:100vh;position:relative}canvas{image-rendering:auto;width:100%;height:100%;position:absolute;inset:0}.overlay{z-index:2;pointer-events:none;position:absolute;inset:0}.overlay--active{pointer-events:auto;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);background:radial-gradient(circle,#ffaa461f,#0000 38%),linear-gradient(135deg,#0d080585,#0d0805c7);place-items:center;padding:24px;display:grid}.panel{border:2px solid var(--panel-border);background:var(--panel);text-align:center;border-radius:28px;width:min(620px,94vw);padding:28px;box-shadow:0 30px 90px #00000073,inset 0 1px #ffffff2e}.panel.hero{transform:rotate(-1deg)}.panel.compact{width:min(420px,92vw)}.eyebrow{color:var(--cyan);letter-spacing:.22em;text-transform:uppercase;margin:0 0 10px;font-size:12px;font-weight:900}h1,h2{text-transform:uppercase;text-shadow:0 5px #5a230f,0 12px 30px #0000008c;margin:0;line-height:.9}h1{font-size:clamp(48px,9vw,92px)}h2{font-size:clamp(38px,7vw,64px)}.lede,.panel p:not(.eyebrow){color:#ffe7c5;font-size:16px;line-height:1.6}.controls-grid{text-align:left;grid-template-columns:1fr 1.2fr;gap:10px 16px;max-width:390px;margin:24px auto;display:grid}.controls-grid span,.result-row span{color:#ffd8a3;letter-spacing:.12em;text-transform:uppercase;font-size:12px;font-weight:800}.controls-grid strong,.result-row strong{color:#fff;font-family:JetBrains Mono,ui-monospace,SFMono-Regular,Menlo,monospace}.primary{color:#241006;background:linear-gradient(180deg, #ffe08a, var(--amber));letter-spacing:.06em;text-transform:uppercase;border:0;border-radius:999px;justify-content:center;align-items:center;min-height:48px;padding:0 22px;font-weight:1000;display:inline-flex;box-shadow:0 7px #8b4314,0 18px 30px #0000004d}.result-row{background:#0003;border:1px solid #ffffff24;border-radius:16px;justify-content:space-between;gap:20px;max-width:340px;margin:16px auto;padding:14px 16px;display:flex}.hud{justify-content:center;align-items:center;gap:10px;display:flex;position:absolute;left:18px;right:18px}.hud--top{top:16px}.hud-card{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background:#150c08ad;border:1px solid #ffebcc3d;border-radius:16px;min-width:112px;padding:10px 12px;box-shadow:0 8px 25px #00000038}.hud-card span{color:#ffd8a3;letter-spacing:.14em;text-transform:uppercase;font-size:10px;font-weight:900;display:block}.hud-card strong{margin-top:2px;font-family:JetBrains Mono,ui-monospace,SFMono-Regular,Menlo,monospace;font-size:clamp(16px,3vw,22px);display:block}.nitro-stack{background:#0e0a08ad;border:1px solid #ffffff29;border-radius:999px;gap:8px;padding:10px;display:flex;position:absolute;bottom:22px;right:22px}.nitro-stack i{background:#ffffff1f;border:2px solid #ffffff59;border-radius:999px;width:48px;height:16px}.nitro-stack i.full{background:linear-gradient(90deg, #0891b2, var(--cyan));border-color:#a5f3fc;box-shadow:0 0 18px #32f5ffbf}.countdown{color:#fff;text-shadow:0 10px #6c2c10,0 0 60px #ffb543cc;place-items:center;font-size:clamp(84px,20vw,210px);font-weight:1000;animation:.45s both pop;display:grid;position:absolute;inset:0}.toast{color:#261208;text-transform:uppercase;background:#ffe08a;border-radius:999px;padding:12px 18px;font-weight:1000;position:absolute;bottom:34px;left:50%;transform:translate(-50%);box-shadow:0 8px #8b4314}@keyframes pop{0%{opacity:0;transform:scale(.7)rotate(-4deg)}to{opacity:1;transform:scale(1)rotate(0)}}@media (width<=720px){.hud{flex-wrap:wrap;justify-content:flex-start;left:8px;right:8px}.hud-card{min-width:calc(50% - 6px);padding:8px 10px}.nitro-stack i{width:34px}}
@@ -0,0 +1,23 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Offroad Nitro Racer</title>
7
+
8
+ <script type="importmap">
9
+ {
10
+ "imports": {
11
+ "/src/": "/api/preview/bff706c0/proxy/src/",
12
+ "/@vite/client": "/api/preview/bff706c0/proxy/@vite/client",
13
+ "/node_modules/": "/api/preview/bff706c0/proxy/node_modules/"
14
+ }
15
+ }
16
+ </script>
17
+ <script type="module" crossorigin src="./assets/index-CuvdKK6U.js"></script>
18
+ <link rel="stylesheet" crossorigin href="./assets/index-DvHn2Thu.css">
19
+ </head>
20
+ <body>
21
+ <div id="app"></div>
22
+ </body>
23
+ </html>
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Offroad Nitro Racer</title>
7
+ <script type="importmap">
8
+ {
9
+ "imports": {
10
+ "/src/": "/api/preview/bff706c0/proxy/src/",
11
+ "/@vite/client": "/api/preview/bff706c0/proxy/@vite/client",
12
+ "/node_modules/": "/api/preview/bff706c0/proxy/node_modules/"
13
+ }
14
+ }
15
+ </script>
16
+ </head>
17
+ <body>
18
+ <div id="app"></div>
19
+ <script type="module" src="./src/main.ts"></script>
20
+ </body>
21
+ </html>