{"id":1163,"date":"2026-01-03T18:50:47","date_gmt":"2026-01-03T23:50:47","guid":{"rendered":"https:\/\/makeyour.life\/?page_id=1163"},"modified":"2026-01-04T10:47:31","modified_gmt":"2026-01-04T15:47:31","slug":"ball-blaster","status":"publish","type":"page","link":"https:\/\/makeyour.life\/index.php\/ball-blaster\/","title":{"rendered":"Ball Blaster\ud83c\udfaf"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"1163\" class=\"elementor elementor-1163\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-88fe4eb e-flex e-con-boxed e-con e-parent\" data-id=\"88fe4eb\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-44e742f elementor-widget__width-inherit elementor-widget elementor-widget-html\" data-id=\"44e742f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!-- ===== BALL BLASTER (MVP+) Random Tracks + Next Round Button \ud83c\udfaf ===== -->\r\n<div id=\"bb-wrap\" style=\"max-width:420px;margin:0 auto;padding:10px;font-family:system-ui,-apple-system,Segoe UI,sans-serif;direction:ltr;\">\r\n\r\n  <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;font-size:14px;\">\r\n    <div>\u23f1 <span id=\"bb-time\">05:00<\/span><\/div>\r\n    <div>\ud83c\udfaf Score: <span id=\"bb-score\">0<\/span><\/div>\r\n  <\/div>\r\n\r\n  <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;font-size:12px;\">\r\n    <div>Lives: <span id=\"bb-lives\">\u2764\ufe0f\u2764\ufe0f\u2764\ufe0f<\/span><\/div>\r\n\r\n    <div style=\"display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:flex-end;\">\r\n      <div style=\"display:flex;gap:6px;align-items:center;\">\r\n        <span>Shot:<\/span>\r\n        <span id=\"bb-cur\" style=\"display:inline-block;width:14px;height:14px;border-radius:50%;border:1px solid rgba(0,0,0,.2)\"><\/span>\r\n      <\/div>\r\n      <div style=\"display:flex;gap:6px;align-items:center;\">\r\n        <span>Next:<\/span>\r\n        <span id=\"bb-next\" style=\"display:inline-block;width:14px;height:14px;border-radius:50%;border:1px solid rgba(0,0,0,.2)\"><\/span>\r\n      <\/div>\r\n\r\n      <button id=\"bb-pause\" style=\"padding:3px 8px;border:none;border-radius:999px;background:#607d8b;color:#fff;font-size:11px;cursor:pointer;\">Pause<\/button>\r\n      <button id=\"bb-restart\" style=\"padding:3px 8px;border:none;border-radius:999px;background:#9c27b0;color:#fff;font-size:11px;cursor:pointer;\">Restart<\/button>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <!-- Kid-friendly info box (transparent \/ pastel) -->\r\n  <div id=\"bb-info\" style=\"\r\n    border-radius:14px;\r\n    padding:10px 12px;\r\n    text-align:center;\r\n    margin-bottom:10px;\r\n    font-size:12px;\r\n    background:rgba(255,255,255,0.55);\r\n    border:1px solid rgba(255,255,255,0.65);\r\n    color:#0b1020;\r\n    backdrop-filter: blur(6px);\r\n  \">\r\n    Tap to aim & shoot. Hit the chain. Match 3+ in a row to explode.\r\n    <div style=\"margin-top:4px;font-weight:700;\">Track: <span id=\"bb-track\">\u2014<\/span><\/div>\r\n  <\/div>\r\n\r\n  <div id=\"bb-stage\" style=\"\r\n    position:relative;\r\n    width:100%;\r\n    aspect-ratio: 3 \/ 4;\r\n    background:linear-gradient(#7dd3fc,#a78bfa,#fda4af);\r\n    border-radius:14px;\r\n    overflow:hidden;\r\n    touch-action:manipulation;\r\n  \">\r\n    <canvas id=\"bb-canvas\" style=\"width:100%;height:100%;display:block;\"><\/canvas>\r\n\r\n    <div id=\"bb-msg\" style=\"\r\n      position:absolute;inset:0;display:flex;align-items:center;justify-content:center;\r\n      color:#0b1020;font-weight:900;font-size:20px;text-align:center;\r\n      background:rgba(255,255,255,.55);\r\n      backdrop-filter: blur(6px);\r\n      padding:18px; pointer-events:none;\r\n      white-space:pre-line;\r\n    \">Press Start<\/div>\r\n  <\/div>\r\n\r\n  <div style=\"text-align:center;margin-top:12px;\">\r\n    <button id=\"bb-start\" style=\"padding:10px 20px;border:none;border-radius:22px;background:#ff6f61;color:#fff;font-size:15px;cursor:pointer;\">\r\n      Start Game\r\n    <\/button>\r\n  <\/div>\r\n<\/div>\r\n\r\n<script>\r\n(function(){\r\n  const TOTAL_TIME = 300;\r\n  const MAX_LIVES = 3;\r\n\r\n  const stage = document.getElementById('bb-stage');\r\n  const canvas = document.getElementById('bb-canvas');\r\n  const ctx = canvas.getContext('2d');\r\n\r\n  const timeEl = document.getElementById('bb-time');\r\n  const scoreEl = document.getElementById('bb-score');\r\n  const livesEl = document.getElementById('bb-lives');\r\n  const msgEl = document.getElementById('bb-msg');\r\n\r\n  const curDot = document.getElementById('bb-cur');\r\n  const nextDot = document.getElementById('bb-next');\r\n  const trackEl = document.getElementById('bb-track');\r\n\r\n  const startBtn = document.getElementById('bb-start');\r\n  const pauseBtn = document.getElementById('bb-pause');\r\n  const restartBtn = document.getElementById('bb-restart');\r\n\r\n  \/\/ 3 colors only (RED \/ GREEN \/ YELLOW)\r\n  const COLORS = [\r\n    {name:'red',    c1:'#ff5252', c2:'#ff1744'},\r\n    {name:'green',  c1:'#66bb6a', c2:'#2e7d32'},\r\n    {name:'yellow', c1:'#ffe082', c2:'#ffb300'},\r\n  ];\r\n\r\n  let running=false, paused=false;\r\n  let timeLeft=TOTAL_TIME, score=0, lives=MAX_LIVES;\r\n\r\n  let rafId=null, timer=null;\r\n  let W=0, H=0, DPR=1;\r\n\r\n  \/\/ shooter\r\n  let shooter = { x:0, y:0, r:18, angle:-Math.PI\/2, cooldown:0, curColor:0, nextColor:1 };\r\n\r\n  \/\/ chain balls\r\n  let balls = [];       \/\/ {t, r, colorIdx}\r\n  let projectiles = []; \/\/ {x,y,vx,vy,r,colorIdx}\r\n\r\n  let lastSpawn = 0;\r\n\r\n  const BALL_R = 12;\r\n  const MIN_GAP_T = 0.020; \/\/ tight\r\n  let pendingNextRound = false;\r\n\r\n  \/\/ ---- PATH LIBRARY (10 tracks) ----\r\n  const PATHS = [\r\n    { name:'Kids Easy', fn:function(t){\r\n        const x = W*(0.2 + 0.6*t) + Math.sin(t*Math.PI)*W*0.08;\r\n        const y = H*(0.12 + 0.75*t);\r\n        return {x, y};\r\n    }},\r\n    { name:'Classic S', fn:function(t){\r\n        const x = W*(0.15 + 0.7*t) + Math.sin(t*2*Math.PI)*W*0.14;\r\n        const y = H*(0.1 + 0.8*t);\r\n        return {x, y};\r\n    }},\r\n    { name:'Double Wave', fn:function(t){\r\n        const x = W*(0.1 + 0.8*t) + Math.sin(t*4*Math.PI)*W*0.18;\r\n        const y = H*(0.1 + 0.75*t);\r\n        return {x, y};\r\n    }},\r\n    { name:'Snake', fn:function(t){\r\n        const x = W\/2 + Math.sin(t*3*Math.PI)*W*0.25;\r\n        const y = H*(0.1 + 0.8*t);\r\n        return {x, y};\r\n    }},\r\n    { name:'ZigZag', fn:function(t){\r\n        const zig = Math.sign(Math.sin(t*6*Math.PI));\r\n        const x = W*(0.15 + 0.7*t) + zig*W*0.22;\r\n        const y = H*(0.1 + 0.8*t);\r\n        return {x, y};\r\n    }},\r\n    { name:'Crazy Wave', fn:function(t){\r\n        const x = W*(0.1 + 0.8*t) + Math.sin(t*5*Math.PI)*W*0.25;\r\n        const y = H*(0.1 + 0.75*t) + Math.cos(t*2*Math.PI)*H*0.08;\r\n        return {x, y};\r\n    }},\r\n    { name:'Spiral In', fn:function(t){\r\n        const a = t * 2.8 * Math.PI;\r\n        const r = W * (0.35 - t*0.18);\r\n        const cx = W\/2;\r\n        const cy = H*0.45;\r\n        return { x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r };\r\n    }},\r\n    { name:'Spiral Down', fn:function(t){\r\n        const a = t * 3 * Math.PI;\r\n        const r = W * 0.25;\r\n        const x = W\/2 + Math.cos(a) * r;\r\n        const y = H*(0.15 + 0.75*t) + Math.sin(a) * r * 0.25;\r\n        return {x, y};\r\n    }},\r\n    { name:'Infinity', fn:function(t){\r\n        const a = t * 2 * Math.PI;\r\n        const cx = W\/2;\r\n        const cy = H*0.45;\r\n        const x = cx + Math.sin(a) * W*0.25;\r\n        const y = cy + Math.sin(a*2) * H*0.18 + t*H*0.3;\r\n        return {x, y};\r\n    }},\r\n    { name:'Random Feel', fn:function(t){\r\n        const x = W*(0.1 + 0.8*t) + Math.sin(t*7*Math.PI)*W*0.2;\r\n        const y = H*(0.1 + 0.75*t) + Math.sin(t*3*Math.PI)*H*0.12;\r\n        return {x, y};\r\n    }},\r\n  ];\r\n\r\n  let pathIndex = Math.floor(Math.random()*PATHS.length);\r\n  let lastPathIndex = pathIndex;\r\n\r\n  function pickNewPath(){\r\n    let tries=0;\r\n    do{\r\n      pathIndex = Math.floor(Math.random()*PATHS.length);\r\n      tries++;\r\n    }while(pathIndex === lastPathIndex && tries < 8);\r\n    lastPathIndex = pathIndex;\r\n    trackEl.textContent = PATHS[pathIndex].name;\r\n  }\r\n\r\n  function pathPos(t){\r\n    return PATHS[pathIndex].fn(t);\r\n  }\r\n\r\n  function fmt(sec){\r\n    const m=Math.floor(sec\/60), s=sec%60;\r\n    return String(m).padStart(2,'0')+':'+String(s).padStart(2,'0');\r\n  }\r\n\r\n  function progress(){\r\n    return 1 - (timeLeft \/ TOTAL_TIME);\r\n  }\r\n\r\n  function chainSpeed(){\r\n    const p = progress();\r\n    return 0.022 + p*0.012; \/\/ balanced\r\n  }\r\n\r\n  function ballGradient(x,y,r,ci){\r\n    const col = COLORS[ci];\r\n    const g = ctx.createRadialGradient(x-r*0.3,y-r*0.3, r*0.2, x,y,r);\r\n    g.addColorStop(0, col.c1);\r\n    g.addColorStop(1, col.c2);\r\n    return g;\r\n  }\r\n\r\n  function randColor(){\r\n    return Math.floor(Math.random()*COLORS.length);\r\n  }\r\n\r\n  function hud(){\r\n    timeEl.textContent = fmt(timeLeft);\r\n    scoreEl.textContent = score;\r\n    livesEl.textContent = '\u2764\ufe0f'.repeat(lives);\r\n\r\n    curDot.style.background = `linear-gradient(135deg,${COLORS[shooter.curColor].c1},${COLORS[shooter.curColor].c2})`;\r\n    nextDot.style.background = `linear-gradient(135deg,${COLORS[shooter.nextColor].c1},${COLORS[shooter.nextColor].c2})`;\r\n  }\r\n\r\n  function resize(){\r\n    const rect = stage.getBoundingClientRect();\r\n    DPR = Math.min(2, window.devicePixelRatio || 1);\r\n    W = Math.floor(rect.width);\r\n    H = Math.floor(rect.height);\r\n\r\n    canvas.width = Math.floor(W * DPR);\r\n    canvas.height = Math.floor(H * DPR);\r\n    canvas.style.width = W+'px';\r\n    canvas.style.height = H+'px';\r\n    ctx.setTransform(DPR,0,0,DPR,0,0);\r\n\r\n    shooter.x = W\/2;\r\n    shooter.y = H - 38;\r\n  }\r\n\r\n  function drawPath(){\r\n    ctx.save();\r\n    ctx.globalAlpha = 0.35;\r\n    ctx.lineWidth = 10;\r\n    ctx.strokeStyle = 'rgba(255,255,255,0.65)';\r\n    ctx.lineCap = 'round';\r\n    ctx.beginPath();\r\n    for(let i=0;i<=70;i++){\r\n      const t=i\/70, p=pathPos(t);\r\n      if(i===0) ctx.moveTo(p.x,p.y); else ctx.lineTo(p.x,p.y);\r\n    }\r\n    ctx.stroke();\r\n    ctx.restore();\r\n\r\n    const end = pathPos(1);\r\n    ctx.save();\r\n    ctx.fillStyle = 'rgba(0,0,0,0.25)';\r\n    ctx.beginPath();\r\n    ctx.arc(end.x, end.y, 18, 0, Math.PI*2);\r\n    ctx.fill();\r\n    ctx.strokeStyle = 'rgba(255,255,255,0.55)';\r\n    ctx.lineWidth = 2;\r\n    ctx.stroke();\r\n    ctx.restore();\r\n  }\r\n\r\n  function normalizeChain(){\r\n    balls.sort((a,b)=>a.t-b.t);\r\n    if(!balls.length) return;\r\n\r\n    balls[0].t = Math.max(0, Math.min(1, balls[0].t));\r\n    for(let i=1;i<balls.length;i++){\r\n      balls[i].t = balls[i-1].t + MIN_GAP_T;\r\n    }\r\n  }\r\n\r\n  function spawnBall(){\r\n    balls.push({ t: 0, r: BALL_R, colorIdx: randColor() });\r\n    normalizeChain();\r\n  }\r\n\r\n  function shootTo(px, py){\r\n    if(!running || paused || pendingNextRound) return;\r\n    if(shooter.cooldown > 0) return;\r\n\r\n    const dx = px - shooter.x;\r\n    const dy = py - shooter.y;\r\n    shooter.angle = Math.atan2(dy, dx);\r\n\r\n    const v = 650; \/\/ projectile speed (you can tune this)\r\n    const ci = shooter.curColor;\r\n\r\n    projectiles.push({\r\n      x: shooter.x + Math.cos(shooter.angle)*(shooter.r+8),\r\n      y: shooter.y + Math.sin(shooter.angle)*(shooter.r+8),\r\n      vx: Math.cos(shooter.angle)*v,\r\n      vy: Math.sin(shooter.angle)*v,\r\n      r: 9,\r\n      colorIdx: ci\r\n    });\r\n\r\n    shooter.curColor = shooter.nextColor;\r\n    shooter.nextColor = randColor();\r\n    hud();\r\n\r\n    shooter.cooldown = 0.18; \/\/ prevents multi-shot spam\r\n  }\r\n\r\n  function dist2(ax,ay,bx,by){\r\n    const dx=ax-bx, dy=ay-by;\r\n    return dx*dx+dy*dy;\r\n  }\r\n\r\n  function checkExplode(centerIdx){\r\n    const color = balls[centerIdx].colorIdx;\r\n    let L=centerIdx, R=centerIdx;\r\n    while(L-1>=0 && balls[L-1].colorIdx===color) L--;\r\n    while(R+1<balls.length && balls[R+1].colorIdx===color) R++;\r\n    const count = R-L+1;\r\n    if(count>=3){\r\n      balls.splice(L, count);\r\n      score += count;\r\n      scoreEl.textContent = score;\r\n      normalizeChain();\r\n    }\r\n  }\r\n\r\n  function insertBallNear(indexT, colorIdx){\r\n    balls.push({ t: indexT, r: BALL_R, colorIdx });\r\n    normalizeChain();\r\n\r\n    balls.sort((a,b)=>a.t-b.t);\r\n    let nearest=0, best=Infinity;\r\n    for(let i=0;i<balls.length;i++){\r\n      const d = Math.abs(balls[i].t - indexT);\r\n      if(d<best){ best=d; nearest=i; }\r\n    }\r\n    checkExplode(nearest);\r\n  }\r\n\r\n  function update(dt, now){\r\n    const p = progress();\r\n\r\n    \/\/ controlled spawn (not too many balls)\r\n    const spawnInterval = 1700 - p*350; \/\/ fewer balls, good for kids\r\n    if(now - lastSpawn > spawnInterval){\r\n      spawnBall();\r\n      lastSpawn = now;\r\n    }\r\n\r\n    shooter.cooldown = Math.max(0, shooter.cooldown - dt);\r\n\r\n    const sp = chainSpeed();\r\n    for(const b of balls){\r\n      b.t += sp * dt;\r\n    }\r\n    normalizeChain();\r\n\r\n    \/\/ hole reached\r\n    for(let i=balls.length-1;i>=0;i--){\r\n      if(balls[i].t >= 1){\r\n        balls.splice(i,1);\r\n        lives--;\r\n        hud();\r\n        if(lives<=0){\r\n          endRound('Game Over \ud83d\udca5\\nScore: ' + score);\r\n          return;\r\n        }\r\n      }\r\n    }\r\n\r\n    \/\/ move projectiles + collide with chain only\r\n    for(let i=projectiles.length-1;i>=0;i--){\r\n      const pr = projectiles[i];\r\n      pr.x += pr.vx * dt;\r\n      pr.y += pr.vy * dt;\r\n\r\n      let hit=false;\r\n      for(let j=0;j<balls.length;j++){\r\n        const bp = pathPos(balls[j].t);\r\n        const rSum = pr.r + balls[j].r;\r\n        if(dist2(pr.x,pr.y,bp.x,bp.y) <= rSum*rSum){\r\n          insertBallNear(Math.max(0, balls[j].t - 0.001), pr.colorIdx);\r\n          projectiles.splice(i,1);\r\n          hit=true;\r\n          break;\r\n        }\r\n      }\r\n      if(hit) continue;\r\n\r\n      if(pr.x < -80 || pr.x > W+80 || pr.y < -100 || pr.y > H+100){\r\n        projectiles.splice(i,1);\r\n      }\r\n    }\r\n  }\r\n\r\n  function render(){\r\n    ctx.clearRect(0,0,W,H);\r\n\r\n    \/\/ background bubbles\r\n    ctx.save();\r\n    ctx.globalAlpha = 0.15;\r\n    for(let i=0;i<16;i++){\r\n      ctx.fillStyle = 'rgba(255,255,255,0.9)';\r\n      ctx.beginPath();\r\n      ctx.arc((i*73)%W, (i*119)%H, 1.5, 0, Math.PI*2);\r\n      ctx.fill();\r\n    }\r\n    ctx.restore();\r\n\r\n    drawPath();\r\n\r\n    for(const b of balls){\r\n      const p = pathPos(b.t);\r\n      ctx.save();\r\n      ctx.fillStyle = ballGradient(p.x,p.y,b.r,b.colorIdx);\r\n      ctx.beginPath();\r\n      ctx.arc(p.x,p.y,b.r,0,Math.PI*2);\r\n      ctx.fill();\r\n      ctx.globalAlpha = 0.25;\r\n      ctx.strokeStyle = '#fff';\r\n      ctx.lineWidth = 1;\r\n      ctx.stroke();\r\n      ctx.restore();\r\n    }\r\n\r\n    for(const pr of projectiles){\r\n      ctx.save();\r\n      ctx.fillStyle = ballGradient(pr.x,pr.y,pr.r,pr.colorIdx);\r\n      ctx.beginPath();\r\n      ctx.arc(pr.x,pr.y,pr.r,0,Math.PI*2);\r\n      ctx.fill();\r\n      ctx.restore();\r\n    }\r\n\r\n    \/\/ shooter base\r\n    ctx.save();\r\n    ctx.fillStyle = 'rgba(255,255,255,0.35)';\r\n    ctx.beginPath();\r\n    ctx.arc(shooter.x, shooter.y, shooter.r+10, 0, Math.PI*2);\r\n    ctx.fill();\r\n    ctx.restore();\r\n\r\n    \/\/ cannon\r\n    ctx.save();\r\n    ctx.translate(shooter.x, shooter.y);\r\n    ctx.rotate(shooter.angle);\r\n    ctx.fillStyle = 'rgba(0,0,0,0.25)';\r\n    ctx.fillRect(0, -6, 34, 12);\r\n    ctx.restore();\r\n\r\n    \/\/ head = current color\r\n    ctx.save();\r\n    ctx.fillStyle = ballGradient(shooter.x,shooter.y,shooter.r,shooter.curColor);\r\n    ctx.beginPath();\r\n    ctx.arc(shooter.x, shooter.y, shooter.r, 0, Math.PI*2);\r\n    ctx.fill();\r\n    ctx.globalAlpha = 0.35;\r\n    ctx.strokeStyle = '#fff';\r\n    ctx.lineWidth = 2;\r\n    ctx.stroke();\r\n    ctx.restore();\r\n  }\r\n\r\n  function endRound(text){\r\n    running=false; paused=false;\r\n    if(rafId){ cancelAnimationFrame(rafId); rafId=null; }\r\n    if(timer){ clearInterval(timer); timer=null; }\r\n\r\n    pendingNextRound = true;\r\n\r\n    msgEl.style.display='flex';\r\n    msgEl.textContent = text + '\\n\\nNext track ready.';\r\n    startBtn.textContent = 'Start Next Round';\r\n    pauseBtn.textContent = 'Pause';\r\n  }\r\n\r\n  function resetAll(){\r\n    running=false; paused=false;\r\n    if(rafId){ cancelAnimationFrame(rafId); rafId=null; }\r\n    if(timer){ clearInterval(timer); timer=null; }\r\n\r\n    timeLeft=TOTAL_TIME; score=0; lives=MAX_LIVES;\r\n    balls=[]; projectiles=[]; lastSpawn=0;\r\n\r\n    shooter.cooldown=0;\r\n    shooter.angle=-Math.PI\/2;\r\n    shooter.curColor = randColor();\r\n    shooter.nextColor = randColor();\r\n\r\n    pendingNextRound = false;\r\n\r\n    hud();\r\n    msgEl.style.display='flex';\r\n    msgEl.textContent='Press Start';\r\n    startBtn.textContent='Start Game';\r\n    pauseBtn.textContent='Pause';\r\n  }\r\n\r\n  function startRound(){\r\n    \/\/ pick random path every round\r\n    pickNewPath();\r\n\r\n    timeLeft=TOTAL_TIME;\r\n    balls=[];\r\n    projectiles=[];\r\n    lastSpawn=0;\r\n\r\n    \/\/ initial chain\r\n    for(let i=0;i<9;i++){\r\n      balls.push({ t: i*MIN_GAP_T, r: BALL_R, colorIdx: randColor() });\r\n    }\r\n    normalizeChain();\r\n\r\n    pendingNextRound = false;\r\n    running=true; paused=false;\r\n\r\n    msgEl.style.display='none';\r\n    startBtn.textContent='Playing...';\r\n\r\n    if(timer) clearInterval(timer);\r\n    timer = setInterval(()=>{\r\n      if(!running || paused) return;\r\n      timeLeft--;\r\n      hud();\r\n      if(timeLeft<=0){\r\n        timeLeft=0; hud();\r\n        endRound('Time Up \u23f1\\nScore: ' + score);\r\n      }\r\n    },1000);\r\n\r\n    let lastT = performance.now();\r\n    function loop(now){\r\n      if(!running) return;\r\n      const dt = Math.min(0.033, (now-lastT)\/1000);\r\n      lastT = now;\r\n\r\n      if(!paused){\r\n        update(dt, now);\r\n      }\r\n      render();\r\n      rafId = requestAnimationFrame(loop);\r\n    }\r\n    if(rafId) cancelAnimationFrame(rafId);\r\n    rafId = requestAnimationFrame(loop);\r\n  }\r\n\r\n  function togglePause(){\r\n    if(!running || pendingNextRound) return;\r\n    paused=!paused;\r\n    pauseBtn.textContent = paused ? 'Resume' : 'Pause';\r\n    msgEl.style.display = paused ? 'flex' : 'none';\r\n    msgEl.textContent = paused ? 'Paused' : '';\r\n  }\r\n\r\n  \/\/ Single unified input (prevents double shots)\r\n  stage.addEventListener('pointerdown', (e)=>{\r\n    if(!running || paused || pendingNextRound) return;\r\n    const r = stage.getBoundingClientRect();\r\n    shootTo(e.clientX - r.left, e.clientY - r.top);\r\n  }, {passive:true});\r\n\r\n  startBtn.addEventListener('click', ()=>{\r\n    \/\/ If never started: reset and start first round\r\n    if(!running && !pendingNextRound){\r\n      resetAll();\r\n      \/\/ keep score reset for first start\r\n      score = 0; scoreEl.textContent = score;\r\n    }\r\n    \/\/ start next round (or first round)\r\n    if(!running){\r\n      startRound();\r\n    }\r\n  });\r\n\r\n  pauseBtn.addEventListener('click', togglePause);\r\n\r\n  restartBtn.addEventListener('click', ()=>{\r\n    resetAll();\r\n    \/\/ restart from scratch\r\n    score = 0; scoreEl.textContent = score;\r\n    startRound();\r\n  });\r\n\r\n  window.addEventListener('resize', ()=>{ resize(); });\r\n\r\n  \/\/ init\r\n  resize();\r\n  resetAll();\r\n  \/\/ show first random track name (for display even before start)\r\n  pickNewPath();\r\n  hud();\r\n})();\r\n<\/script>\r\n<!-- ===== END BALL BLASTER ===== -->\r\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>\u23f1 05:00 \ud83c\udfaf Score: 0 Lives: \u2764\ufe0f\u2764\ufe0f\u2764\ufe0f Shot: Next: Pause Restart Tap to aim &#038; shoot. Hit the chain. Match [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"site-sidebar-layout":"no-sidebar","site-content-layout":"","ast-site-content-layout":"full-width-container","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"disabled","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"class_list":["post-1163","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/makeyour.life\/index.php\/wp-json\/wp\/v2\/pages\/1163","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/makeyour.life\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/makeyour.life\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/makeyour.life\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/makeyour.life\/index.php\/wp-json\/wp\/v2\/comments?post=1163"}],"version-history":[{"count":47,"href":"https:\/\/makeyour.life\/index.php\/wp-json\/wp\/v2\/pages\/1163\/revisions"}],"predecessor-version":[{"id":1228,"href":"https:\/\/makeyour.life\/index.php\/wp-json\/wp\/v2\/pages\/1163\/revisions\/1228"}],"wp:attachment":[{"href":"https:\/\/makeyour.life\/index.php\/wp-json\/wp\/v2\/media?parent=1163"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}