{"id":26173,"date":"2025-10-12T21:40:55","date_gmt":"2025-10-12T12:40:55","guid":{"rendered":"http:\/\/www.tyosuke20xx.com\/blog\/?p=26173"},"modified":"2025-10-12T21:41:52","modified_gmt":"2025-10-12T12:41:52","slug":"%e3%83%96%e3%83%a9%e3%82%a6%e3%82%b6%e3%82%b2%e3%83%bc%e3%83%a0-html","status":"publish","type":"post","link":"http:\/\/www.tyosuke20xx.com\/blog\/?p=26173","title":{"rendered":"\u30d6\u30e9\u30a6\u30b6\u30b2\u30fc\u30e0.html"},"content":{"rendered":"\n<pre class=\"wp-block-code\"><code>&lt;!DOCTYPE html>\n&lt;html lang=\"ja\">\n&lt;head>\n  &lt;meta charset=\"utf-8\" \/>\n  &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\" \/>\n  &lt;title>Endless Dodge ULTRA - Bullet &amp; Boss&lt;\/title>\n  &lt;style>\n    :root{\n      --bg1:#070816; --bg2:#0f1b38; --accent:#6ee7ff; --accent2:#9bffb7; --danger:#ff6b6b; --panel:rgba(255,255,255,.08);\n      --text:#eaf2ff; --muted:#b5c0d0; --gold:#ffd166; --purple:#c4a7ff; --emerald:#86efac;\n    }\n    *{box-sizing:border-box}\n    html,body{height:100%;}\n    body{ margin:0; font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans JP\";\n      color:var(--text);\n      background: radial-gradient(1200px 800px at 20% 10%, #1b2444 0%, var(--bg1) 50%), linear-gradient(160deg, var(--bg2), var(--bg1));\n      overflow:hidden;}\n    .wrap{position:fixed; inset:0; display:grid; grid-template-rows:auto 1fr auto;}\n    header, footer{display:flex; gap:.75rem; align-items:center; justify-content:space-between; padding:.6rem .9rem; backdrop-filter: blur(6px); background:linear-gradient( to bottom, rgba(255,255,255,.06), rgba(255,255,255,.02)); border-bottom:1px solid rgba(255,255,255,.08)}\n    header h1{font-size:1rem; margin:0; letter-spacing:.05em; font-weight:700}\n    header .right{display:flex; gap:.5rem; align-items:center}\n    .pill{ pointer-events:auto; border:1px solid rgba(255,255,255,.14); background:var(--panel); padding:.5rem .8rem; border-radius:999px; font-size:.9rem; color:var(--text); cursor:pointer; user-select:none; transition:transform .08s ease}\n    .pill:active{ transform:scale(.97)}\n    #gamePanel{ position:relative; display:grid; place-items:center;}\n    canvas{ width: min(94vw, 800px); aspect-ratio: 9\/16; border-radius: 18px; box-shadow: 0 10px 40px rgba(0,0,0,.5), inset 0 0 0 1px rgba(255,255,255,.06);\n      background: radial-gradient(600px 500px at 50% 10%, rgba(110,231,255,.12), transparent 60%), linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,.02));}\n    .hud{ position:absolute; inset:0; pointer-events:none;}\n    .row{ display:flex; justify-content:space-between; align-items:center; padding:10px;}\n    .score{ font-variant-numeric: tabular-nums; font-size: clamp(18px, 3.5vw, 28px); text-shadow:0 1px 0 rgba(0,0,0,.5)}\n    .muted{ color: var(--muted)}\n    .center{ position:absolute; inset:0; display:grid; place-items:center;}\n    .card{ pointer-events:auto; background:rgba(7,8,22,.92); border:1px solid rgba(255,255,255,.14); border-radius:16px; padding:20px; width:min(92vw, 480px); box-shadow:0 20px 60px rgba(0,0,0,.6)}\n    .card h2{ margin:0 0 8px; font-size:1.25rem}\n    .card p{ margin:.25rem 0; color:var(--muted)}\n    .btn{ display:inline-flex; align-items:center; justify-content:center; gap:.5rem; padding:.7rem 1rem; border-radius:12px; border:1px solid rgba(255,255,255,.16); background:linear-gradient(180deg, rgba(255,255,255,.12), rgba(255,255,255,.06)); color:var(--text); cursor:pointer; font-weight:600}\n    .btn:hover{ filter:brightness(1.08)}\n    .btn.primary{ border-color: rgba(110,231,255,.5); box-shadow: 0 0 30px rgba(110,231,255,.15) inset}\n    .grid{ display:grid; grid-template-columns:1fr 1fr; gap:.6rem}\n    .touch{ position:absolute; inset:auto 0 10px 0; display:flex; justify-content:center; gap:12px; pointer-events:auto}\n    .touch button{ width:clamp(64px, 22vw, 106px); aspect-ratio:1\/1; border-radius:16px; border:1px solid rgba(255,255,255,.14); background:var(--panel); color:var(--text); font-weight:700; font-size:clamp(16px, 4.5vw, 22px); text-shadow:0 1px 0 rgba(0,0,0,.35)}\n    .badge{border:1px solid rgba(255,255,255,.14); background:var(--panel); padding:.35rem .6rem; border-radius:999px; font-size:.75rem}\n    .toast{ position:absolute; left:50%; top:14%; transform:translateX(-50%); pointer-events:none; opacity:0; transition: opacity .2s, transform .2s; background:rgba(0,0,0,.5); border:1px solid rgba(255,255,255,.18); padding:.35rem .7rem; border-radius:10px; font-weight:700}\n    .toast.show{ opacity:1; transform:translate(-50%, -6px)}\n    footer{ border-top:1px solid rgba(255,255,255,.08); border-bottom:none; justify-content:center}\n    a{ color:var(--accent)}\n    dialog{ border:none; border-radius:16px; background:rgba(7,8,22,.96); color:var(--text); width:min(92vw,560px); }\n    dialog::backdrop{ background:rgba(0,0,0,.6); }\n    .field{ display:flex; justify-content:space-between; align-items:center; gap:10px; padding:8px 0; }\n    .range{ width:58% }\n    .switch{ appearance:none; width:42px; height:24px; border-radius:999px; background:#445; position:relative; outline:none; cursor:pointer; }\n    .switch:checked{ background:#2aa }\n    .switch::after{ content:\"\"; position:absolute; top:3px; left:3px; width:18px; height:18px; border-radius:50%; background:#fff; transition:left .15s}\n    .switch:checked::after{ left:21px }\n    .shop-item{ display:grid; grid-template-columns:1fr auto; gap:.4rem; align-items:center; padding:.5rem; border:1px solid rgba(255,255,255,.12); border-radius:12px; margin:.35rem 0; }\n    .chip{ padding:.2rem .5rem; border:1px solid rgba(255,255,255,.16); border-radius:999px; font-size:.75rem; }\n  &lt;\/style>\n&lt;\/head>\n&lt;body>\n  &lt;div class=\"wrap\">\n    &lt;header>\n      &lt;h1>Endless Dodge &lt;span class=\"badge\">ULTRA&lt;\/span>&lt;\/h1>\n      &lt;div class=\"right\">\n        &lt;span class=\"badge\">\ud83d\udc8e &lt;span id=\"wallet\">0&lt;\/span>&lt;\/span>\n        &lt;button id=\"btnShop\" class=\"pill\" aria-label=\"shop\">\ud83d\uded2 \u30b7\u30e7\u30c3\u30d7&lt;\/button>\n        &lt;button id=\"btnSkins\" class=\"pill\" aria-label=\"skins\">\ud83c\udfa8 \u30b9\u30ad\u30f3&lt;\/button>\n        &lt;button id=\"btnPause\" class=\"pill\" aria-label=\"pause\">\u23f8&lt;\/button>\n        &lt;button id=\"btnSound\" class=\"pill\" aria-label=\"sound\">\ud83d\udd0a&lt;\/button>\n        &lt;button id=\"btnSettings\" class=\"pill\" aria-label=\"settings\">\u2699&lt;\/button>\n      &lt;\/div>\n    &lt;\/header>\n    &lt;div id=\"gamePanel\">\n      &lt;canvas id=\"game\" width=\"360\" height=\"640\" aria-label=\"game canvas\">&lt;\/canvas>\n      &lt;div class=\"hud\">\n        &lt;div class=\"row\">\n          &lt;div class=\"score\">\n            &lt;span id=\"score\">0&lt;\/span> pts\n            \u00b7 &lt;span class=\"muted\">Best:&lt;\/span> &lt;span id=\"best\">0&lt;\/span>\n            \u00b7 &lt;span class=\"muted\">Combo:&lt;\/span> &lt;span id=\"combo\">x1.0&lt;\/span>\n            \u00b7 &lt;span class=\"muted\">Stage:&lt;\/span> &lt;span id=\"stage\">1&lt;\/span>\n          &lt;\/div>\n          &lt;div class=\"row\" style=\"gap:.5rem\">\n            &lt;span class=\"badge\" id=\"badges\">\u26e8 0 \u00b7 \ud83e\uddf2 0 \u00b7 \u23f3 0&lt;\/span>\n          &lt;\/div>\n        &lt;\/div>\n        &lt;div class=\"center\" id=\"overlayStart\">\n          &lt;div class=\"card\">\n            &lt;h2>\u907f\u3051\u3066\u3001\u6483\u3063\u3066\u3001\u5f37\u5316\u3057\u3066\u3001\u30dc\u30b9\u3092\u5012\u305b\uff01&lt;\/h2>\n            &lt;p>\u2190 \u2192 \/ A\u30fbD \u3067\u79fb\u52d5\u3002&lt;strong>Space\u3067\u30b7\u30e7\u30c3\u30c8&lt;\/strong>\u3001&lt;kbd>Shift&lt;\/kbd>\u3067\u30c0\u30c3\u30b7\u30e5\uff08\u7121\u65750.4s\uff09\u3002&lt;\/p>\n            &lt;p>\u30d1\u30ef\u30fc\u30a2\u30c3\u30d7\uff1a\u26e8\u30b7\u30fc\u30eb\u30c9 \/ \ud83e\uddf2\u30de\u30b0\u30cd\u30c3\u30c8 \/ \u23f3\u30b9\u30ed\u30a6\u3002\u30b3\u30f3\u30dc\u3067\u30b9\u30b3\u30a2\u500d\u7387UP\u3002&lt;\/p>\n            &lt;p>\u30b9\u30c6\u30fc\u30b8\u3054\u3068\u306b\u30dc\u30b9\u6226\u3002\u30dc\u30b9\u306f\u5f3e\u5e55\u3092\u767a\u5c04\u3002\u30b7\u30e7\u30c3\u30c8\u3067HP\u3092\u524a\u308d\u3046\u3002&lt;\/p>\n            &lt;div class=\"grid\" style=\"margin-top:10px\">\n              &lt;button class=\"btn primary\" id=\"btnStart\">\u25b6 \u30b2\u30fc\u30e0\u958b\u59cb&lt;\/button>\n              &lt;button class=\"btn\" id=\"btnHow\">\u2753 \u64cd\u4f5c&lt;\/button>\n            &lt;\/div>\n            &lt;div style=\"margin-top:10px\" class=\"muted\" id=\"missions\">&lt;\/div>\n          &lt;\/div>\n        &lt;\/div>\n        &lt;div class=\"center\" id=\"overlayBoss\" style=\"display:none\">\n          &lt;div class=\"card\" style=\"text-align:center\">\n            &lt;h2>\u26a0 B O S S \u26a0&lt;\/h2>\n            &lt;p>\u5f3e\u5e55\u3092\u907f\u3051\u3064\u3064\u3001Space\u3067\u6483\u3066\uff01Shift\u30c0\u30c3\u30b7\u30e5\u3082\u6d3b\u7528\u3002&lt;\/p>\n            &lt;button class=\"btn primary\" id=\"btnBossGo\">\u6226\u95d8\u958b\u59cb&lt;\/button>\n          &lt;\/div>\n        &lt;\/div>\n        &lt;div class=\"center\" id=\"overlayGameOver\" style=\"display:none\">\n          &lt;div class=\"card\">\n            &lt;h2>\u30b2\u30fc\u30e0\u30aa\u30fc\u30d0\u30fc&lt;\/h2>\n            &lt;p>\u30b9\u30b3\u30a2: &lt;strong id=\"finalScore\">0&lt;\/strong> \/ \u30d9\u30b9\u30c8: &lt;strong id=\"finalBest\">0&lt;\/strong> \/ \ud83d\udc8e&lt;strong id=\"earned\">0&lt;\/strong>&lt;\/p>\n            &lt;p>\u9054\u6210\uff1a&lt;span id=\"finalMissions\" class=\"muted\">-&lt;\/span>&lt;\/p>\n            &lt;div class=\"grid\" style=\"margin-top:10px\">\n              &lt;button class=\"btn primary\" id=\"btnRetry\">\u21bb \u30ea\u30c8\u30e9\u30a4&lt;\/button>\n              &lt;button class=\"btn\" id=\"btnHome\">\u2302 \u30bf\u30a4\u30c8\u30eb&lt;\/button>\n            &lt;\/div>\n          &lt;\/div>\n        &lt;\/div>\n        &lt;div class=\"touch\" id=\"touchControls\" aria-hidden=\"true\">\n          &lt;button id=\"leftBtn\" aria-label=\"left\">\u27f5&lt;\/button>\n          &lt;button id=\"dashBtn\" aria-label=\"dash\">\u21e7&lt;\/button>\n          &lt;button id=\"rightBtn\" aria-label=\"right\">\u27f6&lt;\/button>\n        &lt;\/div>\n        &lt;div class=\"toast\" id=\"toast\">Ready&lt;\/div>\n      &lt;\/div>\n    &lt;\/div>\n    &lt;footer>\n      &lt;small class=\"muted\">\u00a9 2025 Endless Dodge ULTRA \u00b7 \u56f3\u5f62\u306e\u307f \u00b7 \u30ed\u30fc\u30ab\u30eb\u4fdd\u5b58(\u8a2d\u5b9a\/\u9032\u884c\/\u30a6\u30a9\u30ec\u30c3\u30c8\/\u5b9f\u7e3e)&lt;\/small>\n    &lt;\/footer>\n  &lt;\/div>\n\n  &lt;!-- Settings \/ Shop \/ Skins (unchanged structure) -->\n  &lt;dialog id=\"dlgSettings\">\n    &lt;form method=\"dialog\" style=\"padding:16px\">\n      &lt;h3 style=\"margin:0 0 8px\">\u8a2d\u5b9a&lt;\/h3>\n      &lt;div class=\"field\">&lt;span>\u96e3\u6613\u5ea6\uff08\u901f\u5ea6\u500d\u7387\uff09&lt;\/span>&lt;input class=\"range\" id=\"rangeSpeed\" type=\"range\" min=\"0.8\" max=\"1.6\" step=\"0.05\">&lt;\/div>\n      &lt;div class=\"field\">&lt;span>\u753b\u9762\u30b7\u30a7\u30a4\u30af&lt;\/span>&lt;input id=\"chkShake\" class=\"switch\" type=\"checkbox\">&lt;\/div>\n      &lt;div class=\"field\">&lt;span>\u8272\u5f31\u30e2\u30fc\u30c9\uff08\u9ad8\u30b3\u30f3\u30c8\u30e9\u30b9\u30c8\uff09&lt;\/span>&lt;input id=\"chkCB\" class=\"switch\" type=\"checkbox\">&lt;\/div>\n      &lt;div class=\"field\">&lt;span>\u7701\u30a8\u30cd\u63cf\u753b\uff08\u2605\u6570\u6e1b\u5c11\uff09&lt;\/span>&lt;input id=\"chkEco\" class=\"switch\" type=\"checkbox\">&lt;\/div>\n      &lt;div class=\"field\">&lt;span>\u64cd\u4f5c\u30d2\u30f3\u30c8\u306e\u8868\u793a&lt;\/span>&lt;input id=\"chkHints\" class=\"switch\" type=\"checkbox\">&lt;\/div>\n      &lt;div style=\"display:flex; gap:.5rem; justify-content:flex-end; margin-top:10px\">\n        &lt;button class=\"btn\" value=\"cancel\">\u9589\u3058\u308b&lt;\/button>\n        &lt;button class=\"btn primary\" id=\"btnSaveSettings\" value=\"default\">\u4fdd\u5b58&lt;\/button>\n      &lt;\/div>\n    &lt;\/form>\n  &lt;\/dialog>\n\n  &lt;dialog id=\"dlgShop\">&lt;form method=\"dialog\" style=\"padding:16px\">&lt;h3 style=\"margin:0 0 8px\">\u30b7\u30e7\u30c3\u30d7&lt;\/h3>&lt;p class=\"muted\">\ud83d\udc8e\u306f\u30d7\u30ec\u30a4\u5f8c\u306b\u30b9\u30b3\u30a2\u304b\u3089\u63db\u7b97\uff08100pts \u2252 1\ud83d\udc8e\uff09\u3002&lt;\/p>&lt;div id=\"shopList\">&lt;\/div>&lt;div style=\"display:flex; gap:.5rem; justify-content:flex-end; margin-top:10px\">&lt;button class=\"btn\" value=\"cancel\">\u9589\u3058\u308b&lt;\/button>&lt;\/div>&lt;\/form>&lt;\/dialog>\n  &lt;dialog id=\"dlgSkins\">&lt;form method=\"dialog\" style=\"padding:16px\">&lt;h3 style=\"margin:0 0 8px\">\u30b9\u30ad\u30f3&lt;\/h3>&lt;div id=\"skinList\">&lt;\/div>&lt;div style=\"display:flex; gap:.5rem; justify-content:flex-end; margin-top:10px\">&lt;button class=\"btn\" value=\"cancel\">\u9589\u3058\u308b&lt;\/button>&lt;\/div>&lt;\/form>&lt;\/dialog>\n\n  &lt;script>\n  \/\/ ===== Utilities &amp; Persistence =====\n  const clamp=(v,min,max)=>Math.max(min,Math.min(max,v));\n  const rand=(a,b)=>Math.random()*(b-a)+a; const choice=a=>a&#91;(Math.random()*a.length)|0];\n  const storage={ get(k,def){ try{return JSON.parse(localStorage.getItem(k)) ?? def}catch{ return def }}, set(k,v){ localStorage.setItem(k, JSON.stringify(v)); } };\n\n  const SAVE={ best:'ultra-best', opts:'ultra-opts', stats:'ultra-stats', wallet:'ultra-wallet', upgrades:'ultra-upgrades', missions:'ultra-missions', skin:'ultra-skin' };\n  const opts = Object.assign({ speedMul:1.0, shake:true, colorblind:false, eco:false, hints:true }, storage.get(SAVE.opts, {})); storage.set(SAVE.opts, opts);\n  const wallet = { gems: storage.get(SAVE.wallet, 0) };\n  function addGems(n){ wallet.gems = Math.max(0, Math.floor(wallet.gems + n)); storage.set(SAVE.wallet, wallet.gems); walletEl.textContent = wallet.gems; }\n\n  const upgrades = Object.assign({ startShield:0, magnetDur:0, dashCD:0, scoreMul:0, extraLife:0 }, storage.get(SAVE.upgrades, {}));\n  function uLevel(name){ return upgrades&#91;name]||0 } function saveUpgrades(){ storage.set(SAVE.upgrades, upgrades); buildShop(); }\n\n  const skins = &#91;\n    {id:'default', name:'Default', cost:0, color:'#eaf2ff'},\n    {id:'neon', name:'Neon Blue', cost:50, color:'#7ee0ff'},\n    {id:'sun', name:'Sun Gold', cost:80, color:'#ffd166'},\n    {id:'void', name:'Void Purple', cost:120, color:'#c4a7ff'},\n    {id:'leaf', name:'Leaf Green', cost:120, color:'#86efac'}\n  ];\n  let currentSkin = storage.get(SAVE.skin, 'default');\n\n  function toast(msg, t=1200){ const el=document.getElementById('toast'); el.textContent=msg; el.classList.add('show'); clearTimeout(el._t); el._t=setTimeout(()=>el.classList.remove('show'), t); }\n\n  \/\/ ===== Audio =====\n  const AudioKit=(()=>{ let ctx, enabled=false; function ensure(){ if(!ctx){ const C=window.AudioContext||window.webkitAudioContext; if(C){ ctx=new C(); }} return ctx }\n    function beep(freq=440, dur=0.08, type='sine', gain=0.02){ if(!enabled) return; const c=ensure(); if(!c) return; const o=c.createOscillator(); const g=c.createGain(); o.type=type; o.frequency.setValueAtTime(freq,c.currentTime); g.gain.setValueAtTime(gain,c.currentTime); o.connect(g).connect(c.destination); const t=c.currentTime; o.start(t); o.stop(t+dur); }\n    function arpeggio(){ if(!enabled) return; const c=ensure(); if(!c) return; const base=220; const seq=&#91;0,4,7,12,7,4]; seq.forEach((st,i)=>{ const o=c.createOscillator(); const g=c.createGain(); o.type='triangle'; o.frequency.setValueAtTime(base*Math.pow(2,st\/12), c.currentTime + i*0.08); g.gain.setValueAtTime(0.02, c.currentTime + i*0.08); o.connect(g).connect(c.destination); o.start(c.currentTime + i*0.08); o.stop(c.currentTime + i*0.08 + 0.1); }); }\n    return{ enable(){ enabled=true; ensure(); }, disable(){ enabled=false; }, toggle(){ enabled=!enabled; if(enabled) ensure(); return enabled; }, hit(){ beep(120,0.18,'square',0.05); }, coin(){ beep(880,0.07,'triangle',0.03); }, tick(){ beep(660,0.02,'sine',0.015); }, power(){ beep(520,0.1,'sawtooth',0.04); }, dash(){ beep(240,0.06,'square',0.05); }, fanfare(){ arpeggio(); }, shoot(){ beep(720,0.04,'square',0.03); } }\n  })();\n\n  \/\/ ===== Canvas &amp; World =====\n  const canvas=document.getElementById('game'); const ctx=canvas.getContext('2d');\n  let dpr=1; function resize(){ dpr=Math.max(1, Math.min(2, window.devicePixelRatio||1)); const w=canvas.clientWidth; const h=canvas.clientHeight; canvas.width=Math.round(w*dpr); canvas.height=Math.round(h*dpr); ctx.setTransform(dpr,0,0,dpr,0,0); }\n  new ResizeObserver(resize).observe(canvas); window.addEventListener('orientationchange', resize); resize();\n\n  const state={ running:false, over:false, t:0, score:0, best: storage.get(SAVE.best, 0), baseSpeed:120, speed:120, worldW:360, worldH:640, combo:1, comboTime:0, slowed:0, stage:1, boss:false };\n  const fx={ shakeTime:0, shakeAmp:0 };\n  const starCount = opts.eco? 40 : 90; const stars=&#91;...Array(starCount)].map(()=>({x:rand(0,360), y:rand(0,640), s:rand(0.5,2), sp:rand(10,40)}));\n\n  const player={ x:180, y:560, r:12, vx:0, speed:270, color:'#eaf2ff', alive:true, flash:0, shield:0, magnet:0, dashCD:0, dashT:0, extra:0, fireCD:0 };\n  const obstacles=&#91;]; const coins=&#91;]; const lasers=&#91;]; const particles=&#91;]; const powerups=&#91;]; const bullets=&#91;]; \/\/ boss bullets\n  const pbullets=&#91;]; \/\/ player bullets\n\n  \/\/ ===== Spawning =====\n  let lastSpawn=0, spawnInt=0.9; let lastLaser=0, laserInt=6.0; let stageTime=0, nextBossAt=28; \/\/ seconds\n  function spawnBlockRow(yOff=-40){ const gap = clamp(140 - state.t*0.02, 70, 150); const blockW = rand(40, 90); const leftW = rand(10, state.worldW - gap - blockW - 10); const rightX = leftW + gap + blockW; const moving = Math.random()&lt;clamp(0.08 + state.t*0.0006, 0.08, 0.4); const speed = moving? rand(30, 90)* (Math.random()&lt;0.5?-1:1) : 0; obstacles.push({x:0, y:yOff, w:leftW, h:16, vx:0}); obstacles.push({x:rightX, y:yOff, w: state.worldW - rightX, h:16, vx:0}); if(moving){ obstacles.push({x:leftW+4, y:yOff-18, w: blockW-8, h:10, vx:speed}); }\n    const cx = leftW + gap\/2 + rand(-gap*0.35, gap*0.35); const cluster = (Math.random()&lt;0.6) ? 4 : 1; for(let i=0;i&lt;cluster;i++) coins.push({x:cx + (cluster>1?(i-1.5)*10:0), y:yOff-20 - i*8, r:6, vy:0}); if(Math.random()&lt;0.22) powerups.push({x:cx+rand(-gap*0.3,gap*0.3), y:yOff-36, r:8, kind: choice(&#91;'shield','magnet','slow'])}); }\n  function spawnLaser(){ const side = Math.random()&lt;0.5? 'L':'R'; const x = side==='L'? -40 : state.worldW+40; const dir = side==='L'? 1 : -1; lasers.push({x, y: rand(120, state.worldH-160), w:120, h:10, vx: 170*dir, life: 4}); }\n\n  \/\/ ===== Boss &amp; Bullet Hell =====\n  let boss=null; let patternT=0, patternId=0, spiralAng=0; \/\/ patterns\n  function enterBoss(){ state.boss=true; show(bossOverlay); }\n  function startBoss(){ hide(bossOverlay); boss = { x: state.worldW\/2, y: 160, r: 22, hp: 6 + state.stage*2, vx: 80 }; bullets.length=0; patternT=0; patternId=0; spiralAng=0; }\n  function bossShootFan(){ \/\/ \u6247\u72b6\uff08\u81ea\u6a5f\u72d9\u3044\uff09\n    const dx = player.x - boss.x; const dy = (player.y - boss.y); const base = Math.atan2(dy, dx); const n=5; const spread=0.6; for(let i=0;i&lt;n;i++){ const a = base + (i-(n-1)\/2)*spread\/n; bullets.push({x:boss.x, y:boss.y, r:4, vx:Math.cos(a)*160, vy:Math.sin(a)*160}); }\n  }\n  function bossShootRing(){ \/\/ \u5168\u65b9\u4f4d\u30ea\u30f3\u30b0\n    const n=14; for(let i=0;i&lt;n;i++){ const a = (i\/n)*Math.PI*2; bullets.push({x:boss.x, y:boss.y, r:3.5, vx:Math.cos(a)*120, vy:Math.sin(a)*120}); }\n  }\n  function bossShootSpiral(){ \/\/ \u6e26\u5dfb\u304d\n    const a1 = spiralAng; const a2 = spiralAng + Math.PI; spiralAng += 0.35; bullets.push({x:boss.x, y:boss.y, r:3.5, vx:Math.cos(a1)*150, vy:Math.sin(a1)*150}); bullets.push({x:boss.x, y:boss.y, r:3.5, vx:Math.cos(a2)*150, vy:Math.sin(a2)*150}); }\n\n  function updateBoss(dt){ if(!boss) return; boss.x += boss.vx*dt; if(boss.x&lt;40){ boss.x=40; boss.vx=Math.abs(boss.vx);} if(boss.x>state.worldW-40){ boss.x=state.worldW-40; boss.vx=-Math.abs(boss.vx);} \/\/ pattern timeline\n    patternT += dt; if(patternId===0){ if(patternT>0.6){ bossShootFan(); patternT=0; if(Math.random()&lt;0.25) patternId=1; } }\n    else if(patternId===1){ bossShootSpiral(); if(patternT>2.4){ patternT=0; patternId=2; } }\n    else if(patternId===2){ if(patternT>1.0){ bossShootRing(); patternT=0; if(Math.random()&lt;0.5) patternId=0; else patternId=1; } }\n\n    \/\/ move bullets\n    for(const b of bullets){ b.x += b.vx*dt; b.y += b.vy*dt; }\n    for(let i=bullets.length-1;i>=0;i--){ const b=bullets&#91;i]; if(b.x&lt;-40||b.x>state.worldW+40||b.y&lt;-40||b.y>state.worldH+60) bullets.splice(i,1); }\n\n    \/\/ hit player\n    for(const b of bullets){ const dx=player.x-b.x, dy=player.y-b.y; if(dx*dx+dy*dy &lt;= (player.r+b.r)*(player.r+b.r)){ if(player.dashT&lt;=0){ if(player.shield>0){ player.shield-=1; emit(player.x,player.y,12,'#6ee7ff'); } else if(player.extra>0){ player.extra--; toast('Extra Life!'); } else { return gameOver(); } } } }\n  }\n  function damageBoss(dmg=1){ if(!boss) return; boss.hp-=dmg; emit(boss.x,boss.y,16,'#c4a7ff'); if(boss.hp&lt;=0){ boss=null; state.boss=false; state.stage++; stageTime=0; nextBossAt = clamp(26 - state.stage, 18, 26); addScore(200); toast(`Stage ${state.stage} \u30af\u30ea\u30a2\uff01`); AudioKit.fanfare(); }\n  }\n\n  function emit(x,y, n=8, col='#a8ffce'){ for(let i=0;i&lt;n;i++){ particles.push({x,y, vx:rand(-90,90), vy:rand(-120,-40), life: rand(.3,.75), col}) } }\n\n  \/\/ ===== Input =====\n  let left=false, right=false, dashReq=false, shootHold=false;\n  window.addEventListener('keydown',e=>{\n    if(e.key==='ArrowLeft'||e.key==='a'||e.key==='A') left=true;\n    if(e.key==='ArrowRight'||e.key==='d'||e.key==='D') right=true;\n    if(e.code==='Space'){ shootHold=true; e.preventDefault(); }\n    if(e.key==='Shift') dashReq=true;\n  });\n  window.addEventListener('keyup',e=>{\n    if(e.key==='ArrowLeft'||e.key==='a'||e.key==='A') left=false;\n    if(e.key==='ArrowRight'||e.key==='d'||e.key==='D') right=false;\n    if(e.code==='Space') shootHold=false;\n  });\n\n  const leftBtn=document.getElementById('leftBtn'); const rightBtn=document.getElementById('rightBtn'); const dashBtn=document.getElementById('dashBtn');\n  const tp=document.getElementById('touchControls'); const isMobile = \/Mobi|Android\/i.test(navigator.userAgent); tp.style.display = isMobile? 'flex':'none';\n  const press=(b)=>{ b.dataset.down='1'; if(b===leftBtn) left=true; else if(b===rightBtn) right=true; else dashReq=true; };\n  const release=(b)=>{ b.dataset.down='0'; if(b===leftBtn) left=false; else if(b===rightBtn) right=false; };\n  &#91;leftBtn,rightBtn,dashBtn].forEach(b=>{ b.addEventListener('pointerdown',()=>press(b)); b.addEventListener('pointerup',()=>release(b)); b.addEventListener('pointerleave',()=>release(b)); });\n  \/\/ mobile taps: single tap=shot, two-finger=dash\n  canvas.addEventListener('touchstart',e=>{ if(e.touches.length>=2) { dashReq=true; } else { shootOnce(); } }, {passive:true});\n  \/\/ desktop click to shoot too\n  canvas.addEventListener('mousedown', shootOnce);\n\n  \/\/ ===== Loop =====\n  let last=performance.now(); function loop(t){ const dt=Math.min(0.033,(t-last)\/1000); last=t; if(state.running) update(dt); draw(dt); requestAnimationFrame(loop); } requestAnimationFrame(loop);\n\n  \/\/ ===== Mechanics =====\n  const stats = { coins:0, dash:0, maxCombo:1, shield:0, score:0 };\n\n  function reset(){ state.running=false; state.over=false; state.t=0; state.score=0; state.stage=1; stageTime=0; nextBossAt=28; state.speed=state.baseSpeed*opts.speedMul; state.combo=1; state.comboTime=0; state.slowed=0; state.boss=false; fx.shakeTime=0; fx.shakeAmp=0; boss=null;\n    obstacles.length=0; coins.length=0; particles.length=0; lasers.length=0; powerups.length=0; bullets.length=0; pbullets.length=0;\n    player.x=state.worldW\/2; player.alive=true; player.flash=0; player.shield=0; player.magnet=0; player.dashCD=Math.max(0,2.6 - uLevel('dashCD')*0.4); player.dashT=0; player.extra = uLevel('extraLife'); player.fireCD=0;\n    if(uLevel('startShield')>0) player.shield = 0.8 + 0.4*uLevel('startShield');\n    spawnBlockRow(0); updateUI(); }\n\n  function start(){ state.running=true; hide(startOverlay); hide(gameoverOverlay); hide(bossOverlay); AudioKit.tick(); }\n  function gameOver(){ state.running=false; state.over=true; player.alive=false; AudioKit.hit(); state.best=Math.max(state.best, Math.floor(state.score)); storage.set(SAVE.best, state.best); const earned = Math.floor((state.score * (1 + 0.1*uLevel('scoreMul')))\/100); addGems(earned); finalScore.textContent = Math.floor(state.score); finalBest.textContent = state.best; earnedEl.textContent = earned; finalMissions.textContent = summarizeMissions(); show(gameoverOverlay); updateUI(); }\n\n  function updateUI(){ scoreEl.textContent = Math.floor(state.score); bestEl.textContent = state.best; comboEl.textContent = 'x'+state.combo.toFixed(1); badgesEl.textContent = `\u26e8 ${Math.ceil(player.shield)} \u00b7 \ud83e\uddf2 ${Math.ceil(player.magnet)} \u00b7 \u23f3 ${Math.ceil(state.slowed)}`; stageEl.textContent = state.stage; walletEl.textContent = wallet.gems; }\n\n  function addScore(v){ state.score += v * (1 + 0.1*uLevel('scoreMul')) * state.combo; stats.score = Math.floor(state.score); }\n  function addCombo(dt){ state.combo = clamp(state.combo + dt*0.05, 1, 5); state.comboTime = 1.8; stats.maxCombo = Math.max(stats.maxCombo, state.combo); }\n\n  function doDash(){ if(player.dashT>0 || player.dashCD>0) return; player.dashT=0.4; player.dashCD=Math.max(0.8, 3.0 - uLevel('dashCD')*0.4); stats.dash++; AudioKit.dash(); toast('Dash!'); fx.shakeTime=0.12; fx.shakeAmp=4; }\n\n  function applyPower(kind){ if(kind==='shield'){ player.shield = Math.max(player.shield, 1.5 + 0.2*uLevel('startShield')); stats.shield++; toast('Shield \u26e8'); }\n    else if(kind==='magnet'){ player.magnet = Math.max(player.magnet, 4.5 + 0.5*uLevel('magnetDur')); toast('Magnet \ud83e\uddf2'); }\n    else if(kind==='slow'){ state.slowed = Math.max(state.slowed, 2.5); toast('Slow \u23f3'); }\n    AudioKit.power(); }\n\n  function collideCircleRect(cx,cy,cr, r){ const tx=clamp(cx, r.x, r.x+r.w); const ty=clamp(cy, r.y, r.y+r.h); const dx=cx-tx, dy=cy-ty; return dx*dx+dy*dy &lt;= cr*cr; }\n\n  function tryFire(){ if(player.fireCD>0) return; \/\/ fire 1~3 shots based on combo\n    const n = (state.combo>=3.5? 3 : (state.combo>=2.0? 2:1));\n    for(let i=0;i&lt;n;i++){\n      const off = (n===1)?0:(i-(n-1)\/2)*6; pbullets.push({x:player.x+off, y:player.y-player.r-2, r:3, vy:-380});\n    }\n    player.fireCD = Math.max(0.08, 0.22 - (state.combo-1)*0.02);\n    AudioKit.shoot();\n  }\n  function shootOnce(){ tryFire(); }\n\n  function update(dt){\n    state.t += dt; stageTime += dt; const speedMul = opts.speedMul * (state.slowed>0? 0.55:1); state.speed = clamp(120 + state.t*6, 120, 540) * speedMul; spawnInt = clamp(0.9 - state.t*0.02, 0.26, 0.9); laserInt = clamp(6.0 - state.t*0.01, 3.0, 6.0);\n\n    if(!state.boss &amp;&amp; stageTime>=nextBossAt){ enterBoss(); }\n\n    lastSpawn += dt; if(lastSpawn>=spawnInt &amp;&amp; !state.boss){ lastSpawn=0; spawnBlockRow(-20); }\n    lastLaser += dt; if(lastLaser>=laserInt &amp;&amp; !state.boss){ lastLaser=0; spawnLaser(); }\n\n    \/\/ Player movement &amp; actions\n    const dir = (right?1:0) - (left?1:0);\n    const skinCol = skins.find(s=>s.id===currentSkin)?.color || '#eaf2ff'; player.color = skinCol;\n    player.vx = dir * player.speed * (player.dashT>0? 1.6:1);\n    player.x = clamp(player.x + player.vx * dt, player.r+2, state.worldW - player.r-2);\n    if(dashReq){ doDash(); dashReq=false; }\n    if(player.dashT>0) player.dashT-=dt; if(player.dashCD>0) player.dashCD-=dt;\n    if(player.fireCD>0) player.fireCD-=dt; if(shootHold) tryFire();\n\n    \/\/ Stars\n    for(const s of stars){ s.y += (state.speed*0.2 + s.sp) * dt; if(s.y>state.worldH) { s.y -= state.worldH; s.x = rand(0,state.worldW);} }\n\n    \/\/ Entities movement\n    for(const o of obstacles){ o.y += state.speed * dt; o.x += (o.vx||0) * dt; if(o.x&lt;0){ o.x=0; o.vx=Math.abs(o.vx||0);} if(o.x+o.w>state.worldW){ o.x=state.worldW-o.w; o.vx = -Math.abs(o.vx||0);} }\n    for(const c of coins){ c.y += (state.speed*0.95) * dt; const ax = (player.magnet>0? (player.x - c.x)*1.6 : 0); const ay = (player.magnet>0? (player.y - c.y)*1.6 : 0); c.x += ax*dt; c.y += ay*dt; }\n    for(const p of particles){ p.x += p.vx*dt; p.y += p.vy*dt; p.vy += 420*dt; p.life -= dt; }\n    for(const l of lasers){ l.x += l.vx*dt; l.life -= dt; }\n    for(const pb of pbullets){ pb.y += pb.vy*dt; }\n\n    if(state.boss){ updateBoss(dt); }\n\n    \/\/ Clean\n    while(obstacles.length &amp;&amp; obstacles&#91;0].y>state.worldH+40) obstacles.shift();\n    while(coins.length &amp;&amp; coins&#91;0].y>state.worldH+40) coins.shift();\n    for(let i=particles.length-1;i>=0;i--) if(particles&#91;i].life&lt;=0) particles.splice(i,1);\n    for(let i=lasers.length-1;i>=0;i--) if(lasers&#91;i].life&lt;=0 || lasers&#91;i].x&lt;-160 || lasers&#91;i].x>state.worldW+160) lasers.splice(i,1);\n    for(let i=pbullets.length-1;i>=0;i--) if(pbullets&#91;i].y&lt;-30) pbullets.splice(i,1);\n\n    for(let i=powerups.length-1;i>=0;i--) if(powerups&#91;i].y>state.worldH+40) powerups.splice(i,1);\n    for(const u of powerups){ u.y += state.speed*0.9*dt; }\n\n    \/\/ Collisions with hazards\n    let hit=false; if(!state.boss){ for(const o of obstacles){ if(collideCircleRect(player.x,player.y,player.r, o)) { hit=true; break; } } for(const l of lasers){ const r={x:l.x-4, y:l.y-2, w:l.w+8, h:l.h+4}; if(collideCircleRect(player.x,player.y,player.r, r)) { hit=true; break; } } }\n    if(hit &amp;&amp; player.dashT&lt;=0){ if(player.shield>0){ player.shield-=0.9; emit(player.x, player.y, 14, '#6ee7ff'); fx.shakeTime=0.18; fx.shakeAmp=6; } else if(player.extra>0){ player.extra--; toast('Extra Life!'); emit(player.x,player.y,12,'#86efac'); } else { player.flash=0.18; emit(player.x, player.y, 18, '#ff7777'); return gameOver(); } }\n\n    \/\/ coins\n    for(let i=coins.length-1;i>=0;i--){ const c=coins&#91;i]; const dx=player.x-c.x, dy=player.y-c.y; if(dx*dx+dy*dy &lt; (player.r+c.r)*(player.r+c.r)){ coins.splice(i,1); addScore(10); addCombo(0.25); stats.coins++; AudioKit.coin(); emit(c.x,c.y,6,'#ffd166'); if(state.boss &amp;&amp; boss){ damageBoss(0.3); } } }\n\n    \/\/ powerups\n    for(let i=powerups.length-1;i>=0;i--){ const u=powerups&#91;i]; const dx=player.x-u.x, dy=player.y-u.y; if(dx*dx+dy*dy &lt; (player.r+u.r)*(player.r+u.r)){ powerups.splice(i,1); applyPower(u.kind); addScore(5); } }\n\n    \/\/ player bullets vs boss\n    if(boss){ for(let i=pbullets.length-1;i>=0;i--){ const pb=pbullets&#91;i]; const dx=boss.x-pb.x, dy=boss.y-pb.y; if(dx*dx+dy*dy &lt;= (boss.r+pb.r)*(boss.r+pb.r)){ pbullets.splice(i,1); damageBoss(1); addScore(2); } } }\n\n    \/\/ Effects timers\n    if(player.shield>0) player.shield=Math.max(0, player.shield-dt);\n    if(player.magnet>0) player.magnet=Math.max(0, player.magnet-dt);\n    if(state.slowed>0) state.slowed=Math.max(0, state.slowed-dt);\n    if(player.flash>0) player.flash=Math.max(0, player.flash-0.016);\n    if(state.comboTime>0){ state.comboTime-=dt; if(state.comboTime&lt;=0) state.combo = Math.max(1, state.combo-0.1); }\n\n    \/\/ Score by time\n    addScore(dt*3); updateUI();\n  }\n\n  \/\/ ===== Rendering =====\n  function draw(){ const w=canvas.width\/dpr, h=canvas.height\/dpr; const sx = (fx.shakeTime>0 &amp;&amp; opts.shake)? (rand(-fx.shakeAmp,fx.shakeAmp)) : 0; const sy = (fx.shakeTime>0 &amp;&amp; opts.shake)? (rand(-fx.shakeAmp,fx.shakeAmp)) : 0; if(fx.shakeTime>0) fx.shakeTime -= 1\/60; ctx.save(); ctx.clearRect(0,0,w,h); ctx.translate(sx, sy);\n    const obCol = opts.colorblind? 'rgba(255,255,255,.9)': 'rgba(255,255,255,.14)';\n    ctx.save(); ctx.globalAlpha=0.9; for(const s of stars){ ctx.fillStyle = `rgba(255,255,255,${0.2 + s.s*0.2})`; ctx.fillRect(s.x, s.y, s.s, s.s); } ctx.restore();\n    ctx.save(); ctx.globalAlpha=0.06; ctx.lineWidth=1; const grid=20; ctx.beginPath(); for(let x=0;x&lt;w;x+=grid){ ctx.moveTo(x,0); ctx.lineTo(x,h);} for(let y=0;y&lt;h;y+=grid){ ctx.moveTo(0,y); ctx.lineTo(w,y);} ctx.strokeStyle='white'; ctx.stroke(); ctx.restore();\n\n    \/\/ coins\n    ctx.save(); for(const c of coins){ ctx.beginPath(); ctx.arc(c.x, c.y, c.r, 0, Math.PI*2); ctx.fillStyle = opts.colorblind? '#ffbf00' : 'var(--gold)'; ctx.fill(); ctx.lineWidth=1; ctx.strokeStyle='rgba(0,0,0,.25)'; ctx.stroke(); } ctx.restore();\n    \/\/ powerups\n    ctx.save(); for(const u of powerups){ ctx.beginPath(); ctx.arc(u.x, u.y, u.r, 0, Math.PI*2); ctx.fillStyle = u.kind==='shield'? '#6ee7ff' : (u.kind==='magnet'? '#9bffb7' : '#c4a7ff'); ctx.fill(); ctx.strokeStyle='rgba(0,0,0,.3)'; ctx.stroke(); ctx.font='10px system-ui'; ctx.fillStyle='#001'; const sym = u.kind==='shield'? '\u26e8' : (u.kind==='magnet'? '\ud83e\uddf2' : '\u23f3'); ctx.fillText(sym, u.x-6, u.y+3); } ctx.restore();\n    \/\/ obstacles &amp; lasers (no boss phase)\n    if(!state.boss){ ctx.save(); ctx.fillStyle=obCol; for(const o of obstacles){ ctx.fillRect(o.x, o.y, o.w, o.h); } ctx.restore(); ctx.save(); for(const l of lasers){ const grad=ctx.createLinearGradient(l.x, l.y, l.x+l.w, l.y+l.h); grad.addColorStop(0,'rgba(255,90,90,.85)'); grad.addColorStop(1,'rgba(255,160,160,.5)'); ctx.fillStyle=grad; ctx.fillRect(l.x, l.y, l.w, l.h); } ctx.restore(); }\n    \/\/ boss\n    if(state.boss &amp;&amp; boss){ ctx.save(); const g=ctx.createRadialGradient(boss.x-6,boss.y-6,4, boss.x,boss.y,boss.r+6); g.addColorStop(0,'#fff'); g.addColorStop(1,'#c4a7ff'); ctx.fillStyle=g; ctx.beginPath(); ctx.arc(boss.x,boss.y,boss.r,0,Math.PI*2); ctx.fill(); ctx.fillStyle='rgba(255,255,255,.8)'; ctx.fillRect(boss.x-24,boss.y-boss.r-16,48,6); ctx.fillStyle='#ff6bcb'; const hpw = clamp((boss.hp\/(6+state.stage*2))*48,0,48); ctx.fillRect(boss.x-24,boss.y-boss.r-16,hpw,6); ctx.restore(); ctx.save(); ctx.fillStyle='#ff9d9d'; for(const b of bullets){ ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,Math.PI*2); ctx.fill(); } ctx.restore(); }\n    \/\/ player bullets\n    ctx.save(); ctx.fillStyle='#aee3ff'; for(const pb of pbullets){ ctx.beginPath(); ctx.arc(pb.x,pb.y,pb.r,0,Math.PI*2); ctx.fill(); } ctx.restore();\n    \/\/ player\n    ctx.save(); if(player.flash>0){ ctx.shadowColor=getCSS('--danger', '#ff6b6b'); ctx.shadowBlur=18; }\n    ctx.beginPath(); ctx.arc(player.x, player.y, player.r, 0, Math.PI*2); const grad=ctx.createRadialGradient(player.x-4,player.y-6,4, player.x,player.y, player.r+6); grad.addColorStop(0, '#ffffff'); grad.addColorStop(1, player.color||'#7ee0ff'); ctx.fillStyle=grad; ctx.fill(); if(player.shield>0){ ctx.globalAlpha=0.25+0.15*Math.sin(performance.now()\/120); ctx.beginPath(); ctx.arc(player.x, player.y, player.r+6, 0, Math.PI*2); ctx.strokeStyle='#8ae9ff'; ctx.lineWidth=3; ctx.stroke(); ctx.globalAlpha=1; } if(player.dashT>0){ ctx.globalAlpha=0.5; ctx.beginPath(); ctx.arc(player.x - 10, player.y, player.r*0.9, 0, Math.PI*2); ctx.fillStyle='#bde3ff'; ctx.fill(); ctx.globalAlpha=1; } ctx.restore();\n    \/\/ particles\n    ctx.save(); for(const p of particles){ ctx.globalAlpha = clamp(p.life,0,1); ctx.fillStyle=p.col||'#a8ffce'; ctx.fillRect(p.x, p.y, 2,2); } ctx.restore();\n    ctx.restore();\n  }\n\n  function getCSS(name, fallback){ return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback; }\n\n  \/\/ ===== UI wires =====\n  const startOverlay=document.getElementById('overlayStart'); const gameoverOverlay=document.getElementById('overlayGameOver'); const bossOverlay=document.getElementById('overlayBoss');\n  const scoreEl=document.getElementById('score'); const bestEl=document.getElementById('best'); const comboEl=document.getElementById('combo'); const stageEl=document.getElementById('stage'); const badgesEl=document.getElementById('badges');\n  const btnStart=document.getElementById('btnStart'); const btnRetry=document.getElementById('btnRetry'); const btnHome=document.getElementById('btnHome'); const btnPause=document.getElementById('btnPause'); const btnSound=document.getElementById('btnSound'); const btnSettings=document.getElementById('btnSettings'); const btnShop=document.getElementById('btnShop'); const btnSkins=document.getElementById('btnSkins'); const btnBossGo=document.getElementById('btnBossGo');\n  const dlgSettings=document.getElementById('dlgSettings'); const rangeSpeed=document.getElementById('rangeSpeed'); const chkShake=document.getElementById('chkShake'); const chkCB=document.getElementById('chkCB'); const chkEco=document.getElementById('chkEco'); const chkHints=document.getElementById('chkHints'); const missionsEl=document.getElementById('missions'); const walletEl=document.getElementById('wallet');\n  const finalScore=document.getElementById('finalScore'); const finalBest=document.getElementById('finalBest'); const earnedEl=document.getElementById('earned'); const finalMissions=document.getElementById('finalMissions');\n\n  function show(el){ el.style.display='grid'; } function hide(el){ el.style.display='none'; }\n  btnStart.addEventListener('click',()=>{ start(); AudioKit.enable(); }); btnRetry.addEventListener('click',()=>{ reset(); start(); }); btnHome.addEventListener('click',()=>{ reset(); show(startOverlay); });\n  btnPause.addEventListener('click',()=>{ if(!state.running) resume(); else togglePause(); }); btnSound.addEventListener('click',()=>{ const on = AudioKit.toggle(); btnSound.textContent = on ? '\ud83d\udd0a' : '\ud83d\udd07'; if(on) AudioKit.tick(); });\n  btnSettings.addEventListener('click',()=>{ rangeSpeed.value=opts.speedMul; chkShake.checked=opts.shake; chkCB.checked=opts.colorblind; chkEco.checked=opts.eco; chkHints.checked=opts.hints; dlgSettings.showModal(); });\n  document.getElementById('btnSaveSettings').addEventListener('click',(e)=>{ e.preventDefault(); opts.speedMul=parseFloat(rangeSpeed.value); opts.shake=chkShake.checked; opts.colorblind=chkCB.checked; opts.eco=chkEco.checked; opts.hints=chkHints.checked; storage.set(SAVE.opts, opts); dlgSettings.close(); toast('\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f'); });\n\n  \/\/ Shop &amp; Skins\n  const dlgShop=document.getElementById('dlgShop'); const shopList=document.getElementById('shopList');\n  function buildShop(){ shopList.innerHTML=''; const items=&#91;\n    {key:'startShield', name:'\u958b\u59cb\u6642\u30b7\u30fc\u30eb\u30c9', desc:'+0.4\u301c\u306e\u30b7\u30fc\u30eb\u30c9\u3092\u4ed8\u4e0e', base:40, max:3},\n    {key:'magnetDur', name:'\u30de\u30b0\u30cd\u30c3\u30c8\u5ef6\u9577', desc:'+0.5s\/\u30ec\u30d9\u30eb', base:30, max:5},\n    {key:'dashCD', name:'\u30c0\u30c3\u30b7\u30e5CD\u77ed\u7e2e', desc:'-0.4s\/\u30ec\u30d9\u30eb', base:45, max:4},\n    {key:'scoreMul', name:'\u30b9\u30b3\u30a2\u500d\u7387', desc:'+10%\/\u30ec\u30d9\u30eb', base:60, max:5},\n    {key:'extraLife', name:'\u30a8\u30af\u30b9\u30c8\u30e9\u30e9\u30a4\u30d5', desc:'1\u56de\u3060\u3051\u30df\u30b9\u3092\u7121\u52b9\u5316', base:120, max:1},\n  ]; items.forEach(it=>{ const lv=uLevel(it.key); const cost = Math.floor(it.base * Math.pow(1.6, lv)); const can = lv&lt;it.max &amp;&amp; wallet.gems>=cost; const row=document.createElement('div'); row.className='shop-item'; row.innerHTML=`&lt;div>&lt;strong>${it.name}&lt;\/strong> &lt;span class=\"chip\">Lv.${lv}\/${it.max}&lt;\/span>&lt;div class=\"muted\" style=\"font-size:.85rem\">${it.desc}&lt;\/div>&lt;\/div>&lt;button class=\"btn ${can?'primary':''}\" ${can?'':'disabled'}>${lv>=it.max?'MAX':`\u8cfc\u5165 \ud83d\udc8e${cost}`}&lt;\/button>`; row.querySelector('button').onclick=()=>{ if(lv>=it.max) return; if(wallet.gems&lt;cost){ toast('\ud83d\udc8e\u4e0d\u8db3'); return; } addGems(-cost); upgrades&#91;it.key]=(upgrades&#91;it.key]||0)+1; saveUpgrades(); toast(`${it.name} Lv.${upgrades&#91;it.key]}`); }; shopList.appendChild(row); }); }\n  const dlgSkins=document.getElementById('dlgSkins'); const skinList=document.getElementById('skinList');\n  function buildSkins(){ skinList.innerHTML=''; skins.forEach(s=>{ const owned = (s.cost===0) || storage.get('skin-'+s.id, false); const can = wallet.gems>=s.cost &amp;&amp; !owned; const row=document.createElement('div'); row.className='shop-item'; row.innerHTML=`&lt;div>&lt;strong>${s.name}&lt;\/strong> &lt;span class=\"chip\" style=\"background:${s.color}; color:#000\">\u25cf&lt;\/span> ${s.cost?`&lt;span class='muted'>\/ \ud83d\udc8e${s.cost}&lt;\/span>`:'&lt;span class=\"muted\">\/ Free&lt;\/span>'}&lt;\/div>&lt;div>&lt;button class=\"btn ${owned?'':'primary'}\" data-id=\"${s.id}\">${owned?(currentSkin===s.id?'\u4f7f\u7528\u4e2d':'\u4f7f\u7528'):('\u8cfc\u5165')}&lt;\/button>&lt;\/div>`; row.querySelector('button').onclick=()=>{ if(!owned){ if(wallet.gems&lt;s.cost){ toast('\ud83d\udc8e\u4e0d\u8db3'); return; } addGems(-s.cost); storage.set('skin-'+s.id,true); } currentSkin=s.id; storage.set(SAVE.skin, currentSkin); buildSkins(); toast(`${s.name} \u3092\u88c5\u5099`); }; skinList.appendChild(row); }); }\n  btnShop.addEventListener('click',()=>{ buildShop(); dlgShop.showModal(); }); btnSkins.addEventListener('click',()=>{ buildSkins(); dlgSkins.showModal(); }); btnBossGo.addEventListener('click',()=>{ startBoss(); });\n\n  function togglePause(){ if(!state.running || state.over) return; state.running=false; btnPause.textContent='\u25b6'; toast('Pause'); }\n  function resume(){ if(state.over) return; state.running=true; btnPause.textContent='\u23f8'; toast('Resume'); }\n\n  \/\/ Missions\n  function generateMissions(){ const pool=&#91;\n    {id:'c80', text:'\u30b3\u30a4\u30f3\u309280\u679a\u96c6\u3081\u308b', test: s=>s.coins>=80},\n    {id:'dash4', text:'1\u30d7\u30ec\u30a4\u3067\u30c0\u30c3\u30b7\u30e5\u30924\u56de', test: s=>s.dash>=4},\n    {id:'combo35', text:'\u30b3\u30f3\u30dc\u500d\u73873.5\u9054\u6210', test: s=>s.maxCombo>=3.5},\n    {id:'shield', text:'\u30b7\u30fc\u30eb\u30c9\u53d6\u5f97', test: s=>s.shield>0},\n    {id:'score1200', text:'\u30b9\u30b3\u30a21200\u5230\u9054', test: s=>s.score>=1200},\n  ]; const chosen=&#91;]; while(chosen.length&lt;3){ const m=choice(pool); if(!chosen.find(c=>c.id===m.id)) chosen.push(m);} return chosen; }\n  let missions = storage.get(SAVE.missions, null); if(!missions){ missions=generateMissions(); storage.set(SAVE.missions, missions);} missionsEl.innerHTML = '&lt;strong>\u672c\u65e5\u306e\u30df\u30c3\u30b7\u30e7\u30f3&lt;\/strong>&lt;br>\u2022 '+missions.map(m=>m.text).join('&lt;br>\u2022 ');\n  function summarizeMissions(){ const done = missions.filter(m=>m.test(stats)).map(m=>m.text); return (done.length? done.join(' \/ ') : '\u306a\u3057'); }\n\n  \/\/ Init\n  state.best = storage.get(SAVE.best, 0); walletEl.textContent=wallet.gems; updateUI(); reset(); show(startOverlay);\n\n  &lt;\/script>\n&lt;\/body>\n&lt;\/html>\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[1,7,61,44],"tags":[3],"class_list":["post-26173","post","type-post","status-publish","format-standard","hentry","category-uncategorized","category-webdev","category-61","category-44","tag-programming"],"aioseo_notices":[],"jetpack_featured_media_url":"","_links":{"self":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/26173","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=26173"}],"version-history":[{"count":1,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/26173\/revisions"}],"predecessor-version":[{"id":26174,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/26173\/revisions\/26174"}],"wp:attachment":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=26173"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=26173"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=26173"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}