Revert "UI redesign: CS2 gambling aesthetic, glassmorphism, responsive layout, enhanced sounds, admin panel overhaul"
This reverts commit 4b0fc594b4.
This commit is contained in:
+128
-105
@@ -1,132 +1,155 @@
|
||||
// ─── Shared CS2 Simulator Utilities ─────────────────────────────────────────
|
||||
|
||||
// Logout
|
||||
async function logout() {
|
||||
try { await fetch('/web/api/auth/logout', { method: 'POST' }); } catch (e) {}
|
||||
window.location.href = '/';
|
||||
try {
|
||||
await fetch('/web/api/auth/logout', { method: 'POST' });
|
||||
} catch (e) {}
|
||||
window.location.href = '/';
|
||||
}
|
||||
|
||||
// Refresh balance display
|
||||
async function refreshBalance() {
|
||||
try {
|
||||
const res = await fetch('/web/api/user/balance');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
document.querySelectorAll('.user-balance').forEach(el => {
|
||||
const old = el.textContent;
|
||||
const val = `${Math.floor(data.balance)} ₽`;
|
||||
if (old !== val) {
|
||||
el.textContent = val;
|
||||
el.classList.remove('animate-scaleIn');
|
||||
void el.offsetWidth;
|
||||
el.classList.add('animate-scaleIn');
|
||||
try {
|
||||
const res = await fetch('/web/api/user/balance');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
document.querySelectorAll('.user-balance').forEach(el => {
|
||||
el.textContent = `${Math.floor(data.balance)} ₽`;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Rarity colors
|
||||
function getRarityColor(rarity) {
|
||||
const colors = {
|
||||
'Consumer Grade': '#b0b0b0',
|
||||
'Industrial Grade': '#5e98d9',
|
||||
'Mil-Spec': '#4b69ff',
|
||||
'Mil-Spec Grade': '#4b69ff',
|
||||
'Restricted': '#8847ff',
|
||||
'Classified': '#d32ce6',
|
||||
'Covert': '#eb4b4b',
|
||||
'Rare Special Item': '#ffd700',
|
||||
'Extraordinary': '#ffd700',
|
||||
};
|
||||
return colors[rarity] || '#b0b0b0';
|
||||
const colors = {
|
||||
'Consumer Grade': '#b0b0b0',
|
||||
'Industrial Grade': '#5e98d9',
|
||||
'Mil-Spec': '#4b69ff',
|
||||
'Mil-Spec Grade': '#4b69ff',
|
||||
'Restricted': '#8847ff',
|
||||
'Classified': '#d32ce6',
|
||||
'Covert': '#eb4b4b',
|
||||
'Rare Special Item': '#ffd700',
|
||||
'Extraordinary': '#ffd700',
|
||||
};
|
||||
return colors[rarity] || '#b0b0b0';
|
||||
}
|
||||
|
||||
// Format number with spaces
|
||||
function formatNumber(n) {
|
||||
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
|
||||
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
|
||||
}
|
||||
|
||||
// Debounce
|
||||
function debounce(fn, ms = 300) {
|
||||
let timer;
|
||||
return function (...args) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => fn.apply(this, args), ms);
|
||||
};
|
||||
let timer;
|
||||
return function (...args) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => fn.apply(this, args), ms);
|
||||
};
|
||||
}
|
||||
|
||||
// Escape HTML
|
||||
function escapeHtml(str) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Show notification
|
||||
function showNotification(message, type = 'info', duration = 3000) {
|
||||
let container = document.getElementById('toastContainer');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'toastContainer';
|
||||
container.className = 'toast-container';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
const existing = document.querySelector('.notification-toast');
|
||||
if (existing) existing.remove();
|
||||
|
||||
const icons = { success: '✓', error: '✗', info: 'ℹ', warning: '⚠' };
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast toast-${type} animate-slideUp`;
|
||||
toast.innerHTML = `<span>${icons[type] || ''}</span><span>${message}</span>`;
|
||||
container.appendChild(toast);
|
||||
const colors = {
|
||||
success: '#22c55e',
|
||||
error: '#ef4444',
|
||||
info: '#3b82f6',
|
||||
warning: '#f59e0b',
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
toast.style.opacity = '0';
|
||||
toast.style.transform = 'translateX(100%)';
|
||||
toast.style.transition = 'all 0.3s ease';
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, duration);
|
||||
}
|
||||
|
||||
function showAchievementPopup(title, reward) {
|
||||
let popup = document.getElementById('achievementGlobalPopup');
|
||||
if (!popup) {
|
||||
popup = document.createElement('div');
|
||||
popup.id = 'achievementGlobalPopup';
|
||||
popup.className = 'modal-overlay';
|
||||
popup.style.background = 'rgba(0,0,0,0.5)';
|
||||
popup.style.alignItems = 'flex-start';
|
||||
popup.style.paddingTop = '60px';
|
||||
popup.innerHTML = `
|
||||
<div style="background:linear-gradient(135deg,#1a3a1a,#0d260d);border:1px solid #22c55e;border-radius:14px;padding:1.25rem 1.75rem;max-width:400px;width:90%;text-align:center;animation:modalSlideIn 0.3s ease;box-shadow:0 16px 48px rgba(0,0,0,0.6);">
|
||||
<div style="font-size:0.8rem;color:var(--text-dim,#94a3b8);margin-bottom:0.25rem;text-transform:uppercase;letter-spacing:0.05em;">🏆 Достижение разблокировано!</div>
|
||||
<div style="font-size:1.2rem;font-weight:600;font-family:var(--font-display,'Russo One',sans-serif);" id="globalPopupTitle"></div>
|
||||
<div style="color:#22c55e;font-size:0.95rem;margin-top:0.35rem;font-weight:600;" id="globalPopupReward"></div>
|
||||
</div>
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'notification-toast';
|
||||
toast.style.cssText = `
|
||||
position: fixed; top: 20px; right: 20px; z-index: 99999;
|
||||
background: #1a1a2e; border: 1px solid ${colors[type] || colors.info};
|
||||
color: white; padding: 12px 20px; border-radius: 8px;
|
||||
font-size: 14px; box-shadow: 0 10px 40px rgba(0,0,0,0.5);
|
||||
animation: slideInRight 0.3s ease; max-width: 400px;
|
||||
`;
|
||||
document.body.appendChild(popup);
|
||||
}
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
document.getElementById('globalPopupTitle').textContent = title;
|
||||
document.getElementById('globalPopupReward').textContent = reward ? `+${reward} ₽` : '';
|
||||
popup.style.display = 'flex';
|
||||
|
||||
if (window.SoundManager) SoundManager.achievement();
|
||||
|
||||
setTimeout(() => {
|
||||
popup.style.display = 'none';
|
||||
}, 4000);
|
||||
setTimeout(() => {
|
||||
toast.style.animation = 'slideOutRight 0.3s ease forwards';
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, duration);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const toggle = document.getElementById('mobileToggle');
|
||||
const nav = document.getElementById('navLinks');
|
||||
if (toggle && nav) {
|
||||
toggle.addEventListener('click', () => {
|
||||
nav.classList.toggle('open');
|
||||
});
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!toggle.contains(e.target) && !nav.contains(e.target)) {
|
||||
nav.classList.remove('open');
|
||||
}
|
||||
});
|
||||
}
|
||||
// Inject notification styles once
|
||||
if (!document.getElementById('notificationStyles')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'notificationStyles';
|
||||
style.textContent = `
|
||||
@keyframes slideInRight {
|
||||
from { transform: translateX(120%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
@keyframes slideOutRight {
|
||||
from { transform: translateX(0); opacity: 1; }
|
||||
to { transform: translateX(120%); opacity: 0; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
const soundToggle = document.getElementById('soundToggle');
|
||||
if (soundToggle && window.SoundManager) {
|
||||
soundToggle.addEventListener('click', () => {
|
||||
const enabled = SoundManager.toggle();
|
||||
soundToggle.textContent = enabled ? '🔊' : '🔇';
|
||||
});
|
||||
}
|
||||
});
|
||||
// Show achievement popup (global function used by templates)
|
||||
function showAchievementPopup(title, reward) {
|
||||
let popup = document.getElementById('achievementGlobalPopup');
|
||||
if (!popup) {
|
||||
popup = document.createElement('div');
|
||||
popup.id = 'achievementGlobalPopup';
|
||||
popup.style.cssText = `
|
||||
position: fixed; top: 20px; left: 50%; transform: translateX(-50%);
|
||||
z-index: 99999; background: linear-gradient(135deg, #1a3a1a, #0d260d);
|
||||
border: 1px solid #22c55e; border-radius: 12px; padding: 1rem 1.5rem;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
|
||||
animation: slideInDown 0.5s ease; max-width: 400px; width: 90%;
|
||||
text-align: center;
|
||||
`;
|
||||
popup.innerHTML = `
|
||||
<div style="font-size:0.85rem;color:#94a3b8;margin-bottom:0.25rem;">🏆 Новое достижение!</div>
|
||||
<div style="font-size:1.1rem;font-weight:600;" id="globalPopupTitle"></div>
|
||||
<div style="color:#22c55e;font-size:0.9rem;margin-top:0.25rem;" id="globalPopupReward"></div>
|
||||
`;
|
||||
document.body.appendChild(popup);
|
||||
|
||||
if (!document.getElementById('slideInDownStyle')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = 'slideInDownStyle';
|
||||
s.textContent = `
|
||||
@keyframes slideInDown {
|
||||
from { transform: translateX(-50%) translateY(-120%); opacity: 0; }
|
||||
to { transform: translateX(-50%) translateY(0); opacity: 1; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('globalPopupTitle').textContent = title;
|
||||
document.getElementById('globalPopupReward').textContent = reward ? `+${reward} ₽` : '';
|
||||
popup.style.display = 'block';
|
||||
|
||||
if (window.SoundManager) SoundManager.achievement();
|
||||
|
||||
setTimeout(() => {
|
||||
popup.style.animation = 'slideInDown 0.5s ease reverse forwards';
|
||||
setTimeout(() => {
|
||||
popup.style.display = 'none';
|
||||
popup.style.animation = 'slideInDown 0.5s ease';
|
||||
}, 500);
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
+179
-343
@@ -1,349 +1,185 @@
|
||||
// Sound effects system using Web Audio API - no external files needed
|
||||
const SoundManager = {
|
||||
_ctx: null,
|
||||
_master: null,
|
||||
_analyser: null,
|
||||
_enabled: true,
|
||||
_volume: 0.3,
|
||||
_ambient: null,
|
||||
_ambientGain: null,
|
||||
_initCalled: false,
|
||||
_ctx: null,
|
||||
_enabled: true,
|
||||
_volume: 0.3,
|
||||
|
||||
init() {
|
||||
if (this._initCalled) return;
|
||||
this._initCalled = true;
|
||||
try {
|
||||
this._ctx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
this._master = this._ctx.createGain();
|
||||
this._master.gain.value = this._volume;
|
||||
this._master.connect(this._ctx.destination);
|
||||
} catch (e) {
|
||||
console.warn('Web Audio API not supported');
|
||||
this._enabled = false;
|
||||
init() {
|
||||
try {
|
||||
this._ctx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
} catch (e) {
|
||||
console.warn('Web Audio API not supported');
|
||||
this._enabled = false;
|
||||
}
|
||||
const saved = localStorage.getItem('sound_enabled');
|
||||
if (saved !== null) this._enabled = saved === 'true';
|
||||
},
|
||||
|
||||
setEnabled(on) {
|
||||
this._enabled = on;
|
||||
localStorage.setItem('sound_enabled', on);
|
||||
},
|
||||
|
||||
toggle() {
|
||||
this.setEnabled(!this._enabled);
|
||||
return this._enabled;
|
||||
},
|
||||
|
||||
_play(freq, duration, type = 'sine', volume = 1) {
|
||||
if (!this._enabled || !this._ctx) return;
|
||||
if (this._ctx.state === 'suspended') this._ctx.resume();
|
||||
|
||||
const osc = this._ctx.createOscillator();
|
||||
const gain = this._ctx.createGain();
|
||||
osc.type = type;
|
||||
osc.frequency.setValueAtTime(freq, this._ctx.currentTime);
|
||||
gain.gain.setValueAtTime(this._volume * volume, this._ctx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, this._ctx.currentTime + duration);
|
||||
osc.connect(gain);
|
||||
gain.connect(this._ctx.destination);
|
||||
osc.start();
|
||||
osc.stop(this._ctx.currentTime + duration);
|
||||
},
|
||||
|
||||
_noise(duration, volume = 1) {
|
||||
if (!this._enabled || !this._ctx) return;
|
||||
if (this._ctx.state === 'suspended') this._ctx.resume();
|
||||
|
||||
const bufferSize = this._ctx.sampleRate * duration;
|
||||
const buffer = this._ctx.createBuffer(1, bufferSize, this._ctx.sampleRate);
|
||||
const data = buffer.getChannelData(0);
|
||||
for (let i = 0; i < bufferSize; i++) {
|
||||
data[i] = Math.random() * 2 - 1;
|
||||
}
|
||||
const source = this._ctx.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
const gain = this._ctx.createGain();
|
||||
gain.gain.setValueAtTime(this._volume * volume, this._ctx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, this._ctx.currentTime + duration);
|
||||
source.connect(gain);
|
||||
gain.connect(this._ctx.destination);
|
||||
source.start();
|
||||
},
|
||||
|
||||
caseOpen() {
|
||||
// Descending sweep with click
|
||||
this._play(800, 0.1, 'square', 0.3);
|
||||
setTimeout(() => this._play(400, 0.15, 'sawtooth', 0.2), 80);
|
||||
setTimeout(() => this._play(200, 0.2, 'sine', 0.15), 180);
|
||||
this._noise(0.05, 0.4);
|
||||
},
|
||||
|
||||
caseRare() {
|
||||
// Ascending bright chime for rare items
|
||||
this._play(523, 0.15, 'sine', 0.3);
|
||||
setTimeout(() => this._play(659, 0.15, 'sine', 0.3), 100);
|
||||
setTimeout(() => this._play(784, 0.15, 'sine', 0.3), 200);
|
||||
setTimeout(() => this._play(1047, 0.3, 'sine', 0.4), 300);
|
||||
},
|
||||
|
||||
caseCovert() {
|
||||
// Epic sound for covert/knife
|
||||
this._play(392, 0.2, 'sawtooth', 0.3);
|
||||
setTimeout(() => this._play(523, 0.2, 'sawtooth', 0.3), 150);
|
||||
setTimeout(() => this._play(659, 0.2, 'sawtooth', 0.3), 300);
|
||||
setTimeout(() => this._play(784, 0.4, 'sine', 0.5), 450);
|
||||
setTimeout(() => this._play(1047, 0.6, 'sine', 0.6), 600);
|
||||
},
|
||||
|
||||
slotSpin() {
|
||||
// Ratcheting sound for slot reels
|
||||
for (let i = 0; i < 8; i++) {
|
||||
setTimeout(() => {
|
||||
this._play(300 + Math.random() * 400, 0.05, 'square', 0.15);
|
||||
}, i * 80);
|
||||
}
|
||||
},
|
||||
|
||||
slotMatch() {
|
||||
// Win jingle
|
||||
this._play(523, 0.1, 'sine', 0.3);
|
||||
setTimeout(() => this._play(659, 0.1, 'sine', 0.3), 100);
|
||||
setTimeout(() => this._play(784, 0.1, 'sine', 0.3), 200);
|
||||
setTimeout(() => this._play(1047, 0.4, 'sine', 0.5), 300);
|
||||
},
|
||||
|
||||
contractSubmit() {
|
||||
// Mechanical sound
|
||||
this._play(150, 0.3, 'sawtooth', 0.2);
|
||||
setTimeout(() => this._play(200, 0.2, 'square', 0.2), 200);
|
||||
setTimeout(() => this._play(250, 0.1, 'square', 0.15), 350);
|
||||
},
|
||||
|
||||
contractResult() {
|
||||
// Rising success
|
||||
this._play(400, 0.15, 'sine', 0.3);
|
||||
setTimeout(() => this._play(600, 0.15, 'sine', 0.3), 120);
|
||||
setTimeout(() => this._play(800, 0.3, 'sine', 0.4), 240);
|
||||
},
|
||||
|
||||
wheelSpin() {
|
||||
// Ticking as wheel rotates
|
||||
this._play(200, 0.03, 'square', 0.1);
|
||||
},
|
||||
|
||||
wheelWin() {
|
||||
// Celebration
|
||||
this._play(523, 0.15, 'sine', 0.3);
|
||||
setTimeout(() => this._play(659, 0.15, 'sine', 0.3), 120);
|
||||
setTimeout(() => this._play(784, 0.2, 'sine', 0.35), 240);
|
||||
setTimeout(() => this._play(1047, 0.5, 'sine', 0.5), 360);
|
||||
},
|
||||
|
||||
wheelLose() {
|
||||
// Sad trombone
|
||||
this._play(400, 0.2, 'sine', 0.2);
|
||||
setTimeout(() => this._play(350, 0.2, 'sine', 0.2), 150);
|
||||
setTimeout(() => this._play(300, 0.3, 'sine', 0.15), 300);
|
||||
},
|
||||
|
||||
achievement() {
|
||||
// Achievement unlock
|
||||
this._play(659, 0.1, 'sine', 0.3);
|
||||
setTimeout(() => this._play(523, 0.1, 'sine', 0.3), 100);
|
||||
setTimeout(() => this._play(784, 0.1, 'sine', 0.3), 200);
|
||||
setTimeout(() => this._play(659, 0.1, 'sine', 0.3), 300);
|
||||
setTimeout(() => this._play(1047, 0.4, 'sine', 0.5), 400);
|
||||
},
|
||||
|
||||
crashBet() {
|
||||
this._play(300, 0.1, 'square', 0.15);
|
||||
},
|
||||
|
||||
crashTick() {
|
||||
this._play(500 + Math.random() * 500, 0.03, 'sine', 0.08);
|
||||
},
|
||||
|
||||
crashCrashed() {
|
||||
// Explosion
|
||||
this._noise(0.3, 0.6);
|
||||
this._play(80, 0.5, 'sawtooth', 0.4);
|
||||
setTimeout(() => this._play(50, 0.8, 'sine', 0.3), 100);
|
||||
},
|
||||
|
||||
crashCashout() {
|
||||
// Cashout success
|
||||
this._play(600, 0.1, 'sine', 0.3);
|
||||
setTimeout(() => this._play(800, 0.1, 'sine', 0.3), 80);
|
||||
setTimeout(() => this._play(1000, 0.2, 'sine', 0.4), 160);
|
||||
},
|
||||
|
||||
click() {
|
||||
this._play(800, 0.03, 'sine', 0.1);
|
||||
},
|
||||
|
||||
error() {
|
||||
this._play(200, 0.15, 'square', 0.2);
|
||||
},
|
||||
|
||||
balanceUpdate() {
|
||||
this._play(1000, 0.05, 'sine', 0.1);
|
||||
}
|
||||
const saved = localStorage.getItem('sound_enabled');
|
||||
if (saved !== null) this._enabled = saved === 'true';
|
||||
const vol = localStorage.getItem('sound_volume');
|
||||
if (vol !== null) this.setVolume(parseFloat(vol));
|
||||
|
||||
document.addEventListener('click', () => {
|
||||
if (this._ctx && this._ctx.state === 'suspended') this._ctx.resume();
|
||||
}, { once: true });
|
||||
},
|
||||
|
||||
setEnabled(on) {
|
||||
this._enabled = on;
|
||||
localStorage.setItem('sound_enabled', on);
|
||||
if (!on && this._ambientGain) {
|
||||
this._ambientGain.gain.setTargetAtTime(0, this._ctx.currentTime, 0.5);
|
||||
}
|
||||
},
|
||||
|
||||
toggle() {
|
||||
this.setEnabled(!this._enabled);
|
||||
return this._enabled;
|
||||
},
|
||||
|
||||
setVolume(v) {
|
||||
this._volume = Math.max(0, Math.min(1, v));
|
||||
if (this._master) this._master.gain.value = this._volume;
|
||||
localStorage.setItem('sound_volume', this._volume);
|
||||
},
|
||||
|
||||
_t() {
|
||||
return this._ctx ? this._ctx.currentTime : 0;
|
||||
},
|
||||
|
||||
_osc(freq, duration, type = 'sine', vol = 1, dest) {
|
||||
if (!this._enabled || !this._ctx) return;
|
||||
if (this._ctx.state === 'suspended') this._ctx.resume();
|
||||
const now = this._t();
|
||||
const o = this._ctx.createOscillator();
|
||||
const g = this._ctx.createGain();
|
||||
o.type = type;
|
||||
o.frequency.setValueAtTime(freq, now);
|
||||
g.gain.setValueAtTime(vol, now);
|
||||
g.gain.exponentialRampToValueAtTime(0.001, now + duration);
|
||||
o.connect(g);
|
||||
g.connect(dest || this._master);
|
||||
o.start(now);
|
||||
o.stop(now + duration);
|
||||
},
|
||||
|
||||
_noise(duration, vol = 1, filterFreq, dest) {
|
||||
if (!this._enabled || !this._ctx) return;
|
||||
if (this._ctx.state === 'suspended') this._ctx.resume();
|
||||
const now = this._t();
|
||||
const sr = this._ctx.sampleRate;
|
||||
const len = sr * duration;
|
||||
const buf = this._ctx.createBuffer(1, len, sr);
|
||||
const data = buf.getChannelData(0);
|
||||
for (let i = 0; i < len; i++) data[i] = Math.random() * 2 - 1;
|
||||
const src = this._ctx.createBufferSource();
|
||||
src.buffer = buf;
|
||||
let node = src;
|
||||
if (filterFreq) {
|
||||
const f = this._ctx.createBiquadFilter();
|
||||
f.type = 'lowpass';
|
||||
f.frequency.value = filterFreq;
|
||||
src.connect(f);
|
||||
node = f;
|
||||
}
|
||||
const g = this._ctx.createGain();
|
||||
g.gain.setValueAtTime(vol, now);
|
||||
g.gain.exponentialRampToValueAtTime(0.001, now + duration);
|
||||
node.connect(g);
|
||||
g.connect(dest || this._master);
|
||||
src.start(now);
|
||||
},
|
||||
|
||||
_chord(freqs, duration, type = 'sine', vol = 0.15) {
|
||||
freqs.forEach(f => this._osc(f, duration, type, vol / freqs.length));
|
||||
},
|
||||
|
||||
// ── Rarity color helpers ──
|
||||
_rarityParams(rarity) {
|
||||
const map = {
|
||||
'Consumer Grade': { baseFreq: 300, arp: [300, 350, 400], noiseVol: 0.2, dur: 0.6 },
|
||||
'Industrial Grade': { baseFreq: 400, arp: [400, 500, 600], noiseVol: 0.2, dur: 0.7 },
|
||||
'Mil-Spec': { baseFreq: 500, arp: [500, 630, 750], noiseVol: 0.25, dur: 0.8 },
|
||||
'Mil-Spec Grade': { baseFreq: 500, arp: [500, 630, 750], noiseVol: 0.25, dur: 0.8 },
|
||||
'Restricted': { baseFreq: 600, arp: [600, 750, 900], noiseVol: 0.3, dur: 1.0 },
|
||||
'Classified': { baseFreq: 700, arp: [700, 880, 1050], noiseVol: 0.35, dur: 1.2 },
|
||||
'Covert': { baseFreq: 800, arp: [800, 1000, 1200], noiseVol: 0.4, dur: 1.5 },
|
||||
'Rare Special Item': { baseFreq: 900, arp: [900, 1100, 1400], noiseVol: 0.45, dur: 1.8 },
|
||||
'Extraordinary': { baseFreq: 900, arp: [900, 1100, 1400], noiseVol: 0.45, dur: 1.8 },
|
||||
};
|
||||
return map[rarity] || map['Consumer Grade'];
|
||||
},
|
||||
|
||||
// ── Ambient ──
|
||||
startAmbient() {
|
||||
if (!this._ctx || this._ambient) return;
|
||||
const now = this._t();
|
||||
this._ambientGain = this._ctx.createGain();
|
||||
this._ambientGain.gain.value = 0;
|
||||
|
||||
const bufLen = this._ctx.sampleRate * 2;
|
||||
const buf = this._ctx.createBuffer(1, bufLen, this._ctx.sampleRate);
|
||||
const data = buf.getChannelData(0);
|
||||
for (let i = 0; i < bufLen; i++) {
|
||||
const t = i / this._ctx.sampleRate;
|
||||
data[i] = (Math.random() - 0.5) * 0.5
|
||||
+ Math.sin(2 * Math.PI * 55 * t) * 0.08
|
||||
+ Math.sin(2 * Math.PI * 82.5 * t) * 0.05;
|
||||
}
|
||||
const src = this._ctx.createBufferSource();
|
||||
src.buffer = buf;
|
||||
src.loop = true;
|
||||
|
||||
const lp = this._ctx.createBiquadFilter();
|
||||
lp.type = 'lowpass';
|
||||
lp.frequency.value = 300;
|
||||
|
||||
const hp = this._ctx.createBiquadFilter();
|
||||
hp.type = 'highpass';
|
||||
hp.frequency.value = 40;
|
||||
|
||||
src.connect(lp);
|
||||
lp.connect(hp);
|
||||
hp.connect(this._ambientGain);
|
||||
this._ambientGain.connect(this._master);
|
||||
src.start();
|
||||
this._ambient = src;
|
||||
this._ambientGain.gain.setTargetAtTime(0.06, now, 1);
|
||||
},
|
||||
|
||||
stopAmbient() {
|
||||
if (this._ambientGain) {
|
||||
this._ambientGain.gain.setTargetAtTime(0, this._t(), 0.5);
|
||||
setTimeout(() => {
|
||||
if (this._ambient) { try { this._ambient.stop(); } catch (e) {} this._ambient = null; }
|
||||
}, 1500);
|
||||
}
|
||||
},
|
||||
|
||||
// ── Case Opening ──
|
||||
caseOpen() {
|
||||
this._noise(0.15, 0.4, 2000);
|
||||
this._osc(700, 0.06, 'square', 0.25);
|
||||
setTimeout(() => this._osc(500, 0.08, 'square', 0.2), 50);
|
||||
setTimeout(() => this._osc(350, 0.1, 'sawtooth', 0.18), 110);
|
||||
setTimeout(() => this._osc(250, 0.12, 'sine', 0.12), 180);
|
||||
setTimeout(() => this._noise(0.3, 0.15, 4000), 120);
|
||||
},
|
||||
|
||||
caseReveal(rarity) {
|
||||
const p = this._rarityParams(rarity);
|
||||
this._noise(0.08, 0.3, 3000);
|
||||
p.arp.forEach((f, i) => {
|
||||
setTimeout(() => {
|
||||
this._osc(f, 0.15, 'sine', 0.25);
|
||||
this._osc(f * 2, 0.1, 'sine', 0.08);
|
||||
}, i * 100);
|
||||
});
|
||||
setTimeout(() => {
|
||||
this._osc(p.baseFreq * 2, 0.4, 'sine', 0.35);
|
||||
this._noise(0.15, p.noiseVol, 5000);
|
||||
this._osc(p.baseFreq, 0.6, 'sawtooth', 0.15);
|
||||
}, p.arp.length * 100);
|
||||
},
|
||||
|
||||
caseCovert() {
|
||||
this._noise(0.1, 0.5, 4000);
|
||||
[392, 523, 659, 784, 1047].forEach((f, i) => {
|
||||
setTimeout(() => {
|
||||
this._osc(f, 0.2, 'sawtooth', 0.3);
|
||||
this._osc(f * 1.5, 0.15, 'sine', 0.1);
|
||||
}, i * 120);
|
||||
});
|
||||
setTimeout(() => {
|
||||
this._osc(1047, 0.8, 'sine', 0.6);
|
||||
this._osc(784, 0.6, 'triangle', 0.25);
|
||||
this._noise(0.6, 0.4, 6000);
|
||||
}, 600);
|
||||
},
|
||||
|
||||
caseRare() {
|
||||
[523, 659, 784, 1047].forEach((f, i) => {
|
||||
setTimeout(() => {
|
||||
this._osc(f, 0.12, 'sine', 0.3);
|
||||
this._osc(f * 1.5, 0.08, 'sine', 0.06);
|
||||
}, i * 80);
|
||||
});
|
||||
setTimeout(() => this._osc(1319, 0.5, 'sine', 0.5), 320);
|
||||
},
|
||||
|
||||
// ── Slots ──
|
||||
slotSpin() {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
setTimeout(() => {
|
||||
this._osc(200 + Math.random() * 600, 0.04, 'square', 0.12);
|
||||
}, i * 60);
|
||||
}
|
||||
},
|
||||
|
||||
slotMatch() {
|
||||
[523, 659, 784, 1047].forEach((f, i) => {
|
||||
setTimeout(() => this._osc(f, 0.15, 'sine', 0.35), i * 100);
|
||||
});
|
||||
setTimeout(() => this._noise(0.2, 0.3, 8000), 300);
|
||||
},
|
||||
|
||||
// ── Contract ──
|
||||
contractSubmit() {
|
||||
this._osc(150, 0.25, 'sawtooth', 0.2);
|
||||
setTimeout(() => this._osc(200, 0.15, 'square', 0.18), 150);
|
||||
setTimeout(() => {
|
||||
this._osc(250, 0.08, 'square', 0.15);
|
||||
this._noise(0.05, 0.15);
|
||||
}, 280);
|
||||
},
|
||||
|
||||
contractResult() {
|
||||
[400, 600, 800].forEach((f, i) => {
|
||||
setTimeout(() => this._osc(f, 0.15, 'sine', 0.3), i * 100);
|
||||
});
|
||||
setTimeout(() => {
|
||||
this._osc(1200, 0.4, 'sine', 0.45);
|
||||
this._noise(0.2, 0.2, 5000);
|
||||
}, 300);
|
||||
},
|
||||
|
||||
// ── Wheel ──
|
||||
wheelTick() {
|
||||
this._osc(200, 0.03, 'square', 0.08);
|
||||
},
|
||||
|
||||
wheelWin() {
|
||||
[523, 659, 784, 1047].forEach((f, i) => {
|
||||
setTimeout(() => this._osc(f, 0.18, 'sine', 0.35), i * 100);
|
||||
});
|
||||
setTimeout(() => this._noise(0.4, 0.3, 8000), 400);
|
||||
},
|
||||
|
||||
wheelLose() {
|
||||
[400, 350, 300].forEach((f, i) => {
|
||||
setTimeout(() => this._osc(f, 0.25, 'sine', 0.2), i * 150);
|
||||
});
|
||||
},
|
||||
|
||||
// ── Achievements ──
|
||||
achievement() {
|
||||
[659, 523, 784, 659, 1047].forEach((f, i) => {
|
||||
setTimeout(() => this._osc(f, 0.12, 'sine', 0.3), i * 80);
|
||||
});
|
||||
setTimeout(() => this._noise(0.3, 0.25, 6000), 400);
|
||||
},
|
||||
|
||||
// ── Crash ──
|
||||
crashBet() {
|
||||
this._osc(300, 0.08, 'square', 0.15);
|
||||
setTimeout(() => this._osc(350, 0.06, 'square', 0.1), 60);
|
||||
},
|
||||
|
||||
crashTick(multiplier) {
|
||||
const freq = 300 + Math.min(multiplier * 50, 800);
|
||||
this._osc(freq, 0.03, 'sine', 0.05 + Math.min(multiplier * 0.01, 0.15));
|
||||
},
|
||||
|
||||
crashCrashed() {
|
||||
this._noise(0.5, 0.6, 2000);
|
||||
this._osc(80, 0.6, 'sawtooth', 0.4);
|
||||
setTimeout(() => this._osc(50, 1, 'sine', 0.3), 100);
|
||||
setTimeout(() => this._osc(40, 0.8, 'triangle', 0.2), 200);
|
||||
},
|
||||
|
||||
crashCashout(multiplier) {
|
||||
const vol = 0.2 + Math.min(multiplier * 0.02, 0.5);
|
||||
[600, 800, 1000, 1200].forEach((f, i) => {
|
||||
setTimeout(() => this._osc(f, 0.12, 'sine', vol), i * 80);
|
||||
});
|
||||
setTimeout(() => this._noise(0.3, 0.3, 8000), 320);
|
||||
},
|
||||
|
||||
// ── Upgrade ──
|
||||
upgradeSubmit() {
|
||||
this._osc(250, 0.1, 'square', 0.15);
|
||||
this._noise(0.05, 0.2);
|
||||
},
|
||||
|
||||
upgradeResult() {
|
||||
[523, 784, 1047].forEach((f, i) => {
|
||||
setTimeout(() => this._osc(f, 0.2, 'sine', 0.3), i * 120);
|
||||
});
|
||||
setTimeout(() => this._noise(0.25, 0.25, 6000), 360);
|
||||
},
|
||||
|
||||
upgradeFail() {
|
||||
[300, 250, 200].forEach((f, i) => {
|
||||
setTimeout(() => this._osc(f, 0.2, 'sawtooth', 0.2), i * 120);
|
||||
});
|
||||
},
|
||||
|
||||
// ── UI ──
|
||||
click() {
|
||||
this._osc(800, 0.03, 'sine', 0.08);
|
||||
},
|
||||
|
||||
hover() {
|
||||
this._osc(1200, 0.015, 'sine', 0.03);
|
||||
},
|
||||
|
||||
error() {
|
||||
this._osc(200, 0.15, 'square', 0.2);
|
||||
setTimeout(() => this._osc(180, 0.12, 'square', 0.15), 100);
|
||||
},
|
||||
|
||||
success() {
|
||||
[600, 800, 1000].forEach((f, i) => {
|
||||
setTimeout(() => this._osc(f, 0.08, 'sine', 0.2), i * 60);
|
||||
});
|
||||
},
|
||||
|
||||
balanceUpdate() {
|
||||
this._osc(1000, 0.04, 'sine', 0.08);
|
||||
setTimeout(() => this._osc(1200, 0.04, 'sine', 0.06), 50);
|
||||
},
|
||||
};
|
||||
|
||||
// Auto-init
|
||||
document.addEventListener('DOMContentLoaded', () => SoundManager.init());
|
||||
|
||||
Reference in New Issue
Block a user