Hero Pilot
by StealthWolf751035 lines41.0 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>Hero Pilot</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Nunito:wght@700;800&display=swap');
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;}
html,body{width:100%;height:100%;overflow:hidden;background:#1a1a2e;touch-action:none;user-select:none;}
canvas{position:fixed;top:0;left:0;}
/* OVERLAYS */
.ov{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:400;background:rgba(0,0,0,0.6);padding:12px;}
.ov.hide{display:none!important;}
.card{background:linear-gradient(135deg,#667eea,#764ba2);border-radius:24px;padding:28px 32px;text-align:center;color:#fff;max-width:460px;width:100%;box-shadow:0 20px 60px rgba(0,0,0,0.6);border:3px solid rgba(255,255,255,0.2);max-height:92vh;overflow-y:auto;}
.card.ora{background:linear-gradient(135deg,#f39c12,#d35400);}
.card.grn{background:linear-gradient(135deg,#27ae60,#1e8449);}
.card h1{font-family:โFredoka Oneโ,cursive;font-size:clamp(24px,6vw,42px);margin-bottom:8px;text-shadow:2px 2px 0 rgba(0,0,0,0.25);}
.card h2{font-family:โFredoka Oneโ,cursive;font-size:clamp(18px,4.5vw,28px);margin-bottom:10px;}
.card p{font-size:clamp(12px,2.8vw,15px);line-height:1.55;margin-bottom:12px;opacity:.92;}
.btn{display:inline-block;margin:4px;background:#FFD700;color:#333;border:none;border-radius:50px;padding:clamp(9px,2vw,13px) clamp(18px,4vw,30px);font-family:โFredoka Oneโ,cursive;font-size:clamp(14px,3.5vw,20px);cursor:pointer;box-shadow:0 5px 0 #b8960c;transition:transform .1s,box-shadow .1s;}
.btn:active{transform:translateY(4px);box-shadow:0 1px 0 #b8960c;}
.btn.g{background:#2ecc71;box-shadow:0 5px 0 #1a8a4a;color:#fff;}
.btn.b{background:#3498db;box-shadow:0 5px 0 #1a6fa0;color:#fff;}
/* HUD */
#hud{position:fixed;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:flex-start;padding:env(safe-area-inset-top,6px) 10px 0;z-index:200;pointer-events:none;}
.hb{background:rgba(255,255,255,0.93);border-radius:14px;padding:5px 11px;font-family:โFredoka Oneโ,cursive;font-size:clamp(11px,2.8vw,16px);box-shadow:0 2px 8px rgba(0,0,0,0.2);margin-top:6px;}
.hb.mo{color:#2d8a4e;}.hb.lv{color:#8e44ad;text-align:center;}.hb.pa{color:#2980b9;}
#savBar{position:fixed;top:clamp(46px,9vw,66px);left:50%;transform:translateX(-50%);background:rgba(255,255,255,0.93);border-radius:14px;padding:4px 12px;display:flex;align-items:center;gap:6px;font-family:โFredoka Oneโ,cursive;font-size:clamp(10px,2.4vw,13px);color:#e67e22;z-index:200;pointer-events:none;box-shadow:0 2px 8px rgba(0,0,0,0.2);white-space:nowrap;}
.bOut{width:clamp(70px,18vw,130px);height:11px;background:#f0e0c8;border-radius:6px;overflow:hidden;}
.bIn{height:100%;background:linear-gradient(90deg,#f39c12,#e74c3c);border-radius:6px;transition:width .4s;}
/* SPEECH */
#speech{position:fixed;top:clamp(82px,16vw,110px);left:50%;transform:translateX(-50%);background:#fff;border-radius:18px;padding:8px 16px;font-family:โFredoka Oneโ,cursive;font-size:clamp(12px,2.8vw,16px);color:#333;max-width:min(350px,88vw);text-align:center;box-shadow:0 4px 18px rgba(0,0,0,0.3);z-index:200;pointer-events:none;transition:opacity .35s;width:max-content;}
#speech.hide{opacity:0;}
/* COMBO */
#combo{position:fixed;top:40%;left:50%;transform:translate(-50%,-50%);font-family:โFredoka Oneโ,cursive;font-size:clamp(26px,7vw,50px);color:#FFD700;text-shadow:0 0 20px rgba(255,200,0,0.9),3px 3px 0 rgba(0,0,0,0.3);z-index:300;pointer-events:none;text-align:center;transition:opacity .3s;}
#combo.hide{opacity:0;}
/* CONTROLLER */
#ctrl{position:fixed;bottom:0;left:0;right:0;height:clamp(140px,32vw,190px);display:flex;align-items:center;justify-content:space-between;z-index:200;padding:0 clamp(10px,3vw,20px) env(safe-area-inset-bottom,10px);}
/* JOYSTICK */
#joyWrap{position:relative;width:clamp(110px,28vw,160px);height:clamp(110px,28vw,160px);}
#joyBase{position:absolute;inset:0;border-radius:50%;background:rgba(255,255,255,0.13);border:2px solid rgba(255,255,255,0.38);backdrop-filter:blur(5px);}
#joyKnob{position:absolute;border-radius:50%;background:radial-gradient(circle at 35% 35%,rgba(255,255,255,0.92),rgba(200,200,200,0.7));box-shadow:0 3px 12px rgba(0,0,0,0.35);}
#joyLabel{position:absolute;bottom:-22px;left:50%;transform:translateX(-50%);font-family:โFredoka Oneโ,cursive;font-size:11px;color:rgba(255,255,255,0.55);white-space:nowrap;}
/* ACTION BUTTON */
#actBtn{width:clamp(88px,22vw,125px);height:clamp(88px,22vw,125px);border-radius:50%;background:radial-gradient(circle at 38% 32%,#FFE566,#FFB700);border:3px solid rgba(255,255,255,0.65);display:flex;flex-direction:column;align-items:center;justify-content:center;font-family:โFredoka Oneโ,cursive;font-size:clamp(9px,2.2vw,13px);color:#333;box-shadow:0 6px 0 rgba(0,0,0,0.28),0 8px 20px rgba(0,0,0,0.3);cursor:pointer;line-height:1.25;text-align:center;transition:transform .08s,box-shadow .08s;}
#actBtn.hit{transform:scale(0.9) translateY(3px);box-shadow:0 2px 0 rgba(0,0,0,0.28);}
#actIcon{font-size:clamp(20px,5.5vw,30px);display:block;}
/* CHAR SELECT */
.cp{display:inline-flex;align-items:center;justify-content:center;width:clamp(46px,11vw,62px);height:clamp(46px,11vw,62px);margin:3px;border-radius:50%;border:3px solid rgba(255,255,255,0.28);cursor:pointer;font-size:clamp(22px,5.5vw,30px);transition:transform .15s,border-color .15s;background:rgba(255,255,255,0.1);}
.cp.sel{border-color:#FFD700;transform:scale(1.18);background:rgba(255,215,0,0.22);}
</style>
</head>
<body>
<canvas id="c"></canvas>
<!-- HUD -->
<div id="hud">
<div class="hb mo">๐ฐ$<span id="hMoney">0</span></div>
<div class="hb lv"><div id="hLevel" style="font-size:clamp(13px,3vw,18px)">Level 1</div><div id="hLoc" style="font-size:clamp(9px,2vw,11px);color:#888">๐</div></div>
<div class="hb pa">๐ง<span id="hPass">0</span>/<span id="hTotal">0</span></div>
</div>
<div id="savBar">๐ฆ<div class="bOut"><div class="bIn" id="savFill" style="width:0%"></div></div><span id="hSav">$0</span></div>
<div id="speech" class="hide"></div>
<div id="combo" class="hide"></div>
<!-- CONTROLLER -->
<div id="ctrl">
<div id="joyWrap">
<div id="joyBase"></div>
<div id="joyKnob"></div>
<div id="joyLabel">MOVE</div>
</div>
<div id="actBtn">
<span id="actIcon">๐ค</span>
PICK UP /<br>DROP OFF
</div>
</div>
<!-- START -->
<div class="ov" id="scrStart">
<div class="card">
<h1>๐โจ Hero Pilot!</h1>
<p>Fly your magical <strong>winged car</strong> around the world! Pick up passengers, help communities, and learn to <strong>save money</strong> for big rewards!</p>
<p><strong>Choose your pilot:</strong></p>
<div id="charGrid" style="margin-bottom:14px"></div>
<p style="opacity:.7;font-size:clamp(11px,2.5vw,13px);margin-bottom:14px">Use the joystick โ โ to fly โข Gold button to pick up & drop off</p>
<button class="btn g" id="startBtn">๐ Let's Fly!</button>
</div>
</div>
<!-- LEVEL COMPLETE -->
<div class="ov hide" id="scrLevel">
<div class="card ora">
<h2 id="lcTitle">๐ Level Complete!</h2>
<p id="lcText"></p>
<div id="lcSave">
<p style="color:#FFD700;font-family:'Fredoka One',cursive;font-size:clamp(13px,3vw,18px)">๐ก Delay Gratification!</p>
<p>You earned <strong id="lcAmt"></strong>! What will you do?</p>
<button class="btn" id="btnSpend">๐ฆ Spend Now!</button>
<button class="btn g" id="btnSave">๐ฆ Save it! (+50%!)</button>
</div>
<div id="lcNext"><button class="btn b" id="btnNext">Next Level โก๏ธ</button></div>
</div>
</div>
<!-- WIN -->
<div class="ov hide" id="scrWin">
<div class="card grn">
<h1>๐ YOU WIN!</h1>
<p id="winText"></p>
<button class="btn" id="btnReplay">Play Again! ๐</button>
</div>
</div>
<script>
(function() {
'use strict';
// โโ PILOTS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
var PILOTS = [
{ emoji:'๐ฆ๐ป', hat:'๐งข', carCol:'#FF6B35', wingCol:'#FFD700', name:'Alex' },
{ emoji:'๐ง๐ฝ', hat:'๐', carCol:'#9B59B6', wingCol:'#F1C40F', name:'Maya' },
{ emoji:'๐ง๐ฟ', hat:'โญ', carCol:'#27AE60', wingCol:'#F39C12', name:'Sam' },
{ emoji:'๐ฉ๐ผ', hat:'๐ธ', carCol:'#E74C3C', wingCol:'#fff', name:'Zara' },
{ emoji:'๐จ๐พ', hat:'๐ฉ', carCol:'#2980B9', wingCol:'#1ABC9C', name:'Dre' },
{ emoji:'๐ฑ๐ปโโ๏ธ',hat:'๐', carCol:'#D81B60', wingCol:'#FFD700', name:'Kai' },
];
var pilotIdx = 0;
// Build character grid
var charGrid = document.getElementById('charGrid');
PILOTS.forEach(function(p, i) {
var el = document.createElement('span');
el.className = 'cp' + (i === 0 ? ' sel' : '');
el.textContent = p.emoji;
el.title = p.name;
el.addEventListener('click', function() {
document.querySelectorAll('.cp').forEach(function(e){ e.classList.remove('sel'); });
el.classList.add('sel');
pilotIdx = i;
});
charGrid.appendChild(el);
});
// โโ LEVELS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
var LEVELS = [
{
name:'Level 1', loc:'๐๏ธ Sunny City',
sky:['#87CEEB','#B0E0E6'], gTop:'#5D8A3C', gFill:'#4CAF50',
pax:[
{fx:.14, emoji:'๐ด', dest:'hospital', say:'Take me to the hospital!'},
{fx:.40, emoji:'๐ฉโ๐ซ', dest:'school', say:'I need to get to school!'},
{fx:.70, emoji:'๐ถ', dest:'park', say:'Wahh! I wanna go to the park!'},
],
dests:[
{fx:.25, type:'hospital', emoji:'๐ฅ', label:'Hospital', col:'#e74c3c'},
{fx:.53, type:'school', emoji:'๐ซ', label:'School', col:'#3498db'},
{fx:.84, type:'park', emoji:'๐ณ', label:'Park', col:'#2ecc71'},
],
wind:.04, numCoins:4,
tip:'Welcome! ๐ Use the joystick to fly. Get near a โ person and tap the gold button!'
},
{
name:'Level 2', loc:'๐๏ธ Mountain Valley',
sky:['#B0C4DE','#C8D8E8'], gTop:'#7B8B6F', gFill:'#8B7355',
pax:[
{fx:.09, emoji:'โท๏ธ', dest:'lodge', say:'Take me to the ski lodge!'},
{fx:.32, emoji:'๐งโ๐พ', dest:'market', say:'My veggies need the market!'},
{fx:.57, emoji:'๐งโโ๏ธ', dest:'clinic', say:'Emergency at the clinic!'},
{fx:.80, emoji:'๐ฆ', dest:'house', say:'Deliver me home!'},
],
dests:[
{fx:.20, type:'lodge', emoji:'๐ ', label:'Ski Lodge', col:'#8B4513'},
{fx:.43, type:'market', emoji:'๐', label:'Market', col:'#FF8C00'},
{fx:.66, type:'clinic', emoji:'โ๏ธ', label:'Clinic', col:'#DC143C'},
{fx:.88, type:'house', emoji:'๐ก', label:'House', col:'#9370DB'},
],
wind:.18, numCoins:6,
tip:'Mountain gusts! Push joystick UP to climb. Collect ๐ช coins for bonus cash!'
},
{
name:'Level 3', loc:'๐ Tropical Islands',
sky:['#00CED1','#40E0D0'], gTop:'#DEB887', gFill:'#F4A460',
pax:[
{fx:.08, emoji:'๐คฟ', dest:'reef', say:'To the coral reef!'},
{fx:.27, emoji:'๐ฉโ๐ฌ', dest:'lab', say:'Science samples for the lab!'},
{fx:.47, emoji:'๐งโ๐จ', dest:'gallery', say:'Art show today!'},
{fx:.67, emoji:'๐ต', dest:'home', say:'Take me home dear!'},
{fx:.87, emoji:'๐', dest:'beach', say:"Surf's up at the north beach!"},
],
dests:[
{fx:.17, type:'reef', emoji:'๐ ', label:'Coral Reef', col:'#FF6347'},
{fx:.35, type:'lab', emoji:'๐ฌ', label:'Lab', col:'#4169E1'},
{fx:.55, type:'gallery', emoji:'๐ผ๏ธ', label:'Gallery', col:'#DA70D6'},
{fx:.74, type:'home', emoji:'๐ ', label:'Home', col:'#32CD32'},
{fx:.92, type:'beach', emoji:'๐๏ธ', label:'Beach', col:'#FFD700'},
],
wind:-.13, numCoins:8,
tip:'Ocean breeze blows LEFT! Pick up all 5 passengers! ๐'
},
{
name:'Level 4', loc:'๐ Mega City Rush',
sky:['#FF8C69','#FFA07A'], gTop:'#696969', gFill:'#808080',
pax:[
{fx:.07, emoji:'๐จโ๐ผ', dest:'office', say:'Late for my meeting!'},
{fx:.24, emoji:'๐ฉโ๐ณ', dest:'restaurant', say:'Lunch rush at the restaurant!'},
{fx:.43, emoji:'๐งโ๐', dest:'station', say:'Back to the fire station!'},
{fx:.62, emoji:'๐ค', dest:'stage', say:'Concert starts in 5 minutes!'},
{fx:.82, emoji:'๐จโ๐ง', dest:'garage', say:'Cars need fixing!'},
],
dests:[
{fx:.15, type:'office', emoji:'๐ข', label:'Office', col:'#4682B4'},
{fx:.33, type:'restaurant', emoji:'๐ฝ๏ธ', label:'Restaurant', col:'#FF4500'},
{fx:.51, type:'station', emoji:'๐', label:'Fire Station', col:'#DC143C'},
{fx:.70, type:'stage', emoji:'๐ญ', label:'Stage', col:'#9400D3'},
{fx:.88, type:'garage', emoji:'๐ง', label:'Garage', col:'#A0522D'},
],
wind:.22, numCoins:10,
tip:'City air pockets! Stay steady and collect all the coins! ๐๏ธ'
},
{
name:'Level 5', loc:'๐ Around the World!',
sky:['#0d0d2b','#1a1a5e'], gTop:'#2F4F4F', gFill:'#3CB371',
pax:[
{fx:.07, emoji:'๐งโ๐', dest:'launch', say:'To the launch pad!'},
{fx:.22, emoji:'๐ธ', dest:'castle', say:'My castle awaits!'},
{fx:.37, emoji:'๐ง', dest:'tower', say:'Magic books at the tower!'},
{fx:.53, emoji:'๐ฆธ', dest:'hq', say:'Superhero HQ please!'},
{fx:.70, emoji:'๐บ', dest:'garden', say:'Rare flowers for the garden!'},
{fx:.87, emoji:'๐งโ๐', dest:'hall', say:'Graduation ceremony!'},
],
dests:[
{fx:.14, type:'launch', emoji:'๐', label:'Launch Pad', col:'#FF6347'},
{fx:.29, type:'castle', emoji:'๐ฐ', label:'Castle', col:'#9370DB'},
{fx:.45, type:'tower', emoji:'๐ผ', label:'Magic Tower', col:'#20B2AA'},
{fx:.61, type:'hq', emoji:'โก', label:'Hero HQ', col:'#FFD700'},
{fx:.77, type:'garden', emoji:'๐ธ', label:'Garden', col:'#FF69B4'},
{fx:.92, type:'hall', emoji:'๐', label:'Grad Hall', col:'#4169E1'},
],
wind:-.20, numCoins:12,
tip:'THE FINAL LEVEL! You are the ultimate Hero Pilot! ๐'
}
];
// โโ CANVAS & SIZING โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
var cv = document.getElementById('c');
var ctx = cv.getContext('2d');
var W = 0, H = 0, DPR = 1;
function resize() {
DPR = Math.min(window.devicePixelRatio || 1, 2.5);
W = window.innerWidth;
H = window.innerHeight;
cv.width = Math.round(W * DPR);
cv.height = Math.round(H * DPR);
cv.style.width = W + 'px';
cv.style.height = H + 'px';
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
}
resize();
window.addEventListener('resize', function() {
resize();
if (gs && gs.started) recalcLayout();
});
function ctrlTop() {
var el = document.getElementById('ctrl');
return el ? el.getBoundingClientRect().top : H - 160;
}
function sc() { return Math.min(W, H) / 420; } // global scale factor
// โโ GAME STATE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
var gs = { level:0, money:0, savings:0, bonus:false, started:false, streak:0, coins:0 };
// Car object
var car = { x:0, y:0, vx:0, vy:0, angle:0, w:72, h:34, pax:[] };
// Level dynamic data
var ld = null;
// Animation frame counter, timers
var fr = 0;
var spTimer = 0;
var comboTimer = 0;
var lvlDone = false;
var actPending = false;
// โโ JOYSTICK โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
var joyWrap = document.getElementById('joyWrap');
var joyKnob = document.getElementById('joyKnob');
var joy = { x:0, y:0, tid:-1 };
function updateKnob() {
var sz = joyWrap.offsetWidth;
var ksz = joyKnob.offsetWidth;
var maxR = sz * 0.36;
var kx = sz/2 + joy.x * maxR - ksz/2;
var ky = sz/2 + joy.y * maxR - ksz/2;
joyKnob.style.left = kx + 'px';
joyKnob.style.top = ky + 'px';
}
function setKnobSize() {
var sz = joyWrap.offsetWidth;
var ksz = Math.round(sz * 0.42);
joyKnob.style.width = ksz + 'px';
joyKnob.style.height = ksz + 'px';
joyKnob.style.left = (sz/2 - ksz/2) + 'px';
joyKnob.style.top = (sz/2 - ksz/2) + 'px';
}
function joyMove(clientX, clientY) {
var r = joyWrap.getBoundingClientRect();
var cx2 = r.left + r.width / 2;
var cy2 = r.top + r.height / 2;
var dx = clientX - cx2;
var dy = clientY - cy2;
var dist = Math.sqrt(dx*dx + dy*dy);
var maxD = r.width * 0.38;
if (dist > maxD) { dx = dx/dist*maxD; dy = dy/dist*maxD; }
joy.x = dx / maxD;
joy.y = dy / maxD;
updateKnob();
}
function joyReset() {
joy.x = 0; joy.y = 0; joy.tid = -1;
updateKnob();
}
joyWrap.addEventListener('touchstart', function(e) {
e.preventDefault();
var t = e.changedTouches[0];
joy.tid = t.identifier;
joyMove(t.clientX, t.clientY);
}, { passive: false });
joyWrap.addEventListener('touchmove', function(e) {
e.preventDefault();
for (var i = 0; i < e.changedTouches.length; i++) {
var t = e.changedTouches[i];
if (t.identifier === joy.tid) { joyMove(t.clientX, t.clientY); break; }
}
}, { passive: false });
joyWrap.addEventListener('touchend', function(e){ e.preventDefault(); joyReset(); }, { passive:false });
joyWrap.addEventListener('touchcancel', function(e){ e.preventDefault(); joyReset(); }, { passive:false });
joyWrap.addEventListener('mousedown', function(e){ joyMove(e.clientX, e.clientY); });
window.addEventListener( 'mousemove', function(e){ if (joy.tid !== -1 || e.buttons) joyMove(e.clientX, e.clientY); });
window.addEventListener( 'mouseup', function() { joyReset(); });
// โโ ACTION BUTTON โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
var actBtn = document.getElementById('actBtn');
var actIcon = document.getElementById('actIcon');
function triggerAction() {
actPending = true;
actBtn.classList.add('hit');
actIcon.style.transform = 'scale(1.3)';
setTimeout(function() {
actBtn.classList.remove('hit');
actIcon.style.transform = '';
}, 160);
}
actBtn.addEventListener('touchstart', function(e){ e.preventDefault(); triggerAction(); }, { passive:false });
actBtn.addEventListener('mousedown', function() { triggerAction(); });
// โโ KEYBOARD โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
var keys = {};
document.addEventListener('keydown', function(e) {
keys[e.key] = true;
if ([' ','ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].indexOf(e.key) >= 0) e.preventDefault();
if (e.key === ' ') triggerAction();
});
document.addEventListener('keyup', function(e) { keys[e.key] = false; });
function goU() { return joy.y < -0.2 || keys['ArrowUp'] || keys['w'] || keys['W']; }
function goD() { return joy.y > 0.2 || keys['ArrowDown'] || keys['s'] || keys['S']; }
function goL() { return joy.x < -0.2 || keys['ArrowLeft'] || keys['a'] || keys['A']; }
function goR() { return joy.x > 0.2 || keys['ArrowRight'] || keys['d'] || keys['D']; }
// โโ BUTTON WIRING โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
document.getElementById('startBtn').addEventListener('click', startGame);
document.getElementById('btnSpend').addEventListener('click', spendNow);
document.getElementById('btnSave').addEventListener('click', saveIt);
document.getElementById('btnNext').addEventListener('click', nextLevel);
document.getElementById('btnReplay').addEventListener('click', function(){ location.reload(); });
// โโ HELPERS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function clamp(v, mn, mx) { return v < mn ? mn : v > mx ? mx : v; }
function showEl(id) { document.getElementById(id).classList.remove('hide'); }
function hideEl(id) { document.getElementById(id).classList.add('hide'); }
function updateHUD() {
document.getElementById('hMoney').textContent = gs.money;
document.getElementById('hPass').textContent = ld.delivered;
document.getElementById('hTotal').textContent = ld.total;
document.getElementById('hSav').textContent = '$' + gs.savings;
document.getElementById('savFill').style.width = clamp((gs.savings / 700) * 100, 0, 100) + '%';
}
function say(txt, ms) {
ms = ms || 3000;
var el = document.getElementById('speech');
el.textContent = txt;
el.classList.remove('hide');
spTimer = ms;
}
function showCombo(txt) {
var el = document.getElementById('combo');
el.textContent = txt;
el.classList.remove('hide');
comboTimer = 1800;
}
// โโ LAYOUT RECALC โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function recalcLayout() {
var s = sc();
car.w = Math.round(72 * s);
car.h = Math.round(34 * s);
setKnobSize();
if (ld) ld.gY = ctrlTop() - 8;
}
// โโ GENERATE COINS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function genCoins(n, worldW, gY) {
var coins = [];
for (var i = 0; i < n; i++) {
coins.push({
wx: 80 + Math.random() * (worldW - 200),
wy: gY * 0.12 + Math.random() * gY * 0.5,
r: clamp(9 * sc(), 7, 16),
ph: Math.random() * Math.PI * 2,
collected: false
});
}
return coins;
}
// โโ LOAD LEVEL โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function loadLevel(idx) {
lvlDone = false;
actPending = false;
var lv = LEVELS[idx];
document.getElementById('hLevel').textContent = lv.name;
document.getElementById('hLoc').textContent = lv.loc;
recalcLayout();
var gY = ctrlTop() - 8;
var worldW = W * 2.6;
// Convert fractional positions to world pixels
function wx(f) { return f * worldW; }
var passengers = lv.pax.map(function(p, i) {
return { wx:wx(p.fx), wy:gY-32, emoji:p.emoji, dest:p.dest, say:p.say, id:i, picked:false, done:false };
});
var dests = lv.dests.map(function(d) {
return { wx:wx(d.fx), wy:gY, type:d.type, emoji:d.emoji, label:d.label, col:d.col };
});
var clouds = [];
for (var i = 0; i < 16; i++) {
clouds.push({ wx: Math.random()*worldW, wy: 40+Math.random()*(gY*0.5), r: clamp(30+Math.random()*55,28,90), spd: 0.3+Math.random()*0.35 });
}
var buildings = [];
var numB = 28;
for (var i = 0; i < numB; i++) {
buildings.push({ wx: i*(worldW/numB)+15, bw: clamp(22+Math.random()*55,22,76), bh: clamp(28+Math.random()*110,28,130), col: 'hsl('+Math.round(Math.random()*360)+',32%,'+Math.round(38+Math.random()*20)+'%)' });
}
var stars = [];
if (lv.sky[0].charAt(1) === '0' || lv.sky[0].charAt(1) === '1') {
for (var i = 0; i < 80; i++) {
stars.push({ wx: Math.random()*worldW, wy: Math.random()*(gY*0.7), r: Math.random()*1.8, ph: Math.random()*Math.PI*2 });
}
}
ld = {
worldW: worldW,
gY: gY,
scrollX: 0,
wind: lv.wind,
delivered: 0,
total: passengers.length,
pax: passengers,
dests: dests,
coins: genCoins(lv.numCoins, worldW, gY),
clouds: clouds,
buildings: buildings,
stars: stars,
parts: [] // particles
};
car.x = 80;
car.y = gY - car.h - 20;
car.vx = 0;
car.vy = 0;
car.angle = 0;
car.pax = [];
updateHUD();
say(lv.tip, 5000);
}
// โโ START GAME โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function startGame() {
hideEl('scrStart');
gs.started = true;
loadLevel(0);
requestAnimationFrame(loop);
}
// โโ PHYSICS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function physics() {
var s = sc();
var thrust = 0.40 * s;
var grav = 0.23 * s;
var drag = 0.974;
var maxV = 9 * s;
// Horizontal
var jx = clamp(joy.x, -1, 1);
var jy = clamp(joy.y, -1, 1);
if (goL()) jx = Math.min(jx, -0.65);
if (goR()) jx = Math.max(jx, 0.65);
if (goU()) jy = Math.min(jy, -0.65);
if (goD()) jy = Math.max(jy, 0.30);
if (jx < -0.1) {
car.vx -= thrust * Math.abs(jx);
car.angle = Math.max(car.angle - 0.04, -0.40);
} else if (jx > 0.1) {
car.vx += thrust * Math.abs(jx);
car.angle = Math.min(car.angle + 0.04, 0.40);
} else {
car.angle *= 0.87;
}
if (jy < -0.1) {
car.vy -= thrust * 1.1 * Math.abs(jy);
car.vx += Math.sin(car.angle) * thrust * 0.45;
}
if (jy > 0.1) { car.vy += thrust * 0.5 * Math.abs(jy); }
// Speed-based lift
var spd = Math.abs(car.vx);
if (spd > 1.5 * s) car.vy -= 0.15 * s * Math.min(spd / maxV, 1);
// Wind
car.vx += ld.wind * s;
// Gravity & drag
car.vy += grav;
car.vx *= drag;
car.vy *= drag;
car.vx = clamp(car.vx, -maxV, maxV);
car.vy = clamp(car.vy, -maxV * 1.2, maxV * 1.4);
car.x += car.vx;
car.y += car.vy;
car.x = clamp(car.x, 0, ld.worldW - car.w);
// Ground
var gnd = ld.gY - car.h / 2;
if (car.y >= gnd) { car.y = gnd; car.vy = 0; car.vx *= 0.80; }
if (car.y < 30) { car.y = 30; car.vy = Math.abs(car.vy) * 0.4; }
// Coin collection
var ccx = car.x + car.w / 2;
var ccy = car.y + car.h / 2;
ld.coins.forEach(function(coin) {
if (coin.collected) return;
var dx = ccx - coin.wx;
var dy = ccy - coin.wy;
if (Math.sqrt(dx*dx + dy*dy) < coin.r + car.h / 2 + 6) {
coin.collected = true;
var earn = 10 + (gs.bonus ? 5 : 0);
gs.money += earn;
gs.savings += earn;
gs.coins++;
burst(coin.wx, coin.wy, '#FFD700', 8);
updateHUD();
showCombo('๐ช +$' + earn + '!');
}
});
}
// โโ INTERACT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function interact() {
if (!actPending) return;
actPending = false;
var ccx = car.x + car.w / 2;
var ccy = car.y + car.h / 2;
var reach = car.h * 2.4;
// Try pickup first
if (car.pax.length < 4) {
for (var i = 0; i < ld.pax.length; i++) {
var p = ld.pax[i];
if (p.picked || p.done) continue;
var dx = (p.wx + 16) - ccx;
var dy = p.wy - ccy;
if (Math.sqrt(dx*dx + dy*dy) < reach + 28) {
p.picked = true;
car.pax.push(p);
gs.streak++;
say(p.emoji + ' ' + p.say);
burst(p.wx, p.wy, '#FFD700', 12);
if (gs.streak >= 3) showCombo('๐ฅ ' + gs.streak + ' pickups!');
return;
}
}
}
// Try dropoff
if (car.pax.length > 0) {
for (var i = 0; i < ld.dests.length; i++) {
var d = ld.dests[i];
var dx = (d.wx + 44) - ccx;
var dy = d.wy - ccy;
if (Math.sqrt(dx*dx + dy*dy) < reach + 45) {
var hits = car.pax.filter(function(p){ return p.dest === d.type; });
if (hits.length > 0) {
var earnPer = gs.bonus ? 45 : 30;
hits.forEach(function(p) {
p.done = true;
car.pax = car.pax.filter(function(pp){ return pp.id !== p.id; });
ld.delivered++;
gs.money += earnPer;
gs.savings += earnPer;
burst(d.wx + 44, d.wy - 40, d.col, 15);
});
updateHUD();
say('๐ Delivered! +$' + (earnPer * hits.length) + '! Community thanks you!');
if (gs.streak >= 2) showCombo('โจ STREAK x' + gs.streak + '!');
gs.streak = 0;
if (ld.delivered >= ld.total && !lvlDone) {
lvlDone = true;
setTimeout(showLvlDone, 1100);
}
} else {
say('๐บ๏ธ Wrong stop! Check where this passenger needs to go!');
gs.streak = 0;
}
return;
}
}
}
// Nothing nearby
if (car.pax.length === 0) say('Fly close to a โ person, then tap the gold button!');
else say('Fly to a glowing pad, then tap the gold button!');
gs.streak = 0;
}
// โโ PARTICLES โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function burst(x, y, col, n) {
n = n || 14;
for (var i = 0; i < n; i++) {
ld.parts.push({
x: x, y: y,
vx: (Math.random() - .5) * 9,
vy: (Math.random() - .5) * 9 - 2,
life: 1,
col: col,
sz: 4 + Math.random() * 9
});
}
}
function tickParts() {
ld.parts = ld.parts.filter(function(p){ return p.life > 0; });
ld.parts.forEach(function(p){ p.x+=p.vx; p.y+=p.vy; p.vy+=0.12; p.life-=0.024; });
}
// โโ LEVEL DONE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function showLvlDone() {
document.getElementById('lcNext').style.display = 'none';
document.getElementById('lcSave').style.display = 'none';
if (gs.level < 4) {
document.getElementById('lcTitle').textContent = '๐ Level Complete!';
document.getElementById('lcText').textContent = 'You delivered all ' + ld.total + ' passengers in ' + LEVELS[gs.level].loc + '! ๐ช Coins: ' + gs.coins + ' Your community loves you! ๐';
document.getElementById('lcAmt').textContent = '$' + gs.savings;
document.getElementById('lcSave').style.display = 'block';
} else {
document.getElementById('lcTitle').textContent = '๐ You Beat the Game!';
document.getElementById('lcText').textContent = 'You are the ultimate Hero Pilot! ๐';
document.getElementById('lcNext').style.display = 'block';
}
showEl('scrLevel');
}
function spendNow() { gs.bonus = false; hideEl('scrLevel'); advance(); }
function saveIt() { gs.bonus = true; say('Patience pays! +50% bonus next level! ๐', 4000); hideEl('scrLevel'); advance(); }
function nextLevel(){ hideEl('scrLevel'); advance(); }
function advance() {
if (gs.level >= 4) {
document.getElementById('winText').textContent = 'You helped everyone around the world! ๐ Total Saved: $' + gs.savings + ' | Coins: ๐ช' + gs.coins;
hideEl('scrLevel');
showEl('scrWin');
return;
}
gs.level++;
loadLevel(gs.level);
}
// โโ SCROLL โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function scrollUpdate() {
ld.scrollX = clamp(car.x - W / 2, 0, ld.worldW - W);
}
// โโ DRAW โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function draw() {
var lv = LEVELS[gs.level];
if (!lv || !ld) return;
var sx = ld.scrollX;
var gY = ld.gY;
var s = sc();
var pl = PILOTS[pilotIdx];
ctx.clearRect(0, 0, W, H);
// Sky
var skyG = ctx.createLinearGradient(0, 0, 0, H);
skyG.addColorStop(0, lv.sky[0]);
skyG.addColorStop(1, lv.sky[1]);
ctx.fillStyle = skyG;
ctx.fillRect(0, 0, W, H);
// Stars (parallax 0.3)
ld.stars.forEach(function(st) {
var px = st.wx - sx * 0.3;
if (px < -4 || px > W + 4) return;
var t = 0.4 + 0.6 * Math.sin(fr * 0.045 + st.ph);
ctx.fillStyle = 'rgba(255,255,255,' + t.toFixed(2) + ')';
ctx.beginPath(); ctx.arc(px, st.wy, st.r, 0, Math.PI * 2); ctx.fill();
});
// Clouds (parallax 0.7)
ld.clouds.forEach(function(c) {
c.wx -= c.spd;
if (c.wx < -200) c.wx = ld.worldW + 100;
var px = c.wx - sx * 0.7;
if (px < -200 || px > W + 200) return;
ctx.fillStyle = 'rgba(255,255,255,0.82)';
ctx.beginPath();
ctx.arc(px, c.wy, c.r, 0, Math.PI*2);
ctx.arc(px + c.r*.57, c.wy - c.r*.2, c.r*.66, 0, Math.PI*2);
ctx.arc(px - c.r*.44, c.wy + c.r*.1, c.r*.54, 0, Math.PI*2);
ctx.fill();
});
// Buildings
ld.buildings.forEach(function(b) {
var bx = b.wx - sx;
if (bx < -90 || bx > W + 90) return;
ctx.fillStyle = b.col;
ctx.fillRect(bx, gY - b.bh, b.bw, b.bh);
ctx.fillStyle = 'rgba(255,255,180,0.45)';
for (var wy = gY - b.bh + 8; wy < gY - 8; wy += 13) {
for (var bwx = bx + 4; bwx < bx + b.bw - 4; bwx += 10) {
ctx.fillRect(bwx, wy, 5, 7);
}
}
});
// Ground
ctx.fillStyle = lv.gFill; ctx.fillRect(0, gY, W, H - gY);
ctx.fillStyle = lv.gTop; ctx.fillRect(0, gY, W, 6);
ctx.fillStyle = 'rgba(0,0,0,0.07)';
for (var gx = (-(sx % 55)); gx < W; gx += 55) ctx.fillRect(gx, gY, 2, H - gY);
// Destinations
ld.dests.forEach(function(d) {
var dx = d.wx - sx;
if (dx < -140 || dx > W + 140) return;
var pulse = 0.55 + 0.45 * Math.sin(fr * 0.07);
var padW = 80 * s;
var centerX = dx + 44;
ctx.shadowColor = d.col; ctx.shadowBlur = 12 * pulse;
ctx.fillStyle = d.col + 'bb';
ctx.fillRect(centerX - padW/2, gY - 9, padW, 9);
ctx.shadowBlur = 0;
ctx.font = Math.round(22 * s) + 'px serif';
ctx.textAlign = 'center';
ctx.fillText(d.emoji, centerX, gY - 12);
var lblSz = Math.round(12 * s);
ctx.font = 'bold ' + lblSz + 'px Nunito,sans-serif';
ctx.lineWidth = 3; ctx.strokeStyle = d.col;
ctx.strokeText(d.label, centerX, gY - 50 * s);
ctx.fillStyle = '#fff';
ctx.fillText(d.label, centerX, gY - 50 * s);
ctx.lineWidth = 1; ctx.textAlign = 'left';
ctx.strokeStyle = d.col; ctx.lineWidth = 2 * pulse; ctx.globalAlpha = 0.4 * pulse;
ctx.beginPath(); ctx.arc(centerX, gY - 66 * s, 18 * s + pulse * 7, 0, Math.PI*2); ctx.stroke();
ctx.globalAlpha = 1; ctx.lineWidth = 1;
});
// Passengers waiting
ld.pax.forEach(function(p) {
if (p.picked || p.done) return;
var px = p.wx - sx;
if (px < -80 || px > W + 80) return;
var bob = Math.sin(fr * 0.065 + p.id) * 3;
var bubR = 15 * s;
ctx.fillStyle = 'rgba(255,255,255,0.94)';
ctx.beginPath(); ctx.arc(px + 14, p.wy - 33*s + bob, bubR, 0, Math.PI*2); ctx.fill();
ctx.fillStyle = '#e74c3c';
ctx.font = 'bold ' + Math.round(14*s) + 'px Nunito,sans-serif';
ctx.textAlign = 'center';
ctx.fillText('!', px + 14, p.wy - 28*s + bob);
ctx.textAlign = 'left';
ctx.font = Math.round(26*s) + 'px serif';
ctx.fillText(p.emoji, px, p.wy + bob);
// Destination hint
var dm = null;
for (var i = 0; i < ld.dests.length; i++) {
if (ld.dests[i].type === p.dest) { dm = ld.dests[i]; break; }
}
if (dm) {
ctx.font = Math.round(10*s) + 'px Nunito,sans-serif';
ctx.fillStyle = 'rgba(0,0,0,0.62)';
ctx.textAlign = 'center';
ctx.fillText('โ' + dm.label, px + 14, p.wy + 28*s + bob);
ctx.textAlign = 'left';
}
});
// Coins
ld.coins.forEach(function(coin) {
if (coin.collected) return;
var px = coin.wx - sx;
if (px < -30 || px > W + 30) return;
coin.ph += 0.06;
var py = coin.wy + Math.sin(coin.ph) * 4;
var r = clamp(coin.r, 6, 18);
var grd = ctx.createRadialGradient(px - r*.28, py - r*.28, 0, px, py, r);
grd.addColorStop(0, '#FFF176');
grd.addColorStop(0.5, '#FFD700');
grd.addColorStop(1, '#F57F17');
ctx.fillStyle = grd;
ctx.beginPath(); ctx.arc(px, py, r, 0, Math.PI*2); ctx.fill();
ctx.fillStyle = 'rgba(255,255,255,0.55)';
ctx.beginPath(); ctx.arc(px - r*.28, py - r*.28, r*.32, 0, Math.PI*2); ctx.fill();
ctx.fillStyle = '#7B5800';
ctx.font = 'bold ' + Math.round(r * 1.1) + 'px Nunito,sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('$', px, py);
ctx.textBaseline = 'alphabetic';
ctx.textAlign = 'left';
});
// Particles
ld.parts.forEach(function(p) {
ctx.save();
ctx.globalAlpha = p.life;
ctx.fillStyle = p.col;
ctx.beginPath(); ctx.arc(p.x - sx, p.y, p.sz, 0, Math.PI*2); ctx.fill();
ctx.restore();
});
// Car
drawCar(car.x - sx, car.y, pl, s);
// Wind indicator
if (Math.abs(ld.wind) > 0.04) {
ctx.fillStyle = 'rgba(255,255,255,0.65)';
ctx.font = Math.round(12 * s) + 'px Nunito,sans-serif';
ctx.fillText('Wind ' + (ld.wind > 0 ? '๐จโก๏ธ' : 'โฌ
๏ธ๐จ'), 10, gY - 12);
}
minimap(s);
}
function drawCar(x, y, pl, s) {
var cw = car.w, ch = car.h;
ctx.save();
ctx.translate(x + cw/2, y + ch/2);
ctx.rotate(car.angle);
// Drop shadow
ctx.fillStyle = 'rgba(0,0,0,0.18)';
ctx.beginPath(); ctx.ellipse(0, ch/2 + 4*s, cw*.44, 5*s, 0, 0, Math.PI*2); ctx.fill();
// Body
ctx.fillStyle = pl.carCol;
ctx.beginPath(); ctx.roundRect(-cw/2, -ch/2, cw, ch, 8*s); ctx.fill();
// Shine
ctx.fillStyle = 'rgba(255,255,255,0.18)';
ctx.beginPath(); ctx.roundRect(-cw/2, -ch/2, cw, ch*0.44, 8*s); ctx.fill();
// Wings
ctx.fillStyle = pl.wingCol;
ctx.beginPath(); ctx.moveTo(-8*s,-ch/2); ctx.lineTo(-22*s,-ch/2-20*s); ctx.lineTo(12*s,-ch/2); ctx.closePath(); ctx.fill();
ctx.beginPath(); ctx.moveTo(-8*s, ch/2); ctx.lineTo(-22*s, ch/2+20*s); ctx.lineTo(12*s, ch/2); ctx.closePath(); ctx.fill();
// Wing shine
ctx.fillStyle = 'rgba(255,255,255,0.28)';
ctx.beginPath(); ctx.moveTo(-6*s,-ch/2); ctx.lineTo(-12*s,-ch/2-10*s); ctx.lineTo(5*s,-ch/2); ctx.closePath(); ctx.fill();
// Windows
ctx.fillStyle = 'rgba(135,206,235,0.88)';
ctx.fillRect(-cw/2 + 6, -ch/2 + 4, 20*s, 11*s);
ctx.fillRect(-cw/2 + 28*s, -ch/2 + 4, 13*s, 11*s);
ctx.fillStyle = 'rgba(255,255,255,0.5)';
ctx.fillRect(-cw/2 + 7, -ch/2 + 5, 5*s, 4*s);
// Pilot emoji + hat
var eSize = Math.round(13*s);
ctx.font = eSize + 'px serif';
ctx.textAlign = 'center';
ctx.fillText(pl.emoji, -cw/2 + 13*s, -ch/2 + eSize + 1);
ctx.font = Math.round(10*s) + 'px serif';
ctx.fillText(pl.hat, -cw/2 + 13*s, -ch/2 - 3);
// In-car passengers
car.pax.forEach(function(p, i) {
ctx.font = Math.round(10*s) + 'px serif';
ctx.fillText(p.emoji, -cw/2 + 28*s + i*12*s, -ch/2 + eSize + 1);
});
// Engine flame
var jyPush = joy.y < -0.15 || goU();
if (jyPush) {
var fl = 0.7 + 0.3 * Math.random();
ctx.fillStyle = 'rgba(255,' + Math.round(70 + Math.random()*90) + ',0,' + fl + ')';
ctx.beginPath(); ctx.arc(-cw/2 - 9*s, 0, 9*s*fl, 0, Math.PI*2); ctx.fill();
ctx.fillStyle = 'rgba(255,220,0,' + fl + ')';
ctx.beginPath(); ctx.arc(-cw/2 - 6*s, 0, 5*s*fl, 0, Math.PI*2); ctx.fill();
}
ctx.restore();
}
function minimap(s) {
var mW = Math.round(clamp(W * 0.35, 110, 160));
var mH = Math.round(clamp(H * 0.06, 28, 38));
var mx = W - mW - 10;
var my = ld.gY - mH - 8;
ctx.fillStyle = 'rgba(0,0,0,0.48)';
ctx.fillRect(mx, my, mW, mH);
// Car dot
ctx.fillStyle = PILOTS[pilotIdx].carCol;
ctx.fillRect(mx + clamp((car.x / ld.worldW) * mW, 1, mW-5), my+2, 5, mH-4);
// Passengers
ld.pax.forEach(function(p) {
if (p.done) return;
ctx.fillStyle = p.picked ? '#3498db' : '#FFD700';
ctx.fillRect(mx + clamp((p.wx / ld.worldW) * mW, 1, mW-3), my + mH - 8, 3, 6);
});
// Destinations
ld.dests.forEach(function(d) {
ctx.fillStyle = d.col;
ctx.fillRect(mx + clamp((d.wx / ld.worldW) * mW, 1, mW-3), my + 2, 3, 8);
});
// Coins
ld.coins.forEach(function(c) {
if (c.collected) return;
ctx.fillStyle = '#FFD700';
ctx.fillRect(mx + clamp((c.wx / ld.worldW) * mW, 1, mW-3), my + mH/2 - 2, 3, 3);
});
ctx.strokeStyle = 'rgba(255,255,255,0.38)';
ctx.strokeRect(mx, my, mW, mH);
ctx.fillStyle = 'white';
ctx.font = Math.round(9*s) + 'px Nunito,sans-serif';
ctx.fillText('MAP', mx+3, my+10);
}
// โโ MAIN LOOP โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function loop() {
if (!gs.started) return;
fr++;
physics();
interact();
tickParts();
scrollUpdate();
if (spTimer > 0) { spTimer -= 16; if (spTimer <= 0) document.getElementById('speech').classList.add('hide'); }
if (comboTimer > 0) { comboTimer -= 16; if (comboTimer <= 0) document.getElementById('combo').classList.add('hide'); }
draw();
requestAnimationFrame(loop);
}
// โโ INIT KNOB SIZE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
window.addEventListener('load', function() { setTimeout(setKnobSize, 100); });
setTimeout(setKnobSize, 200);
})(); // end IIFE
</script>
</body>
</html>Game Source: Hero Pilot
Creator: StealthWolf75
Libraries: none
Complexity: complex (1035 lines, 41.0 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: hero-pilot-stealthwolf75" to link back to the original. Then publish at arcadelab.ai/publish.