Created
May 4, 2026 12:43
-
-
Save gahrae/1e0ce38f575c075525d29c5dd576c612 to your computer and use it in GitHub Desktop.
Game: add guns to your browser
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Purpose: Add a gun to your browser | |
| // Install: Paste into the browser developer console | |
| // Usage: Left click to shoot, right click to change weapon | |
| (() => { | |
| if (window.__shootBrowser) { | |
| window.__shootBrowser.cleanup(); | |
| delete window.__shootBrowser; | |
| console.log('%c🔫 shoot-browser disabled', 'color:#888'); | |
| return; | |
| } | |
| const audio = new (window.AudioContext || window.webkitAudioContext)(); | |
| const beep = (freq, dur, type = 'square', vol = 0.2, ramp = 0.3) => { | |
| const o = audio.createOscillator(); | |
| const g = audio.createGain(); | |
| o.type = type; | |
| o.frequency.setValueAtTime(freq, audio.currentTime); | |
| o.frequency.exponentialRampToValueAtTime(Math.max(freq * ramp, 30), audio.currentTime + dur); | |
| g.gain.setValueAtTime(vol, audio.currentTime); | |
| g.gain.exponentialRampToValueAtTime(0.0001, audio.currentTime + dur); | |
| o.connect(g).connect(audio.destination); | |
| o.start(); | |
| o.stop(audio.currentTime + dur); | |
| }; | |
| const guns = [ | |
| { name: 'Pistol', fireRate: 280, auto: false, pellets: 1, spread: 3, size: 9, freq: 850, type: 'square', vol: 0.25, dur: 0.06 }, | |
| { name: 'Revolver', fireRate: 600, auto: false, pellets: 1, spread: 1, size: 13, freq: 500, type: 'sawtooth', vol: 0.4, dur: 0.12 }, | |
| { name: 'Shotgun', fireRate: 750, auto: false, pellets: 9, spread: 90, size: 6, freq: 110, type: 'sawtooth', vol: 0.55, dur: 0.18 }, | |
| { name: 'SMG', fireRate: 85, auto: true, pellets: 1, spread: 22, size: 7, freq: 600, type: 'square', vol: 0.18, dur: 0.04 }, | |
| { name: 'Assault Rifle', fireRate: 110, auto: true, pellets: 1, spread: 14, size: 9, freq: 720, type: 'square', vol: 0.22, dur: 0.05 }, | |
| { name: 'Minigun', fireRate: 38, auto: true, pellets: 1, spread: 30, size: 6, freq: 460, type: 'square', vol: 0.14, dur: 0.03 }, | |
| { name: 'Sniper', fireRate: 1400, auto: false, pellets: 1, spread: 0, size: 22, freq: 1800, type: 'triangle', vol: 0.45, dur: 0.28 }, | |
| { name: 'Rocket Launcher', fireRate: 1500, auto: false, pellets: 1, spread: 0, size: 0, freq: 70, type: 'sawtooth', vol: 0.6, dur: 0.4, explosion: true } | |
| ]; | |
| let gunIdx = 0; | |
| let firing = false; | |
| let lastShot = 0; | |
| let mx = 0, my = 0; | |
| let autoTimer = null; | |
| const css = document.createElement('style'); | |
| css.textContent = ` | |
| html, body, *:not(input):not(textarea) { cursor: crosshair !important; } | |
| .__sb-mark { position: absolute; pointer-events: none; z-index: 2147483646; transform: translate(-50%, -50%); } | |
| .__sb-flash { position: fixed; pointer-events: none; z-index: 2147483647; transform: translate(-50%, -50%); | |
| width: 44px; height: 44px; border-radius: 50%; | |
| background: radial-gradient(circle, #fff 0%, #ffeb3b 25%, rgba(255,140,0,0.6) 55%, rgba(255,80,0,0) 85%); | |
| animation: __sb-flash 90ms linear forwards; } | |
| @keyframes __sb-flash { from { opacity: 1; } to { opacity: 0; transform: translate(-50%,-50%) scale(1.6); } } | |
| .__sb-boom { position: absolute; pointer-events: none; z-index: 2147483646; transform: translate(-50%, -50%); | |
| border-radius: 50%; | |
| background: radial-gradient(circle, #fff 0%, #ffeb3b 14%, #ff9800 32%, #f44336 52%, rgba(60,20,5,0.5) 78%, rgba(0,0,0,0) 100%); | |
| animation: __sb-boom 600ms ease-out forwards; } | |
| @keyframes __sb-boom { | |
| 0% { transform: translate(-50%,-50%) scale(0.05); opacity: 1; } | |
| 40% { transform: translate(-50%,-50%) scale(1); opacity: 1; } | |
| 100% { transform: translate(-50%,-50%) scale(1.45); opacity: 0; } | |
| } | |
| .__sb-shake { animation: __sb-shake 420ms ease-in-out; } | |
| @keyframes __sb-shake { | |
| 0%,100% { transform: translate(0,0) rotate(0); } | |
| 20% { transform: translate(-7px,-3px) rotate(-1.2deg); } | |
| 45% { transform: translate(6px,3px) rotate(1deg); } | |
| 70% { transform: translate(-3px,4px) rotate(-0.6deg); } | |
| 90% { transform: translate(2px,-1px) rotate(0.3deg); } | |
| } | |
| #__sb-hud { | |
| position: fixed; bottom: 16px; right: 16px; z-index: 2147483647; | |
| background: rgba(10,10,12,0.85); color: #fff; padding: 10px 14px; | |
| border-radius: 10px; font: 600 14px/1.2 system-ui, sans-serif; | |
| pointer-events: none; border: 1px solid rgba(255,255,255,0.18); | |
| box-shadow: 0 6px 24px rgba(0,0,0,0.5); user-select: none; | |
| letter-spacing: 0.3px; | |
| } | |
| #__sb-hud small { display:block; font-weight:400; opacity:0.55; font-size:11px; margin-top:4px; letter-spacing:0; } | |
| #__sb-hud b { color: #ffe066; } | |
| `; | |
| document.head.appendChild(css); | |
| const hud = document.createElement('div'); | |
| hud.id = '__sb-hud'; | |
| document.body.appendChild(hud); | |
| const renderHUD = () => { | |
| const g = guns[gunIdx]; | |
| hud.innerHTML = `🔫 <b>${g.name}</b> <small>right-click to swap • ${gunIdx + 1}/${guns.length}</small>`; | |
| }; | |
| renderHUD(); | |
| const SVG_NS = 'http://www.w3.org/2000/svg'; | |
| const bulletHole = (x, y, size) => { | |
| const svg = document.createElementNS(SVG_NS, 'svg'); | |
| svg.classList.add('__sb-mark'); | |
| const w = size * 5; | |
| svg.setAttribute('width', w); | |
| svg.setAttribute('height', w); | |
| svg.setAttribute('viewBox', '-50 -50 100 100'); | |
| svg.style.left = x + 'px'; | |
| svg.style.top = y + 'px'; | |
| const r = 11 + Math.random() * 4; | |
| const cracks = 5 + Math.floor(Math.random() * 5); | |
| let frags = ''; | |
| for (let i = 0; i < cracks; i++) { | |
| const a = (Math.PI * 2 * i / cracks) + (Math.random() - 0.5) * 0.6; | |
| const len = r + 6 + Math.random() * 32; | |
| frags += `<line x1="0" y1="0" x2="${Math.cos(a) * len}" y2="${Math.sin(a) * len}" stroke="rgba(0,0,0,0.55)" stroke-width="${0.4 + Math.random() * 1.4}" />`; | |
| } | |
| svg.innerHTML = ` | |
| ${frags} | |
| <circle cx="0" cy="0" r="${r * 1.7}" fill="rgba(0,0,0,0.18)" /> | |
| <circle cx="0" cy="0" r="${r}" fill="#0a0a0a" /> | |
| <circle cx="${-r * 0.25}" cy="${-r * 0.25}" r="${r * 0.35}" fill="rgba(255,255,255,0.08)" />`; | |
| document.body.appendChild(svg); | |
| }; | |
| const explosion = (x, y, size) => { | |
| const boom = document.createElement('div'); | |
| boom.classList.add('__sb-boom'); | |
| boom.style.left = x + 'px'; | |
| boom.style.top = y + 'px'; | |
| boom.style.width = boom.style.height = size + 'px'; | |
| document.body.appendChild(boom); | |
| setTimeout(() => { | |
| const scorch = document.createElement('div'); | |
| scorch.classList.add('__sb-mark'); | |
| scorch.style.left = x + 'px'; | |
| scorch.style.top = y + 'px'; | |
| scorch.style.width = scorch.style.height = (size * 0.85) + 'px'; | |
| scorch.style.borderRadius = '50%'; | |
| scorch.style.background = 'radial-gradient(circle, rgba(0,0,0,0.85) 0%, rgba(20,15,10,0.55) 45%, rgba(40,25,15,0.25) 72%, rgba(0,0,0,0) 100%)'; | |
| document.body.appendChild(scorch); | |
| // pepper some debris | |
| for (let i = 0; i < 14; i++) { | |
| const a = Math.random() * Math.PI * 2; | |
| const d = Math.random() * size * 0.5; | |
| bulletHole(x + Math.cos(a) * d, y + Math.sin(a) * d, 4 + Math.random() * 4); | |
| } | |
| boom.remove(); | |
| }, 600); | |
| // shake nearby elements | |
| const r2 = (size / 2) ** 2; | |
| document.querySelectorAll('h1,h2,h3,h4,h5,h6,p,img,button,a,li,figure,picture,article,section,header,nav').forEach(el => { | |
| if (el.id === '__sb-hud' || el.classList.contains('__sb-mark') || el.classList.contains('__sb-boom')) return; | |
| const r = el.getBoundingClientRect(); | |
| const cx = r.left + r.width / 2 + window.scrollX; | |
| const cy = r.top + r.height / 2 + window.scrollY; | |
| const dx = cx - x, dy = cy - y; | |
| if (dx * dx + dy * dy < r2) { | |
| el.classList.remove('__sb-shake'); | |
| void el.offsetWidth; | |
| el.classList.add('__sb-shake'); | |
| setTimeout(() => el.classList.remove('__sb-shake'), 450); | |
| } | |
| }); | |
| }; | |
| const muzzleFlash = (x, y) => { | |
| const f = document.createElement('div'); | |
| f.classList.add('__sb-flash'); | |
| f.style.left = x + 'px'; | |
| f.style.top = y + 'px'; | |
| document.body.appendChild(f); | |
| setTimeout(() => f.remove(), 100); | |
| }; | |
| const fire = (vx, vy) => { | |
| const now = performance.now(); | |
| const g = guns[gunIdx]; | |
| if (now - lastShot < g.fireRate) return; | |
| lastShot = now; | |
| beep(g.freq, g.dur, g.type, g.vol); | |
| if (g.explosion) setTimeout(() => beep(50, 0.5, 'square', 0.55, 0.1), 90); | |
| muzzleFlash(vx, vy); | |
| const dx = vx + window.scrollX; | |
| const dy = vy + window.scrollY; | |
| if (g.explosion) { | |
| explosion(dx, dy, 240); | |
| return; | |
| } | |
| for (let i = 0; i < g.pellets; i++) { | |
| const sx = dx + (Math.random() - 0.5) * g.spread; | |
| const sy = dy + (Math.random() - 0.5) * g.spread; | |
| bulletHole(sx, sy, g.size); | |
| } | |
| }; | |
| const onMove = e => { mx = e.clientX; my = e.clientY; }; | |
| const onDown = e => { | |
| if (e.button === 2) { | |
| e.preventDefault(); e.stopPropagation(); | |
| gunIdx = (gunIdx + 1) % guns.length; | |
| renderHUD(); | |
| beep(1200, 0.05, 'sine', 0.18, 1); | |
| setTimeout(() => beep(1700, 0.06, 'sine', 0.18, 1), 55); | |
| return; | |
| } | |
| if (e.button !== 0) return; | |
| e.preventDefault(); e.stopPropagation(); | |
| if (audio.state === 'suspended') audio.resume(); | |
| firing = true; | |
| fire(e.clientX, e.clientY); | |
| const g = guns[gunIdx]; | |
| if (g.auto) { | |
| autoTimer = setInterval(() => { if (firing) fire(mx, my); }, g.fireRate); | |
| } | |
| }; | |
| const onUp = e => { | |
| if (e.button === 0) { | |
| firing = false; | |
| if (autoTimer) { clearInterval(autoTimer); autoTimer = null; } | |
| } | |
| }; | |
| const onCtx = e => { e.preventDefault(); }; | |
| document.addEventListener('mousemove', onMove, true); | |
| document.addEventListener('mousedown', onDown, true); | |
| document.addEventListener('mouseup', onUp, true); | |
| document.addEventListener('contextmenu', onCtx, true); | |
| window.__shootBrowser = { | |
| cleanup() { | |
| document.removeEventListener('mousemove', onMove, true); | |
| document.removeEventListener('mousedown', onDown, true); | |
| document.removeEventListener('mouseup', onUp, true); | |
| document.removeEventListener('contextmenu', onCtx, true); | |
| if (autoTimer) clearInterval(autoTimer); | |
| css.remove(); | |
| hud.remove(); | |
| document.querySelectorAll('.__sb-mark, .__sb-boom, .__sb-flash').forEach(el => el.remove()); | |
| } | |
| }; | |
| console.log('%c🔫 SHOOT-BROWSER ARMED', 'background:#000;color:#ffe066;font:bold 14px monospace;padding:6px 10px;border-radius:4px'); | |
| console.log('left-click: fire • right-click: swap gun • paste snippet again to disarm'); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment