ZENLLC commited on
Commit
dfd73c1
·
verified ·
1 Parent(s): b3a915b

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +224 -0
index.html CHANGED
@@ -160,3 +160,227 @@ for(let z=40; z<=1600; ){
160
  }
161
 
162
  // ---------- Paper glider ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  }
161
 
162
  // ---------- Paper glider ----------
163
+ function makeGlider(){
164
+ const g=new THREE.Group();
165
+ const paper=new THREE.MeshLambertMaterial({color:0xf4f6f8, side:THREE.DoubleSide});
166
+ const bodyS=new THREE.Shape(); bodyS.moveTo(0,0); bodyS.lineTo(-.22,.55); bodyS.lineTo(-.42,1.05); bodyS.lineTo(.42,1.05); bodyS.lineTo(.22,.55); bodyS.lineTo(0,0);
167
+ const body=new THREE.Mesh(new THREE.ExtrudeGeometry(bodyS,{depth:.03,bevelEnabled:false}),paper);
168
+ const wing=(d)=>{const s=new THREE.Shape(); s.moveTo(0,.2); s.lineTo(d*.32,.85); s.lineTo(d*1.0,.52); s.lineTo(0,.2); return new THREE.Mesh(new THREE.ExtrudeGeometry(s,{depth:.02,bevelEnabled:false}),paper);};
169
+ const L=wing(-1), Rg=wing(1); L.position.y=.01; Rg.position.y=.015; L.rotation.x=.18; Rg.rotation.x=.18;
170
+ const crease=new THREE.Line(new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0,.03,0),new THREE.Vector3(0,.03,1.05)]),
171
+ new THREE.LineBasicMaterial({color:0xdedede}));
172
+ g.add(body,L,Rg,crease); g.rotation.order="ZXY"; g.rotation.x=-Math.PI/2; return g;
173
+ }
174
+ const glider=makeGlider(); scene.add(glider);
175
+
176
+ // ---------- State & physics ----------
177
+ let state='aim'; // aim | fly | end
178
+ let power=0, maxPower=16, charging=false;
179
+ let stam=100, maxStam=100, combo=1, comboTimer=0, comboWindow=2.6;
180
+ let dist=0, score=0, best= Number(localStorage.getItem('zen_sky_best')||0);
181
+ const vel=new THREE.Vector3(0,0,0); const forward={cur:0};
182
+ const gravity=5.2, drag=.013;
183
+ let pitch=0, bank=0;
184
+ let slowMo=1.0; // 1 normal, <1 in bubbles
185
+ let showBubbles=false;
186
+
187
+ // UI refs
188
+ const $=id=>document.getElementById(id);
189
+ const elDist=$('dist'), elScore=$('score'), elPow=$('pBar'), elStam=$('sBar'), center=$('center');
190
+ const headline=$('headline'), details=$('details'), badges=$('badges');
191
+ $('btnRestart').onclick=()=>reset(); $('btnScreenshot').onclick=()=>photo();
192
+
193
+ function setBar(el,v){ el.style.width = `${THREE.MathUtils.clamp(v,0,100)}%`; }
194
+
195
+ // Controls
196
+ let key={L:false,R:false,U:false,D:false,B:false}, tL=false,tR=false;
197
+ addEventListener('keydown',e=>{
198
+ if(e.code==='Space'){ if(state==='aim'){charging=true;e.preventDefault();} else if(state==='end'){reset();}}
199
+ if(e.code==='ArrowLeft') key.L=true;
200
+ if(e.code==='ArrowRight') key.R=true;
201
+ if(e.code==='ArrowUp') key.U=true;
202
+ if(e.code==='ArrowDown') key.D=true;
203
+ if(e.code==='ShiftLeft'||e.code==='ShiftRight') key.B=true;
204
+ if(e.code==='KeyT') showBubbles = !showBubbles;
205
+ if(e.code==='KeyP') photo();
206
+ if(e.code==='Escape') reset();
207
+ if(e.code==='KeyR'){ // reroll seed
208
+ const newSeed=Math.floor(Math.random()*1e9).toString(36);
209
+ location.search='?seed='+newSeed;
210
+ }
211
+ });
212
+ addEventListener('keyup',e=>{
213
+ if(e.code==='Space' && state==='aim'){ launch(); e.preventDefault(); }
214
+ if(e.code==='ArrowLeft') key.L=false;
215
+ if(e.code==='ArrowRight') key.R=false;
216
+ if(e.code==='ArrowUp') key.U=false;
217
+ if(e.code==='ArrowDown') key.D=false;
218
+ if(e.code==='ShiftLeft'||e.code==='ShiftRight') key.B=false;
219
+ });
220
+ $('touchL').addEventListener('touchstart',()=>tL=true,{passive:true});
221
+ $('touchL').addEventListener('touchend',()=>tL=false,{passive:true});
222
+ $('touchR').addEventListener('touchstart',()=>tR=true,{passive:true});
223
+ $('touchR').addEventListener('touchend',()=>tR=false,{passive:true});
224
+ addEventListener('mousedown',()=>{ if(state==='aim')charging=true; });
225
+ addEventListener('mouseup',()=>{ if(state==='aim'&&charging)launch(); });
226
+
227
+ function reset(){
228
+ state='aim'; power=0; charging=false; stam=maxStam; combo=1; comboTimer=0; dist=0; score=0; forward.cur=0; vel.set(0,0,0); pitch=0; bank=0; slowMo=1.0;
229
+ glider.position.set(0, 2.5, 0); glider.rotation.set(-Math.PI/2,0,0);
230
+ camera.position.set(0, glider.position.y+4, glider.position.z-10);
231
+ center.style.display='none'; badges.innerHTML='';
232
+ }
233
+ function launch(){
234
+ forward.cur = THREE.MathUtils.mapLinear(power,0,maxPower,9,28);
235
+ vel.y = Math.sin(Math.PI/4)*(power*.68);
236
+ state='fly'; power=0; charging=false;
237
+ }
238
+ reset();
239
+
240
+ // ---------- Helpers ----------
241
+ const clock=new THREE.Clock();
242
+ function photo(){
243
+ const was= center.style.display;
244
+ center.style.display='none';
245
+ const d=renderer.domElement.toDataURL('image/png');
246
+ const a=document.createElement('a'); a.href=d; a.download=`zen-skylines-${Date.now()}.png`; a.click();
247
+ center.style.display=was;
248
+ }
249
+ function endRun(reason, earned=[]){
250
+ state='end';
251
+ if(score>best){ best=score; localStorage.setItem('zen_sky_best', String(Math.floor(best))); }
252
+ headline.textContent = reason||'Run Ended';
253
+ details.textContent = `Distance: ${dist.toFixed(1)} m • Score: ${Math.floor(score)} • Combo ×${combo} • Best ${Math.floor(best)}`;
254
+ badges.innerHTML = earned.map(t=>`<span class="badge">${t}</span>`).join(' ');
255
+ center.style.display='grid';
256
+ }
257
+
258
+ // ---------- Gameplay loop ----------
259
+ function animate(){
260
+ requestAnimationFrame(animate);
261
+ const rawDt = Math.min(.033, clock.getDelta());
262
+ const dt = rawDt * slowMo;
263
+
264
+ // sky parallax
265
+ skyTex.rotation = Math.min(1, dist/1600) * Math.PI * .42; skyTex.needsUpdate=true;
266
+
267
+ // UI
268
+ setBar(elPow, state==='aim' ? (power/maxPower)*100 : 0);
269
+ setBar(elStam, (stam/maxStam)*100);
270
+ elDist.textContent = `Distance: ${dist.toFixed(1)} m`;
271
+ elScore.textContent = `Score: ${Math.floor(score)} | Combo ×${combo} | Best ${Math.floor(best)}`;
272
+
273
+ // aim charging
274
+ if(state==='aim' && charging){ power=Math.min(maxPower, power + 26*rawDt); }
275
+
276
+ if(state==='fly'){
277
+ // flow-field wind force at glider
278
+ const f = flowVector(glider.position.x, glider.position.y, glider.position.z, performance.now()/1000);
279
+ // steering
280
+ const steerL = key.L||tL, steerR = key.R||tR;
281
+ const bankT = (steerL?-1:0)+(steerR?1:0); bank = THREE.MathUtils.lerp(bank, bankT*Math.PI/5, .1);
282
+ const pitchT = (key.U?.35:0) + (key.D?-.35:0); pitch = THREE.MathUtils.lerp(pitch, pitchT, .12);
283
+
284
+ // forward speed & drag
285
+ forward.cur *= (1 - drag);
286
+ if((key.B||key.D) && stam>0){ forward.cur += 8*dt; vel.y += 5.5*dt; stam -= 18*dt; } else { stam += 4*dt; }
287
+ stam = THREE.MathUtils.clamp(stam, 0, maxStam);
288
+
289
+ // lift vs gravity
290
+ const lift=(Math.sin(pitch)*(forward.cur+6))*0.95 + f.y*0.8;
291
+ vel.y += (lift - gravity) * dt;
292
+
293
+ // lateral control + wind push
294
+ const lateral = (steerR?-1:0)+(steerL?1:0);
295
+ glider.position.x += (lateral*3.6 + f.x*0.9) * dt;
296
+
297
+ // forward + wind bias
298
+ glider.position.z += (forward.cur + 10 + f.z*1.3) * dt;
299
+
300
+ // vertical integrate
301
+ glider.position.y += vel.y * dt;
302
+ // clamp vertical play space
303
+ if(glider.position.y<0.5){ glider.position.y=0.5; vel.y=Math.max(0,vel.y); }
304
+ if(glider.position.y>18){ glider.position.y=18; vel.y=Math.min(0,vel.y); }
305
+
306
+ // orientation
307
+ glider.rotation.x = -Math.PI/2 + (-pitch*.6);
308
+ glider.rotation.z = bank*.8;
309
+
310
+ // move flow ribbons
311
+ for(const rb of ribbons){
312
+ rb.position.y += Math.sin((performance.now()+rb.position.z*40)/7000)*0.02;
313
+ rb.position.x += Math.sin((performance.now()+rb.position.z*55)/6000)*0.02;
314
+ }
315
+
316
+ // walls motion & collisions (graze bonus if within 0.6..1.2 of surface)
317
+ let earned=[];
318
+ for(const w of walls){
319
+ w.position.x += w.userData.vx*dt;
320
+ w.position.y += w.userData.vy*dt;
321
+ if(w.position.x<-12||w.position.x>12) w.userData.vx*=-1;
322
+ if(w.position.y<0.5||w.position.y>8) w.userData.vy*=-1;
323
+
324
+ // AABB approx
325
+ const dx=Math.abs(glider.position.x - w.position.x);
326
+ const dy=Math.abs(glider.position.y - w.position.y);
327
+ const dz=Math.abs(glider.position.z - w.position.z);
328
+ const hx=w.geometry.parameters.width/2, hy=w.geometry.parameters.height/2, hz=0.7;
329
+ if(dx<hx && dy<hy && dz<hz){
330
+ return endRun('Storm wall impact', ['Risk-taker']);
331
+ }
332
+ // graze
333
+ if(dz<hz*1.25){
334
+ const gx = Math.abs(dx-hx), gy=Math.abs(dy-hy);
335
+ if((gx<1.0||gy<1.0) && rand()<0.02){ score += 6*combo; earned.push('Graze +6'); }
336
+ }
337
+ }
338
+
339
+ // rings
340
+ for(let i=rings.length-1;i>=0;i--){
341
+ const t=rings[i]; const d=glider.position.distanceTo(t.position);
342
+ if(d<1.55){ // collect
343
+ scene.remove(t); rings.splice(i,1);
344
+ stam = Math.min(maxStam, stam+34); combo = Math.min(12, combo+1); comboTimer = comboWindow; score += 160*combo;
345
+ earned.push('Ring +160×'+combo);
346
+ } else {
347
+ t.rotation.x += .6*dt; t.rotation.z += .4*dt; const s=1+.08*Math.sin(performance.now()/260+i); t.scale.set(s,s,s);
348
+ }
349
+ }
350
+
351
+ // slow-time bubbles (affect slowMo and scoring drip)
352
+ let inBubble=false;
353
+ for(const b of bubbles){
354
+ const d=glider.position.distanceTo(b.position);
355
+ if(d < b.userData.r){ inBubble=true; score += 4*combo*dt; }
356
+ }
357
+ slowMo = THREE.MathUtils.lerp(slowMo, inBubble ? 0.6 : 1.0, 0.15);
358
+
359
+ // combo decay
360
+ comboTimer -= dt; if(comboTimer<=0 && combo>1){ combo=Math.max(1, combo-1); comboTimer=.35; }
361
+
362
+ // progress + score drip
363
+ const dz=(forward.cur + 10 + f.z*1.3) * dt;
364
+ dist += dz; score += dz*(.9*combo);
365
+
366
+ // out-of-corridor fail
367
+ if(Math.abs(glider.position.x)>14) return endRun('You left the jetstream corridor', []);
368
+ if(glider.position.z>1650) return endRun('Finish line reached!', ['Course Clear']);
369
+ }
370
+
371
+ // camera chase
372
+ const camTarget=new THREE.Vector3(glider.position.x, glider.position.y+3.4, glider.position.z-10.5);
373
+ camera.position.lerp(camTarget,.12); camera.lookAt(glider.position);
374
+
375
+ // bubble view toggle visuals
376
+ for(const b of bubbles){ b.material.opacity = showBubbles? .28 : .18; }
377
+
378
+ renderer.render(scene,camera);
379
+ }
380
+ animate();
381
+
382
+ // resize
383
+ addEventListener('resize',()=>{ camera.aspect=innerWidth/innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth,innerHeight); });
384
+ </script>
385
+ </body>
386
+ </html>