{"id":40541,"date":"2026-06-05T10:55:33","date_gmt":"2026-06-05T10:55:33","guid":{"rendered":"https:\/\/pradeepaggarwal.com\/wps\/?page_id=40541"},"modified":"2026-06-05T10:57:31","modified_gmt":"2026-06-05T10:57:31","slug":"index","status":"publish","type":"page","link":"https:\/\/pradeepaggarwal.com\/wps\/index\/","title":{"rendered":"index"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"40541\" class=\"elementor elementor-40541\" data-elementor-post-type=\"page\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-a963e5c elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"a963e5c\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-b07a613\" data-id=\"b07a613\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-aba8fab elementor-widget elementor-widget-html\" data-id=\"aba8fab\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n<title>Pixel Quest \u2014 Adventure Platformer<\/title>\n<style>\n  * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }\n  html, body {\n    height: 100%;\n    background: #1a1228;\n    font-family: 'Courier New', monospace;\n    overflow: hidden;\n    touch-action: none;\n    user-select: none;\n  }\n  #wrap {\n    width: 100vw; height: 100vh;\n    display: flex; align-items: center; justify-content: center;\n    position: relative; overflow: hidden;\n  }\n  #stage {\n    display: flex; flex-direction: column; align-items: center;\n    gap: 8px; max-width: 100%; max-height: 100%;\n  }\n  #mid {\n    display: flex; align-items: center; justify-content: center; gap: 10px;\n  }\n  #gamearea {\n    position: relative; line-height: 0; font-size: 0;\n  }\n  canvas {\n    background: #6ec0ff;\n    image-rendering: pixelated;\n    image-rendering: crisp-edges;\n    display: block;\n    box-shadow: 0 0 0 4px #2a1d40, 0 8px 40px rgba(0,0,0,.6);\n  }\n\n  \/* ---- Ad slots (placeholders \u2014 drop your ad code inside each container) ---- *\/\n  .ad-slot {\n    display: flex; align-items: center; justify-content: center;\n    background:\n      repeating-linear-gradient(45deg, rgba(255,255,255,.04) 0 10px, rgba(255,255,255,.07) 10px 20px);\n    border: 1px dashed rgba(255,255,255,.28);\n    border-radius: 6px; overflow: hidden; flex: none;\n  }\n  .ad-label {\n    font-size: 10px; letter-spacing: 2px; color: rgba(255,255,255,.45);\n    font-family: 'Courier New', monospace; line-height: 1;\n  }\n  .ad-top, .ad-bottom { width: 728px; max-width: 100%; height: 90px; }\n  .ad-side { width: 160px; height: 600px; max-height: 70vh; }\n  \/* Hide side ads when the viewport is too narrow to fit them *\/\n  @media (max-width: 1099px) { .ad-side { display: none; } }\n  @media (max-width: 760px)  { .ad-top  { display: none; } .ad-top, .ad-bottom { height: 60px; } }\n  \/* On touch devices the bottom control bar replaces the bottom banner *\/\n  body.touch .ad-bottom { display: none; }\n\n  \/* HUD *\/\n  #hud {\n    position: absolute; top: 12px; left: 50%; transform: translateX(-50%);\n    display: flex; gap: 18px; color: #fff;\n    font-size: 18px; font-weight: bold; letter-spacing: 1px;\n    text-shadow: 2px 2px 0 #000; pointer-events: none; white-space: nowrap;\n  }\n  #hud span { display: inline-flex; align-items: center; gap: 5px; }\n  .coin-dot { width: 14px; height: 14px; border-radius: 50%; background: #ffd23f; box-shadow: inset -3px -3px 0 #d99a1c; }\n  .heart { color: #ff4d6d; }\n\n  \/* Overlays \u2014 fixed to viewport & scrollable so content\/buttons are never clipped *\/\n  .overlay {\n    position: fixed; inset: 0; display: none;\n    flex-direction: column; align-items: center; justify-content: center;\n    gap: 4px; overflow-y: auto;\n    background: rgba(20,10,40,.92); color: #fff; text-align: center; padding: 24px 16px;\n    z-index: 30; -webkit-overflow-scrolling: touch;\n  }\n  .overlay.show { display: flex; }\n  .overlay > * { flex-shrink: 0; }\n  .overlay h1 {\n    font-size: 54px; letter-spacing: 3px; margin-bottom: 6px;\n    color: #ffd23f; text-shadow: 4px 4px 0 #b5651d, 0 0 30px rgba(255,210,63,.4);\n  }\n  .overlay h2 { font-size: 30px; margin-bottom: 14px; text-shadow: 3px 3px 0 #000; }\n  .overlay p { font-size: 16px; line-height: 1.7; margin-bottom: 8px; opacity: .92; max-width: 460px; }\n  .overlay .keycap {\n    display: inline-block; background: #fff; color: #1a1228; border-radius: 5px;\n    padding: 1px 9px; margin: 0 2px; font-weight: bold; box-shadow: 0 3px 0 #888;\n  }\n  .btn {\n    margin-top: 18px; font-family: inherit; font-size: 22px; font-weight: bold;\n    letter-spacing: 2px; color: #1a1228; background: #ffd23f; border: none;\n    padding: 14px 44px; border-radius: 10px; cursor: pointer;\n    box-shadow: 0 6px 0 #c79324; transition: transform .05s, box-shadow .05s;\n  }\n  .btn:active { transform: translateY(4px); box-shadow: 0 2px 0 #c79324; }\n  \/* Smaller headings on phones so panels fit *\/\n  @media (max-width: 600px) {\n    .overlay h1 { font-size: 38px; }\n    .overlay h2 { font-size: 24px; }\n    .overlay p  { font-size: 14px; line-height: 1.5; }\n  }\n\n  \/* Touch controls \u2014 pinned to the bottom of the screen, off the play area *\/\n  #touch {\n    position: fixed; left: 0; right: 0; bottom: 0; height: 104px;\n    display: none; align-items: center; justify-content: space-between;\n    padding: 0 20px env(safe-area-inset-bottom, 0px); gap: 12px; z-index: 16;\n    background: linear-gradient(180deg, rgba(20,12,30,0) 0%, rgba(20,12,30,.55) 35%, rgba(20,12,30,.9) 100%);\n    pointer-events: none;\n  }\n  #touch.show { display: flex; }\n  .pad-group { display: flex; gap: 16px; }\n  .pad {\n    position: static; width: 70px; height: 70px; border-radius: 50%;\n    background: rgba(255,255,255,.22); border: 3px solid rgba(255,255,255,.55);\n    display: flex; align-items: center; justify-content: center; color: #fff;\n    font-size: 28px; pointer-events: auto; backdrop-filter: blur(2px);\n    touch-action: none;\n  }\n  .pad:active { background: rgba(255,255,255,.45); transform: scale(.94); }\n  #t-jump  { background: rgba(110,220,140,.32); border-color: rgba(110,220,140,.75); }\n  #t-shoot { background: rgba(255,120,120,.32); border-color: rgba(255,120,120,.75); }\n\n  #musicBtn {\n    position: absolute; top: 12px; right: 14px; z-index: 18;\n    width: 40px; height: 40px; border-radius: 9px; cursor: pointer;\n    border: 2px solid rgba(255,255,255,.5); background: rgba(255,255,255,.18);\n    color: #fff; font-size: 18px; line-height: 1; backdrop-filter: blur(2px);\n  }\n  #musicBtn:active { transform: translateY(2px); }\n  #musicBtn.off { opacity: .55; }\n\n  \/* High scores *\/\n  .hs-title { font-size: 15px; letter-spacing: 3px; color:#ffd23f; margin: 14px 0 6px; text-shadow: 2px 2px 0 #000; }\n  table.hs { border-collapse: collapse; margin: 0 auto; min-width: 200px; }\n  table.hs th { font-size: 11px; letter-spacing: 2px; color: rgba(255,255,255,.6); padding: 2px 18px; }\n  table.hs td { font-size: 18px; font-weight: bold; padding: 3px 18px; color:#fff; text-align:center; text-shadow: 2px 2px 0 #000; }\n  table.hs tr.hl td { color:#ffd23f; }\n  .hs-empty { font-size: 14px; opacity:.6; margin-top: 4px; }\n<\/style>\n<\/head>\n<body>\n<div id=\"wrap\">\n  <div id=\"stage\">\n\n    <!-- ===== TOP AD SLOT (e.g. 728x90 leaderboard) ===== -->\n    <!-- Paste your ad network code (Google AdSense, game-portal SDK, etc.) inside this div. -->\n    <div class=\"ad-slot ad-top\" id=\"ad-top\"><span class=\"ad-label\">ADVERTISEMENT<\/span><\/div>\n\n    <div id=\"mid\">\n\n      <!-- ===== LEFT AD SLOT (e.g. 160x600) \u2014 shown on wide screens ===== -->\n      <div class=\"ad-slot ad-side\" id=\"ad-left\"><span class=\"ad-label\">AD<\/span><\/div>\n\n      <!-- ===== GAME AREA ===== -->\n      <div id=\"gamearea\">\n        <canvas id=\"game\" width=\"800\" height=\"450\"><\/canvas>\n\n        <div id=\"hud\">\n          <span><span class=\"coin-dot\"><\/span><span id=\"coins\">0<\/span><\/span>\n          <span>SCORE <span id=\"score\">0<\/span><\/span>\n          <span>TIME <span id=\"time\">200<\/span><\/span>\n          <span class=\"heart\">\u2665 <span id=\"lives\">3<\/span><\/span>\n          <span>WORLD <span id=\"world\">1-1<\/span><\/span>\n        <\/div>\n\n        <button id=\"musicBtn\" title=\"Toggle music\">\ud83c\udfb5<\/button>\n\n        <!-- Start screen -->\n        <div class=\"overlay show\" id=\"startScreen\">\n          <h1>PIXEL QUEST<\/h1>\n          <p>The Sunshine Gems were stolen by the Grumble King. Run, jump and blast your way through three worlds to win them back!<\/p>\n          <p style=\"margin-top:14px\">\n            <span class=\"keycap\">\u2190<\/span><span class=\"keycap\">\u2192<\/span> or <span class=\"keycap\">A<\/span><span class=\"keycap\">D<\/span> &nbsp;move &nbsp;\u2022&nbsp;\n            <span class=\"keycap\">\u2191<\/span> \/ <span class=\"keycap\">W<\/span> \/ <span class=\"keycap\">Space<\/span> jump &nbsp;\u2022&nbsp;\n            <span class=\"keycap\">Z<\/span> shoot\n          <\/p>\n          <p style=\"font-size:14px;opacity:.7\">Stomp enemies from above or blast them. Grab coins, smash <b>?<\/b> blocks, reach the flag!<\/p>\n          <div id=\"hsStart\"><\/div>\n          <button class=\"btn\" id=\"startBtn\">START<\/button>\n        <\/div>\n\n        <!-- Level complete -->\n        <div class=\"overlay\" id=\"winScreen\">\n          <h2 id=\"winTitle\">LEVEL CLEAR!<\/h2>\n          <p id=\"winMsg\"><\/p>\n          <button class=\"btn\" id=\"nextBtn\">CONTINUE<\/button>\n        <\/div>\n\n        <!-- Game over -->\n        <div class=\"overlay\" id=\"overScreen\">\n          <h1 style=\"color:#ff4d6d;text-shadow:4px 4px 0 #6a1020\">GAME OVER<\/h1>\n          <p id=\"overMsg\"><\/p>\n          <button class=\"btn\" id=\"retryBtn\">TRY AGAIN<\/button>\n        <\/div>\n\n        <!-- Touch controls -->\n        <div id=\"touch\">\n          <div class=\"pad-group\">\n            <div class=\"pad\" id=\"t-left\">\u25c0<\/div>\n            <div class=\"pad\" id=\"t-right\">\u25b6<\/div>\n          <\/div>\n          <div class=\"pad-group\">\n            <div class=\"pad\" id=\"t-shoot\">\u25cf<\/div>\n            <div class=\"pad\" id=\"t-jump\">\u25b2<\/div>\n          <\/div>\n        <\/div>\n      <\/div><!-- \/gamearea -->\n\n      <!-- ===== RIGHT AD SLOT (e.g. 160x600) \u2014 shown on wide screens ===== -->\n      <div class=\"ad-slot ad-side\" id=\"ad-right\"><span class=\"ad-label\">AD<\/span><\/div>\n\n    <\/div><!-- \/mid -->\n\n    <!-- ===== BOTTOM AD SLOT (e.g. 728x90 \/ 320x50 on mobile) ===== -->\n    <div class=\"ad-slot ad-bottom\" id=\"ad-bottom\"><span class=\"ad-label\">ADVERTISEMENT<\/span><\/div>\n\n  <\/div><!-- \/stage -->\n<\/div>\n\n<script>\nconst cv = document.getElementById('game');\nconst ctx = cv.getContext('2d');\nctx.imageSmoothingEnabled = false;\nconst W = cv.width, H = cv.height;\n\n\/\/ ---------- Input ----------\nconst keys = {};\nconst map = {\n  ArrowLeft:'left', KeyA:'left', ArrowRight:'right', KeyD:'right',\n  ArrowUp:'jump', KeyW:'jump', Space:'jump', KeyZ:'shoot', KeyJ:'shoot'\n};\naddEventListener('keydown', e => { if (map[e.code]) { keys[map[e.code]] = true; e.preventDefault(); } });\naddEventListener('keyup',   e => { if (map[e.code]) { keys[map[e.code]] = false; e.preventDefault(); } });\n\nfunction bindPad(id, name){\n  const el = document.getElementById(id);\n  const on  = e => { e.preventDefault(); keys[name] = true; };\n  const off = e => { e.preventDefault(); keys[name] = false; };\n  el.addEventListener('touchstart', on, {passive:false});\n  el.addEventListener('touchend', off, {passive:false});\n  el.addEventListener('touchcancel', off, {passive:false});\n  el.addEventListener('mousedown', on);\n  el.addEventListener('mouseup', off);\n  el.addEventListener('mouseleave', off);\n}\nbindPad('t-left','left'); bindPad('t-right','right'); bindPad('t-jump','jump'); bindPad('t-shoot','shoot');\nif ('ontouchstart' in window || navigator.maxTouchPoints > 0){\n  document.getElementById('touch').classList.add('show');\n  document.body.classList.add('touch');\n}\n\n\/\/ ---------- Audio (tiny beeps) ----------\nlet AC;\nfunction beep(freq, dur, type='square', vol=0.06){\n  try {\n    if (!AC) AC = new (window.AudioContext||window.webkitAudioContext)();\n    const o = AC.createOscillator(), g = AC.createGain();\n    o.type = type; o.frequency.value = freq;\n    g.gain.value = vol; o.connect(g); g.connect(AC.destination);\n    o.start(); o.frequency.exponentialRampToValueAtTime(freq*0.6, AC.currentTime+dur);\n    g.gain.exponentialRampToValueAtTime(0.0001, AC.currentTime+dur);\n    o.stop(AC.currentTime+dur);\n  } catch(e){}\n}\nconst sfx = {\n  jump:  ()=>beep(520,.12,'square'),\n  coin:  ()=>{beep(880,.06);setTimeout(()=>beep(1180,.08),60);},\n  shoot: ()=>beep(300,.08,'sawtooth',.04),\n  stomp: ()=>beep(180,.12,'square'),\n  hurt:  ()=>beep(140,.3,'sawtooth',.08),\n  block: ()=>beep(440,.08,'square'),\n  win:   ()=>{[523,659,784,1047].forEach((f,i)=>setTimeout(()=>beep(f,.15),i*120));},\n  power: ()=>{[440,660,880].forEach((f,i)=>setTimeout(()=>beep(f,.1),i*70));}\n};\n\n\/\/ ---------- Generative ASMR ambient music ----------\n\/\/ Soft drifting chord pads + gentle noise wash + occasional soothing chimes.\nlet MUSIC = { on:true, nodes:null };\nfunction makeNoiseBuffer(){\n  const len = 2*AC.sampleRate, buf = AC.createBuffer(1,len,AC.sampleRate), d = buf.getChannelData(0);\n  let last=0;\n  for (let i=0;i<len;i++){ const wht=Math.random()*2-1; d[i]=(last+0.02*wht)\/1.02; last=d[i]; d[i]*=3.2; }\n  return buf;\n}\nfunction softChime(master){\n  const scale=[523.25,587.33,659.25,783.99,880.00,1046.50];\n  const f=scale[Math.floor(Math.random()*scale.length)];\n  const o=AC.createOscillator(), g=AC.createGain();\n  o.type='sine'; o.frequency.value=f;\n  o.connect(g); g.connect(master);\n  const t=AC.currentTime;\n  g.gain.setValueAtTime(0.0001,t);\n  g.gain.linearRampToValueAtTime(0.05, t+0.08);\n  g.gain.exponentialRampToValueAtTime(0.0001, t+2.6);\n  o.start(t); o.stop(t+2.7);\n}\nfunction startMusic(){\n  try{\n    if (!AC) AC = new (window.AudioContext||window.webkitAudioContext)();\n    if (AC.state==='suspended') AC.resume();\n    if (MUSIC.nodes) return;\n    const master=AC.createGain(); master.gain.value=0; master.connect(AC.destination);\n    master.gain.linearRampToValueAtTime(0.16, AC.currentTime+3.5);\n\n    \/\/ pad routing: oscillators -> lowpass -> breathing gain -> master\n    const padGain=AC.createGain(); padGain.gain.value=0.5; padGain.connect(master);\n    const lp=AC.createBiquadFilter(); lp.type='lowpass'; lp.frequency.value=900; lp.Q.value=0.5; lp.connect(padGain);\n\n    const chords=[\n      [130.81,196.00,246.94,329.63], \/\/ C add9 feel\n      [110.00,164.81,220.00,277.18], \/\/ A minor\n      [ 87.31,130.81,174.61,261.63], \/\/ F major7\n      [ 98.00,146.83,196.00,293.66]  \/\/ G\n    ];\n    const oscs=chords[0].map((f,i)=>{\n      const o=AC.createOscillator(), g=AC.createGain();\n      o.type = i%2 ? 'sine' : 'triangle';\n      o.frequency.value=f; o.detune.value=(i-1.5)*4;\n      g.gain.value=0.16; o.connect(g); g.connect(lp); o.start();\n      return o;\n    });\n    \/\/ slow breathing LFO on the pad volume\n    const lfo=AC.createOscillator(), lfoGain=AC.createGain();\n    lfo.frequency.value=0.07; lfoGain.gain.value=0.18;\n    lfo.connect(lfoGain); lfoGain.connect(padGain.gain); lfo.start();\n\n    \/\/ soft noise wash (like distant rain)\n    const noise=AC.createBufferSource(); noise.buffer=makeNoiseBuffer(); noise.loop=true;\n    const nlp=AC.createBiquadFilter(); nlp.type='lowpass'; nlp.frequency.value=650;\n    const ng=AC.createGain(); ng.gain.value=0.022;\n    noise.connect(nlp); nlp.connect(ng); ng.connect(master); noise.start();\n\n    \/\/ drift through chords every 7s\n    let ci=0;\n    const chordTimer=setInterval(()=>{\n      ci=(ci+1)%chords.length; const ch=chords[ci];\n      oscs.forEach((o,i)=>o.frequency.linearRampToValueAtTime(ch[i], AC.currentTime+4.5));\n    },7000);\n    \/\/ gentle random chimes\n    const chimeTimer=setInterval(()=>{ if(Math.random()<0.55) softChime(master); },5200);\n\n    MUSIC.nodes={master,oscs,lfo,noise,chordTimer,chimeTimer};\n  }catch(e){}\n}\nfunction stopMusic(){\n  const n=MUSIC.nodes; if(!n) return;\n  try{\n    clearInterval(n.chordTimer); clearInterval(n.chimeTimer);\n    n.master.gain.linearRampToValueAtTime(0.0001, AC.currentTime+1.2);\n    setTimeout(()=>{ try{ n.oscs.forEach(o=>o.stop()); n.lfo.stop(); n.noise.stop(); }catch(e){} },1300);\n  }catch(e){}\n  MUSIC.nodes=null;\n}\nfunction toggleMusic(){\n  MUSIC.on=!MUSIC.on;\n  const btn=document.getElementById('musicBtn');\n  if (MUSIC.on){ btn.textContent='\ud83c\udfb5'; btn.classList.remove('off'); startMusic(); }\n  else { btn.textContent='\ud83d\udd07'; btn.classList.add('off'); stopMusic(); }\n}\n\n\/\/ ---------- High scores (persisted on device, in-memory fallback) ----------\nconst HS_KEY = 'pixelQuestHighScores';\nlet memScores = [];\n(function initScores(){\n  try { const a = JSON.parse(localStorage.getItem(HS_KEY) || '[]'); if (Array.isArray(a)) memScores = a; }\n  catch(e){}\n})();\nfunction saveScore(score){\n  memScores.push({ score });\n  memScores.sort((a,b)=>b.score-a.score);\n  memScores = memScores.slice(0,5);\n  try { localStorage.setItem(HS_KEY, JSON.stringify(memScores)); } catch(e){}\n}\nfunction isHighScore(score){\n  return memScores.length < 5 || score > memScores[memScores.length-1].score;\n}\nfunction scoresHTML(highlight){\n  if (!memScores.length) return '<div class=\"hs-empty\">No scores yet \u2014 set the first!<\/div>';\n  let used = false;\n  const rows = memScores.map((s,i)=>{\n    let hl = '';\n    if (!used && highlight!=null && s.score===highlight){ hl=' class=\"hl\"'; used=true; }\n    const medal = ['\ud83e\udd47','\ud83e\udd48','\ud83e\udd49'][i] || (i+1);\n    return `<tr${hl}><td>${medal}<\/td><td>${s.score}<\/td><\/tr>`;\n  }).join('');\n  return `<table class=\"hs\"><tr><th>RANK<\/th><th>SCORE<\/th><\/tr>${rows}<\/table>`;\n}\nfunction refreshStartScores(){\n  const el = document.getElementById('hsStart');\n  if (el) el.innerHTML = '<div class=\"hs-title\">HIGH SCORES<\/div>' + scoresHTML();\n}\n\n\/\/ ---------- World data ----------\nconst TILE = 40;\nconst GRAV = 0.50, MOVE = 0.55, MAXVX = 3.6, FRICTION = 0.80, JUMP = -11.6;\n\n\/\/ theme palettes per world\nconst THEMES = {\n  1: { sky:'#6ec0ff', ground:'#7a4f2b', grass:'#5fbf3a', grassDk:'#3f9921', brick:'#c0673a', cloud:'#ffffff', hill:'#9ddb6a' },\n  2: { sky:'#241a3a', ground:'#3a2f4f', grass:'#6a4f8a', grassDk:'#4a3568', brick:'#5a4677', cloud:'#5a4a78', hill:'#3a2d55' },\n  3: { sky:'#0a3a5a', ground:'#1a5a6a', grass:'#2a9aa5', grassDk:'#1c6f78', brick:'#2a7a85', cloud:'#3a8a9a', hill:'#16606a' }\n};\n\n\/*\n  Level layout legend (each string = a column? no \u2014 each string is a ROW from top to bottom)\n  '.' empty   'X' ground\/solid  'B' brick  '?' mystery block  'C' coin\n  'E' walker enemy  'F' flyer enemy  'P' player start  'G' goal flag\n  'S' spike   'M' moving platform  'H' powerup heart block  '=' floating platform\n*\/\nconst LEVELS = [\n{ world:'1-1', time:200, theme:1, rows:[\n\"..............................................................................\",\n\"..............................................................................\",\n\"...........C.C.C..................................CCC.........................\",\n\"..........=====..........?..H..?.........====.....................G...........\",\n\"..................C....................................E.....................\",\n\".............?.......BB.......C.C.C......BB..........====.....................\",\n\"..P.....E...........BBBB.....========...BBBB....F.........E........X..........\",\n\"XXXXX..XXXXXX..XX..XXXXXX............XXXXXXXXX..XXXXX...XXXXXXXXX...XXXXXXXXXXXX\",\n\"XXXXX..XXXXXX..XX..XXXXXX..SS..SS....XXXXXXXXX..XXXXX...XXXXXXXXX...XXXXXXXXXXXX\"\n]},\n{ world:'1-2', time:220, theme:2, rows:[\n\"..............................................................................\",\n\".....C.C...................CCC..................C.C.C........................\",\n\"....=====....?...?...?.....=====.....H.....BB...........====.........G........\",\n\"..............................................BBBB...........................\",\n\"..P......F.......E.....BB.........F........E..........F.....E.....X...........\",\n\".........................BBBB.......BBB...............=====..................\",\n\"XXXXXX....XX..XX....XXXXXXXXX....XX.......XXXXXX...XX........XXXXX...XXXXXXXXXXX\",\n\"XXXXXX.SS.XX..XX.SS.XXXXXXXXX.SS.XX..SSS..XXXXXX...XX..SSSS..XXXXX...XXXXXXXXXXX\"\n]},\n{ world:'1-3', time:240, theme:3, rows:[\n\"..............................................................................\",\n\"...C.C.C..........CCC.............C.C.................CCC.....................\",\n\"..=======...?..?..====....H...?...======....?...?.........====......G........\",\n\".............................................................................\",\n\"..P....F....E.....F.....E....BB.....F....E.....F....BB....E....F....X.........\",\n\"...............BB......BBBB.......BB....BBBB.......BB...........=====.........\",\n\"XXXX...XX..XXXXXXXX...XX....XXXXXX...XX....XXXXXX...XX...XXXXX......XXXXXXXXXXXX\",\n\"XXXX.SS.XX.XXXXXXXX.S.XX.SS.XXXXXX.S.XX.SS.XXXXXX.S.XX.S.XXXXX.SSS..XXXXXXXXXXXX\"\n]}\n];\n\n\/\/ ---------- Game state ----------\nlet G = {};\nfunction newGame(){\n  G = { levelIndex:0, lives:3, score:0, coins:0 };\n  loadLevel(0);\n}\n\nlet level, player, enemies, coinsArr, blocks, solids, spikes, plats, bullets, particles, goal;\nlet cam = 0, timeLeft = 200, timer = 0, state = 'start', winTimer = 0;\n\nfunction loadLevel(i){\n  level = LEVELS[i];\n  const rows = level.rows;\n  const cols = rows[0].length;\n  solids = []; blocks = []; coinsArr = []; enemies = []; spikes = []; plats = []; bullets = []; particles = [];\n  player = null; goal = null;\n\n  for (let r=0; r<rows.length; r++){\n    for (let c=0; c<cols; c++){\n      const ch = rows[r][c];\n      const x = c*TILE, y = r*TILE;\n      if (ch==='X') solids.push({x,y,w:TILE,h:TILE,type:'ground'});\n      else if (ch==='B') blocks.push({x,y,w:TILE,h:TILE,kind:'brick',alive:true});\n      else if (ch==='?') blocks.push({x,y,w:TILE,h:TILE,kind:'q',used:false,item:'coin'});\n      else if (ch==='H') blocks.push({x,y,w:TILE,h:TILE,kind:'q',used:false,item:'heart'});\n      else if (ch==='C') coinsArr.push({x:x+8,y:y+6,w:24,h:28,got:false,pop:0,anim:Math.random()*6});\n      else if (ch==='E') enemies.push(mkEnemy(x,y,'walk'));\n      else if (ch==='F') enemies.push(mkEnemy(x,y-4,'fly'));\n      else if (ch==='S') spikes.push({x:x+4,y:y+24,w:TILE-8,h:16});\n      else if (ch==='=') plats.push({x,y:y+28,w:TILE,h:12});\n      else if (ch==='P') player = mkPlayer(x,y);\n      else if (ch==='G') goal = {x:x+12,y:y-TILE*2,w:16,h:TILE*3+TILE};\n    }\n  }\n  if (!player) player = mkPlayer(80, 200);\n  if (!goal) goal = {x:(cols-3)*TILE,y:H-TILE*4,w:16,h:TILE*3};\n  level.width = cols*TILE;\n\n  timeLeft = level.time; timer = 0; cam = 0; winTimer = 0;\n  setHUD();\n}\n\nfunction mkPlayer(x,y){\n  return { x, y, w:26, h:34, vx:0, vy:0, dir:1, onGround:false,\n           inv:0, shootCD:0, walkAnim:0, big:false };\n}\nfunction mkEnemy(x,y,kind){\n  return { x:x+4, y:y+(kind==='fly'?0:6), w:30, h:30, vx:kind==='fly'? -0.9 : -0.7,\n           vy:0, kind, alive:true, dir:-1, base:y, fly:0, dead:0 };\n}\n\n\/\/ ---------- Collision helpers ----------\nfunction hit(a,b){ return a.x<b.x+b.w && a.x+a.w>b.x && a.y<b.y+b.h && a.y+a.h>b.y; }\n\nfunction solidUnderAt(obj){\n  \/\/ returns true if standing on any solid\/platform\/brick\n  return false;\n}\n\n\/\/ resolve player vs solids\/blocks\/platforms\nfunction moveAndCollide(p){\n  p.onGround = false;\n  \/\/ horizontal\n  p.x += p.vx;\n  for (const s of allSolids()){\n    if (hit(p,s)){\n      if (p.vx>0) p.x = s.x - p.w;\n      else if (p.vx<0) p.x = s.x + s.w;\n      p.vx = 0;\n    }\n  }\n  \/\/ vertical\n  p.y += p.vy;\n  for (const s of allSolids()){\n    if (hit(p,s)){\n      if (p.vy>0){ p.y = s.y - p.h; p.vy = 0; p.onGround = true; }\n      else if (p.vy<0){\n        p.y = s.y + s.h; p.vy = 0;\n        bumpBlock(s);\n      }\n    }\n  }\n  \/\/ one-way platforms (only land from above)\n  for (const pl of plats){\n    if (p.vy>=0 && p.x < pl.x+pl.w && p.x+p.w > pl.x){\n      const prevBottom = p.y + p.h - p.vy;\n      if (prevBottom <= pl.y+6 && p.y+p.h >= pl.y && p.y+p.h <= pl.y+pl.h+8){\n        p.y = pl.y - p.h; p.vy = 0; p.onGround = true;\n      }\n    }\n  }\n}\nfunction allSolids(){\n  const arr = solids.slice();\n  for (const b of blocks) if (b.alive!==false && !(b.kind==='brick'&&b.alive===false)) arr.push(b);\n  return arr;\n}\nfunction bumpBlock(s){\n  if (s.kind==='q' && !s.used){\n    s.used = true; s.bump = 8;\n    if (s.item==='heart'){ G.lives++; sfx.power(); spawnText(s.x+10,s.y-10,'1UP','#ff4d6d'); }\n    else { G.coins++; G.score+=100; sfx.coin();\n           coinsArr.push({x:s.x+8,y:s.y-30,w:24,h:28,got:false,pop:18,anim:0}); }\n    setHUD();\n  } else if (s.kind==='brick'){\n    s.bump = 6; sfx.block();\n  }\n}\n\n\/\/ ---------- Update ----------\nfunction update(){\n  if (state!=='play') return;\n  timer++;\n  if (timer%60===0){ timeLeft--; setHUD(); if (timeLeft<=0){ damage(true); timeLeft=level.time; } }\n\n  const p = player;\n  \/\/ input\n  if (keys.left){ p.vx -= MOVE; p.dir=-1; }\n  if (keys.right){ p.vx += MOVE; p.dir=1; }\n  if (!keys.left && !keys.right) p.vx *= FRICTION;\n  p.vx = Math.max(-MAXVX, Math.min(MAXVX, p.vx));\n  if (Math.abs(p.vx)<0.1) p.vx=0;\n\n  if (keys.jump && p.onGround){ p.vy = JUMP; p.onGround=false; sfx.jump();\n    for (let k=0;k<4;k++) particles.push({x:p.x+p.w\/2,y:p.y+p.h,vx:(Math.random()-.5)*2,vy:Math.random()*2,life:14,c:'#fff'});\n  }\n  if (!keys.jump && p.vy<-5) p.vy = -5; \/\/ variable jump height\n\n  p.vy += GRAV; if (p.vy>16) p.vy=16;\n  moveAndCollide(p);\n\n  if (Math.abs(p.vx)>0.4 && p.onGround) p.walkAnim += Math.abs(p.vx)*0.08; \n\n  \/\/ shoot\n  if (p.shootCD>0) p.shootCD--;\n  if (keys.shoot && p.shootCD<=0){\n    p.shootCD = 16;\n    bullets.push({x:p.x+(p.dir>0?p.w:-8), y:p.y+12, w:10,h:8, vx:p.dir*6, life:80});\n    sfx.shoot();\n  }\n\n  \/\/ bullets\n  for (const b of bullets){\n    b.x += b.vx; b.life--;\n    for (const s of solids) if (hit(b,s)){ b.life=0; }\n    for (const bl of blocks) if (bl.kind==='brick'&&bl.alive!==false&&hit(b,bl)){ b.life=0; }\n    for (const e of enemies){\n      if (e.alive && hit(b,e)){ killEnemy(e); b.life=0; G.score+=150; setHUD(); }\n    }\n  }\n  bullets = bullets.filter(b=>b.life>0);\n\n  \/\/ enemies\n  for (const e of enemies){\n    if (!e.alive){ e.dead++; continue; }\n    if (e.kind==='walk'){\n      e.x += e.vx; e.vy += GRAV; if(e.vy>14)e.vy=14;\n      \/\/ turn at walls\/edges\n      let blocked=false, hasGround=false;\n      for (const s of allSolids()){\n        if (hit({x:e.x,y:e.y,w:e.w,h:e.h}, s)){\n          if (e.vx>0) e.x=s.x-e.w; else e.x=s.x+s.w; blocked=true;\n        }\n      }\n      e.y += e.vy;\n      for (const s of allSolids()){\n        if (hit({x:e.x,y:e.y,w:e.w,h:e.h}, s)){ if(e.vy>0){e.y=s.y-e.h;e.vy=0;} else {e.y=s.y+s.h;e.vy=0;} }\n      }\n      \/\/ edge detection\n      const probe={x: e.vx>0? e.x+e.w+2 : e.x-2, y:e.y+e.h+4, w:4,h:4};\n      for (const s of allSolids()) if (hit(probe,s)) hasGround=true;\n      if (blocked || !hasGround){ e.vx*=-1; e.dir*=-1; }\n    } else { \/\/ fly\n      e.fly += 0.05;\n      e.x += e.vx;\n      e.y = e.base + Math.sin(e.fly)*26;\n      if (e.x < cam-60) { e.vx=Math.abs(e.vx); e.dir=1; }\n      if (e.x > cam+W+60){ e.vx=-Math.abs(e.vx); e.dir=-1; }\n    }\n    \/\/ spikes kill walkers? no. player collision:\n    if (hit(p,e) && e.alive){\n      if (p.vy>1 && p.y+p.h < e.y+e.h*0.6){\n        killEnemy(e); p.vy = JUMP*0.6; G.score+=120; sfx.stomp(); setHUD();\n      } else {\n        damage();\n      }\n    }\n  }\n  enemies = enemies.filter(e=>e.alive||e.dead<40);\n\n  \/\/ coins\n  for (const c of coinsArr){\n    c.anim += 0.15;\n    if (c.pop>0){ c.y -= 1.4; c.pop--; }\n    if (!c.got && (c.pop||0)<=0 && hit(p,c)){ c.got=true; G.coins++; G.score+=50; sfx.coin(); setHUD();\n      spawnText(c.x,c.y,'+50','#ffd23f'); }\n  }\n  coinsArr = coinsArr.filter(c=>!c.got);\n\n  \/\/ spikes\n  for (const s of spikes) if (hit(p,s)) damage();\n\n  \/\/ block bump anim\n  for (const b of blocks){ if (b.bump>0){ b.bump-=1; } }\n\n  \/\/ particles & text\n  for (const pt of particles){ pt.x+=pt.vx; pt.y+=pt.vy; pt.vy+=0.3; pt.life--; }\n  particles = particles.filter(pt=>pt.life>0);\n\n  \/\/ fall in pit\n  if (p.y > H+60) damage(true);\n\n  \/\/ reach goal\n  if (hit(p, goal)){ levelClear(); }\n\n  \/\/ camera follow\n  const target = p.x - W*0.35;\n  cam += (target - cam)*0.12;\n  cam = Math.max(0, Math.min(level.width - W, cam));\n\n  setHUD();\n}\n\nfunction killEnemy(e){ e.alive=false; e.dead=0;\n  for(let k=0;k<6;k++) particles.push({x:e.x+e.w\/2,y:e.y+e.h\/2,vx:(Math.random()-.5)*4,vy:-Math.random()*3,life:20,c:'#fff'});\n}\nfunction spawnText(x,y,txt,c){ particles.push({x,y,txt,c,life:40,vy:-1,vx:0,isText:true}); }\n\nfunction damage(fatal){\n  const p = player;\n  if (!fatal && p.inv>0) return;\n  if (!fatal && p.big){ p.big=false; p.h=34; p.inv=90; sfx.hurt(); return; }\n  \/\/ lose a life\n  G.lives--; sfx.hurt(); setHUD();\n  if (G.lives<=0){ gameOver(); }\n  else { resetPlayer(); }\n}\nfunction resetPlayer(){\n  \/\/ respawn at start of current level\n  const start = level.rows.flatMap((row,r)=>row.split('').map((ch,c)=>ch==='P'?{x:c*TILE,y:r*TILE}:null)).find(Boolean) || {x:80,y:200};\n  player.x=start.x; player.y=start.y; player.vx=0; player.vy=0; player.inv=120; player.big=false; player.h=34;\n  bullets=[]; cam=0;\n}\n\nfunction levelClear(){\n  state='clear'; sfx.win();\n  G.score += timeLeft*10 + 500;\n  if (G.levelIndex < LEVELS.length-1){\n    document.getElementById('winTitle').textContent = 'LEVEL CLEAR!';\n    document.getElementById('winMsg').innerHTML =\n      `Time bonus: <b>${timeLeft*10}<\/b><br>Coins: <b>${G.coins}<\/b> &nbsp; Score: <b>${G.score}<\/b><br>On to world ${LEVELS[G.levelIndex+1].world}!`;\n    document.getElementById('nextBtn').textContent='CONTINUE';\n  } else {\n    saveScore(G.score);\n    document.getElementById('winTitle').textContent = '\ud83c\udfc6 YOU WON! \ud83c\udfc6';\n    document.getElementById('winMsg').innerHTML =\n      `You recovered all the Sunshine Gems!<br>Coins: <b>${G.coins}<\/b><br>Final score: <b>${G.score}<\/b>` +\n      `<div class=\"hs-title\">HIGH SCORES<\/div>` + scoresHTML(G.score);\n    refreshStartScores();\n    document.getElementById('nextBtn').textContent='PLAY AGAIN';\n  }\n  showOverlay('winScreen');\n}\nfunction gameOver(){\n  state='over';\n  const made = isHighScore(G.score);\n  saveScore(G.score);\n  document.getElementById('overMsg').innerHTML =\n    `You collected <b>${G.coins}<\/b> coins.<br>Final score: <b>${G.score}<\/b>` +\n    (made ? `<br><span style=\"color:#ffd23f\">\u2605 New high score! \u2605<\/span>` : '') +\n    `<div class=\"hs-title\">HIGH SCORES<\/div>` + scoresHTML(G.score);\n  refreshStartScores();\n  showOverlay('overScreen');\n}\n\n\/\/ ---------- HUD \/ overlays ----------\nfunction setHUD(){\n  document.getElementById('coins').textContent = G.coins;\n  document.getElementById('score').textContent = G.score;\n  document.getElementById('time').textContent  = timeLeft;\n  document.getElementById('lives').textContent = G.lives;\n  document.getElementById('world').textContent = level ? level.world : '1-1';\n}\nfunction showOverlay(id){\n  document.querySelectorAll('.overlay').forEach(o=>o.classList.remove('show'));\n  document.getElementById(id).classList.add('show');\n}\nfunction hideOverlays(){ document.querySelectorAll('.overlay').forEach(o=>o.classList.remove('show')); }\n\n\/\/ ---------- Render ----------\nfunction draw(){\n  const th = THEMES[level.theme];\n  ctx.fillStyle = th.sky; ctx.fillRect(0,0,W,H);\n\n  \/\/ parallax hills\n  ctx.fillStyle = th.hill;\n  for (let i=0;i<8;i++){\n    const hx = (i*260 - cam*0.3) % (W+260) ; const x = hx<-260? hx+ (W+260): hx;\n    drawHill(x, H-72, 150, 90, th.hill);\n  }\n  \/\/ clouds\n  ctx.fillStyle = th.cloud; ctx.globalAlpha = level.theme===1?0.95:0.5;\n  for (let i=0;i<6;i++){\n    let cx = (i*200 - cam*0.5) % (W+200); if(cx<-200)cx+=W+200;\n    drawCloud(cx, 50 + (i%3)*40);\n  }\n  ctx.globalAlpha=1;\n\n  ctx.save();\n  ctx.translate(-Math.round(cam),0);\n\n  \/\/ platforms (one-way)\n  for (const pl of plats){\n    ctx.fillStyle = th.grassDk; ctx.fillRect(pl.x,pl.y,pl.w,pl.h);\n    ctx.fillStyle = th.grass; ctx.fillRect(pl.x,pl.y,pl.w,4);\n  }\n\n  \/\/ solids \/ ground\n  for (const s of solids){\n    ctx.fillStyle = th.ground; ctx.fillRect(s.x,s.y,s.w,s.h);\n    ctx.fillStyle = th.grass;  ctx.fillRect(s.x,s.y,s.w,10);\n    ctx.fillStyle = th.grassDk; ctx.fillRect(s.x,s.y+10,s.w,3);\n    \/\/ little dirt texture\n    ctx.fillStyle='rgba(0,0,0,.12)';\n    ctx.fillRect(s.x+6,s.y+18,5,5); ctx.fillRect(s.x+24,s.y+28,5,5);\n  }\n\n  \/\/ blocks\n  for (const b of blocks){\n    if (b.alive===false) continue;\n    const by = b.y - (b.bump||0);\n    if (b.kind==='brick'){\n      ctx.fillStyle = th.brick; ctx.fillRect(b.x,by,b.w,b.h);\n      ctx.strokeStyle='rgba(0,0,0,.3)'; ctx.lineWidth=2;\n      ctx.strokeRect(b.x+2,by+2,b.w-4,b.h-4);\n      ctx.beginPath(); ctx.moveTo(b.x,by+b.h\/2); ctx.lineTo(b.x+b.w,by+b.h\/2);\n      ctx.moveTo(b.x+b.w\/2,by); ctx.lineTo(b.x+b.w\/2,by+b.h\/2); ctx.stroke();\n    } else {\n      ctx.fillStyle = b.used? '#8a6a3a' : '#ffb02e';\n      ctx.fillRect(b.x,by,b.w,b.h);\n      ctx.fillStyle='rgba(255,255,255,.25)'; ctx.fillRect(b.x+3,by+3,b.w-6,4);\n      ctx.strokeStyle='rgba(0,0,0,.35)'; ctx.lineWidth=2; ctx.strokeRect(b.x+2,by+2,b.w-4,b.h-4);\n      if (!b.used){\n        ctx.fillStyle='#fff'; ctx.font='bold 24px Courier New'; ctx.textAlign='center';\n        ctx.fillText(b.item==='heart'?'\u2665':'?', b.x+b.w\/2, by+28);\n        ctx.textAlign='left';\n      }\n    }\n  }\n\n  \/\/ spikes\n  for (const s of spikes){\n    ctx.fillStyle='#cfd8e0';\n    const n=Math.floor(s.w\/10);\n    for (let i=0;i<n;i++){\n      ctx.beginPath();\n      ctx.moveTo(s.x+i*10, s.y+s.h);\n      ctx.lineTo(s.x+i*10+5, s.y);\n      ctx.lineTo(s.x+i*10+10, s.y+s.h);\n      ctx.fill();\n    }\n    ctx.fillStyle='#7a8794'; ctx.fillRect(s.x,s.y+s.h-3,s.w,3);\n  }\n\n  \/\/ coins (spinning, with rim + shine)\n  for (const c of coinsArr){\n    const sw = Math.abs(Math.cos(c.anim))*c.w + 4;\n    const cx = c.x+c.w\/2, cy = c.y+c.h\/2;\n    ctx.fillStyle='#c8881a';\n    ctx.beginPath(); ctx.ellipse(cx, cy, sw\/2, c.h\/2, 0,0,Math.PI*2); ctx.fill();\n    ctx.fillStyle='#ffd23f';\n    ctx.beginPath(); ctx.ellipse(cx, cy, Math.max(1,sw\/2-2), c.h\/2-2, 0,0,Math.PI*2); ctx.fill();\n    ctx.fillStyle='#e0a821';\n    ctx.beginPath(); ctx.ellipse(cx, cy, Math.max(1,sw\/4), c.h\/3, 0,0,Math.PI*2); ctx.fill();\n    if (sw > c.w*0.6){\n      ctx.fillStyle='rgba(255,255,255,.85)';\n      ctx.fillRect(cx-2, cy-c.h\/3, 2, c.h*0.5);\n    }\n  }\n\n  \/\/ goal flag\n  drawFlag(goal, th);\n\n  \/\/ enemies\n  for (const e of enemies) drawEnemy(e);\n\n  \/\/ bullets\n  for (const b of bullets){\n    ctx.fillStyle='#ffec80';\n    ctx.beginPath(); ctx.arc(b.x+b.w\/2,b.y+b.h\/2, 6,0,Math.PI*2); ctx.fill();\n    ctx.fillStyle='#ff7a3c';\n    ctx.beginPath(); ctx.arc(b.x+b.w\/2,b.y+b.h\/2, 3,0,Math.PI*2); ctx.fill();\n  }\n\n  \/\/ player\n  drawPlayer(player);\n\n  \/\/ particles\n  for (const pt of particles){\n    if (pt.isText){\n      ctx.fillStyle=pt.c; ctx.font='bold 16px Courier New'; ctx.textAlign='center';\n      ctx.globalAlpha = Math.min(1, pt.life\/20);\n      ctx.fillText(pt.txt, pt.x, pt.y); ctx.globalAlpha=1; ctx.textAlign='left';\n    } else {\n      ctx.fillStyle=pt.c; ctx.globalAlpha=Math.min(1,pt.life\/14);\n      ctx.fillRect(pt.x,pt.y,4,4); ctx.globalAlpha=1;\n    }\n  }\n\n  ctx.restore();\n}\n\nfunction drawHill(x,y,w,h){\n  ctx.beginPath(); ctx.moveTo(x,y+h); ctx.quadraticCurveTo(x+w\/2,y-10,x+w,y+h); ctx.fill();\n}\nfunction drawCloud(x,y){\n  ctx.beginPath();\n  ctx.arc(x,y,16,0,Math.PI*2); ctx.arc(x+18,y+4,20,0,Math.PI*2);\n  ctx.arc(x+40,y,16,0,Math.PI*2); ctx.arc(x+20,y+12,18,0,Math.PI*2); ctx.fill();\n}\nfunction drawFlag(g,th){\n  ctx.fillStyle='#cfcfcf'; ctx.fillRect(g.x+g.w\/2-3, g.y, 6, g.h); \/\/ pole\n  ctx.fillStyle='#ffd23f'; ctx.beginPath();\n  ctx.arc(g.x+g.w\/2, g.y, 7,0,Math.PI*2); ctx.fill();\n  const fw = 34, fy = g.y+10;\n  ctx.fillStyle='#ff4d6d';\n  ctx.beginPath();\n  ctx.moveTo(g.x+g.w\/2, fy);\n  ctx.lineTo(g.x+g.w\/2+fw, fy+12);\n  ctx.lineTo(g.x+g.w\/2, fy+24);\n  ctx.fill();\n}\n\n\/\/ ===== Pixel-art sprite engine =====\n\/\/ draw a sprite defined as an array of equal-length strings, using a palette map.\nfunction px(grid, pal, dx, dy, scale, flip){\n  const cols = grid[0].length;\n  for (let r=0; r<grid.length; r++){\n    const row = grid[r];\n    for (let c=0; c<cols; c++){\n      const ch = row[c];\n      if (ch===' ' || ch==='.') continue;\n      const col = pal[ch];\n      if (!col) continue;\n      const cc = flip ? (cols-1-c) : c;\n      ctx.fillStyle = col;\n      ctx.fillRect(Math.round(dx+cc*scale), Math.round(dy+r*scale), scale, scale);\n    }\n  }\n}\n\n\/\/ ---- Hero sprite (original explorer mascot) ----\nconst PAL_HERO = {\n  C:'#e23b2e', b:'#9c1d18', H:'#5a3a1a',\n  S:'#ffc89e', s:'#e09a63',\n  E:'#1a1a1a', w:'#ffffff',\n  G:'#34c759', g:'#1f9b3e',\n  O:'#3a6fe0', o:'#274fae', Y:'#ffd23f', K:'#5a3414'\n};\nconst HERO_TOP = [\n  \"...CCCCCC....\",\n  \"..CCCCCCCC...\",\n  \".bCCCCCCCCb..\",\n  \".HHSSSSSSSb..\",\n  \".HSSSSSSSSS..\",\n  \".HSSSSEwSS...\",\n  \".HSSSSEwSs...\",\n  \"..SSSSSSSS...\",\n  \"..GgGGGGgG...\",\n  \".GGGGGGGGGG..\",\n  \".GOOYOOOYOOG.\",\n  \".OOOOOOOOOOO.\",\n  \".oOOOOOOOOo..\"\n];\nfunction drawHeroLegs(dx,dy,sc,phase,jumping){\n  const legY = dy + 13*sc;\n  ctx.fillStyle = PAL_HERO.O;\n  if (jumping){\n    ctx.fillRect(dx+1*sc, legY, 3*sc, 2*sc);\n    ctx.fillRect(dx+9*sc, legY, 3*sc, 2*sc);\n    ctx.fillStyle = PAL_HERO.K;\n    ctx.fillRect(dx+0*sc, legY+2*sc, 4*sc, 2*sc);\n    ctx.fillRect(dx+9*sc, legY+2*sc, 4*sc, 2*sc);\n  } else if (phase===1){\n    ctx.fillRect(dx+3*sc, legY, 3*sc, 2*sc);\n    ctx.fillRect(dx+7*sc, legY, 3*sc, 2*sc);\n    ctx.fillStyle = PAL_HERO.K;\n    ctx.fillRect(dx+4*sc, legY+2*sc, 4*sc, 2*sc);\n    ctx.fillRect(dx+5*sc, legY+2*sc, 4*sc, 2*sc);\n  } else {\n    ctx.fillRect(dx+2*sc, legY, 3*sc, 2*sc);\n    ctx.fillRect(dx+8*sc, legY, 3*sc, 2*sc);\n    ctx.fillStyle = PAL_HERO.K;\n    ctx.fillRect(dx+1*sc, legY+2*sc, 4*sc, 2*sc);\n    ctx.fillRect(dx+8*sc, legY+2*sc, 4*sc, 2*sc);\n  }\n}\n\n\/\/ ---- Enemy sprites ----\nconst PAL_SPROUT = {\n  R:'#d8412e', r:'#a82a1c', t:'#3a6a1f', T:'#4f8a28',\n  w:'#ffffff', E:'#1a1a1a', f:'#6a3414', m:'#7a1810'\n};\nconst SPROUT = [\n  \".....TtT.....\",\n  \"....TtTtT....\",\n  \"..RRRRRRRRR..\",\n  \".RRRRRRRRRRR.\",\n  \".RRRRRRRRRRR.\",\n  \"RRwwRRRRRwwRR\",\n  \"RRwERRRRRwERR\",\n  \"RRwwRRRRRwwRR\",\n  \"RRRRRRRRRRRRR\",\n  \".RRRRmmmRRRR.\",\n  \".RRRRRRRRRRR.\",\n  \"..rRRRRRRRr..\",\n  \"..ff.....ff..\",\n  \".fff.....fff.\"\n];\nconst PAL_BAT = {\n  P:'#7d5bb0', p:'#5a3f87', d:'#3f2c63',\n  w:'#ffffff', E:'#e24b4a', f:'#caa6ef'\n};\nfunction drawEnemy(e){\n  const x=e.x, y=e.y, sc=2;\n  if (!e.alive){ \/\/ squashed pancake\n    ctx.fillStyle = e.kind==='walk' ? '#a82a1c' : '#5a3f87';\n    ctx.fillRect(x+2, y+e.h-7, e.w-4, 7);\n    ctx.fillStyle = e.kind==='walk' ? '#d8412e' : '#7d5bb0';\n    ctx.fillRect(x+4, y+e.h-7, e.w-8, 3);\n    return;\n  }\n  if (e.kind==='walk'){\n    \/\/ shuffle feet by walk phase; sprite is 13 wide x14 tall -> 26x28, center in 30x30\n    const phase = Math.floor(e.x\/6)%2;\n    const ox = x + 2, oy = y + 2;\n    const sp = SPROUT.slice();\n    \/\/ animate feet rows by swapping\n    if (phase===1){\n      sp[12] = \"...ff.ff.....\";\n      sp[13] = \"..fff.fff....\";\n    }\n    px(sp, PAL_SPROUT, ox, oy, sc, e.dir>0);\n  } else {\n    \/\/ flying bat: body + flapping wings\n    const flap = Math.sin(e.fly*5);\n    const cx = x+e.w\/2, cy = y+e.h\/2;\n    \/\/ wings\n    ctx.fillStyle = PAL_BAT.p;\n    const wy = flap*7;\n    ctx.beginPath();\n    ctx.moveTo(cx-3, cy);\n    ctx.lineTo(x-7, cy-6+wy); ctx.lineTo(x-2, cy-1+wy*0.4);\n    ctx.lineTo(x-7, cy+5+wy*0.6); ctx.closePath(); ctx.fill();\n    ctx.beginPath();\n    ctx.moveTo(cx+3, cy);\n    ctx.lineTo(x+e.w+7, cy-6+wy); ctx.lineTo(x+e.w+2, cy-1+wy*0.4);\n    ctx.lineTo(x+e.w+7, cy+5+wy*0.6); ctx.closePath(); ctx.fill();\n    \/\/ body\n    ctx.fillStyle = PAL_BAT.P;\n    ctx.beginPath(); ctx.arc(cx, cy, 11, 0, Math.PI*2); ctx.fill();\n    ctx.fillStyle = PAL_BAT.d;\n    ctx.beginPath(); ctx.arc(cx, cy+3, 11, 0.15*Math.PI, 0.85*Math.PI); ctx.fill();\n    \/\/ ears\n    ctx.fillStyle = PAL_BAT.P;\n    ctx.beginPath(); ctx.moveTo(cx-7,cy-8); ctx.lineTo(cx-3,cy-14); ctx.lineTo(cx-1,cy-7); ctx.fill();\n    ctx.beginPath(); ctx.moveTo(cx+7,cy-8); ctx.lineTo(cx+3,cy-14); ctx.lineTo(cx+1,cy-7); ctx.fill();\n    \/\/ eyes + fangs\n    ctx.fillStyle='#fff';\n    ctx.fillRect(cx-7, cy-3, 5,6); ctx.fillRect(cx+2, cy-3, 5,6);\n    ctx.fillStyle = PAL_BAT.E;\n    ctx.fillRect(cx-6+(e.dir>0?2:0), cy-1, 3,3); ctx.fillRect(cx+3+(e.dir>0?2:0), cy-1, 3,3);\n    ctx.fillStyle='#fff';\n    ctx.fillRect(cx-3, cy+7, 2,3); ctx.fillRect(cx+1, cy+7, 2,3); \/\/ fangs\n  }\n}\n\nfunction drawPlayer(p){\n  if (p.inv>0 && Math.floor(p.inv\/4)%2===0) return; \/\/ blink while invincible\n  const sc = 2;\n  const moving = p.onGround && Math.abs(p.vx) > 0.6;\n  const jumping = !p.onGround;\n  const flip = p.dir < 0;\n  \/\/ big mode = taller scale on top sprite only\n  px(HERO_TOP, PAL_HERO, p.x, p.y, sc, flip);\n  let phase = 0;\n  if (moving) phase = (Math.floor(p.walkAnim*2) % 2) === 0 ? 0 : 1;\n  drawHeroLegs(p.x, p.y, sc, phase, jumping);\n  \/\/ muzzle flash when shooting\n  if (p.shootCD>11){\n    ctx.fillStyle='#ffec80';\n    const mx = flip ? p.x-6 : p.x+p.w-2;\n    ctx.fillRect(mx, p.y+16, 8, 6);\n    ctx.fillStyle='#ff7a3c';\n    ctx.fillRect(mx+(flip?0:2), p.y+18, 4, 3);\n  }\n}\n\n\/\/ ---------- Loop ----------\nfunction loop(){\n  update();\n  if (state==='play'||state==='clear'||state==='over'){ draw(); }\n  requestAnimationFrame(loop);\n}\n\n\/\/ ---------- Buttons ----------\ndocument.getElementById('startBtn').onclick = ()=>{ if(AC&&AC.state==='suspended')AC.resume(); newGame(); hideOverlays(); state='play'; if(MUSIC.on) startMusic(); };\ndocument.getElementById('retryBtn').onclick = ()=>{ newGame(); hideOverlays(); state='play'; if(MUSIC.on) startMusic(); };\ndocument.getElementById('nextBtn').onclick = ()=>{\n  if (G.levelIndex < LEVELS.length-1){ G.levelIndex++; loadLevel(G.levelIndex); hideOverlays(); state='play'; }\n  else { newGame(); hideOverlays(); state='play'; }\n};\n\ndocument.getElementById('musicBtn').onclick = ()=>{ toggleMusic(); };\n\n\/\/ fit canvas to screen keeping aspect\nfunction fit(){\n  const ar = W\/H;\n  const vis = el => el && getComputedStyle(el).display !== 'none';\n  const top = document.getElementById('ad-top');\n  const bot = document.getElementById('ad-bottom');\n  const left = document.getElementById('ad-left');\n  const right = document.getElementById('ad-right');\n  const touchBar = document.getElementById('touch');\n\n  \/\/ reserve space taken by visible ad slots (+ gaps)\n  let reservedH = 0, reservedW = 0;\n  if (vis(top))   reservedH += top.offsetHeight + 8;\n  if (vis(bot))   reservedH += bot.offsetHeight + 8;\n  if (vis(touchBar)) reservedH += touchBar.offsetHeight + 6;\n  if (vis(left))  reservedW += left.offsetWidth + 10;\n  if (vis(right)) reservedW += right.offsetWidth + 10;\n\n  let availW = Math.max(160, window.innerWidth  - reservedW - 16);\n  let availH = Math.max(120, window.innerHeight - reservedH - 16);\n\n  let w = availW, h = availH;\n  if (w\/h > ar) w = h*ar; else h = w\/ar;\n  cv.style.width = Math.floor(w)+'px';\n  cv.style.height = Math.floor(h)+'px';\n}\naddEventListener('resize', fit);\n\/\/ run once now and again after layout settles (ad slot sizes)\nfit(); requestAnimationFrame(fit); setTimeout(fit, 100);\n\n\/\/ boot with a placeholder level for the menu backdrop\nlevel = LEVELS[0];\nloadLevel(0); state='start';\nrefreshStartScores();\nloop();\n<\/script>\n<\/body>\n<\/html>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Pixel Quest \u2014 Adventure Platformer ADVERTISEMENT AD 0 SCORE 0 TIME 200 \u2665 3 WORLD 1-1 \ud83c\udfb5 PIXEL QUEST The Sunshine Gems were stolen by the Grumble King. Run, jump and blast your way through three worlds to win them back! \u2190\u2192 or AD &nbsp;move &nbsp;\u2022&nbsp; \u2191 \/ W \/ Space jump &nbsp;\u2022&nbsp; Z shoot [&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":"page-builder","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":"disabled","ast-breadcrumbs-content":"","ast-featured-img":"disabled","footer-sml-layout":"","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":"set","ast-page-background-enabled":"default","ast-page-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-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-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-gradient":""}},"ast-content-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-gradient":""},"tablet":{"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-gradient":""},"mobile":{"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-gradient":""}},"footnotes":""},"class_list":["post-40541","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/pradeepaggarwal.com\/wps\/wp-json\/wp\/v2\/pages\/40541","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pradeepaggarwal.com\/wps\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/pradeepaggarwal.com\/wps\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/pradeepaggarwal.com\/wps\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pradeepaggarwal.com\/wps\/wp-json\/wp\/v2\/comments?post=40541"}],"version-history":[{"count":4,"href":"https:\/\/pradeepaggarwal.com\/wps\/wp-json\/wp\/v2\/pages\/40541\/revisions"}],"predecessor-version":[{"id":40546,"href":"https:\/\/pradeepaggarwal.com\/wps\/wp-json\/wp\/v2\/pages\/40541\/revisions\/40546"}],"wp:attachment":[{"href":"https:\/\/pradeepaggarwal.com\/wps\/wp-json\/wp\/v2\/media?parent=40541"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}