Skip to content

Instantly share code, notes, and snippets.

@gahrae
Created May 4, 2026 12:43
Show Gist options
  • Select an option

  • Save gahrae/1e0ce38f575c075525d29c5dd576c612 to your computer and use it in GitHub Desktop.

Select an option

Save gahrae/1e0ce38f575c075525d29c5dd576c612 to your computer and use it in GitHub Desktop.
Game: add guns to your browser
// 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