Mediawiki:common.js
| Строка 2220: | Строка 2220: | ||
} | } | ||
}; | }; | ||
var AprilFoolsCubeTransitionModule = { | var AprilFoolsCubeTransitionModule = { | ||
overlay: null, | overlay: null, | ||
clockTimer: null, | clockTimer: null, | ||
audioContext: null, | audioContext: null, | ||
started: false, | started: false, | ||
// Сейчас false: пранк только по ?prank=1 | |||
// Сейчас: только ?prank=1 | // Когда захочешь включить всем 1 апреля — просто поставь true | ||
ENABLE_FOR_ALL_ON_APRIL_FIRST: false, | ENABLE_FOR_ALL_ON_APRIL_FIRST: false, | ||
init: function () { | init: function () { | ||
| Строка 2242: | Строка 2239: | ||
} | } | ||
var params = new URLSearchParams(window.location.search); | |||
var prankMode = params.get('prank') === '1'; | |||
var now = new Date(); | |||
var isAprilFirst = now.getMonth() === 3 && now.getDate() === 1; | |||
var allowForAll = this.ENABLE_FOR_ALL_ON_APRIL_FIRST && isAprilFirst; | |||
var shouldRun = prankMode || allowForAll; | |||
if (!shouldRun) { | |||
return; | return; | ||
} | } | ||
// | // Ограничение "один раз за сессию" — только для массового режима | ||
if (sessionStorage.getItem('april-fools-cube-shown') === '1') { | if (!prankMode) { | ||
if (sessionStorage.getItem('april-fools-cube-shown') === '1') { | |||
return; | |||
} | |||
sessionStorage.setItem('april-fools-cube-shown', '1'); | |||
} | } | ||
this.injectStyles(); | this.injectStyles(); | ||
this.createOverlay(); | this.createOverlay(); | ||
this. | this.initAudio(); | ||
this.startClockTick(); // стартуем сразу | |||
this.showOverlay(); | |||
var self = this; | var self = this; | ||
setTimeout(function () { | setTimeout(function () { | ||
self.startExitTransition(); | self.startExitTransition(); | ||
}, | }, 6000); | ||
}, | }, | ||
| Строка 2306: | Строка 2287: | ||
'opacity:0;' + | 'opacity:0;' + | ||
'visibility:hidden;' + | 'visibility:hidden;' + | ||
'overflow:hidden;' + | |||
'transform:translateZ(0);' + | 'transform:translateZ(0);' + | ||
'will-change:transform,opacity;' + | 'will-change:transform,opacity;' + | ||
'}' + | '}' + | ||
'#april-fools-overlay.active{' + | '#april-fools-overlay.active{' + | ||
| Строка 2315: | Строка 2296: | ||
'}' + | '}' + | ||
'#april-fools-overlay img{' + | '#april-fools-overlay img{' + | ||
'width: | 'width:100vw;' + | ||
'height: | 'height:100vh;' + | ||
'object-fit:cover;' + | 'object-fit:cover;' + // ширина/высота заполняются, лишнее режется | ||
'object-position:center top;' + // чуть приятнее для такого скрина | |||
'display:block;' + | 'display:block;' + | ||
'user-select:none;' + | 'user-select:none;' + | ||
| Строка 2350: | Строка 2332: | ||
overlay.appendChild(image); | overlay.appendChild(image); | ||
document.body.appendChild(overlay); | document.body.appendChild(overlay); | ||
this.overlay = overlay; | this.overlay = overlay; | ||
}, | }, | ||
| Строка 2376: | Строка 2357: | ||
}, | }, | ||
initAudio: function () { | |||
try { | try { | ||
var Ctx = window.AudioContext || window.webkitAudioContext; | var Ctx = window.AudioContext || window.webkitAudioContext; | ||
| Строка 2382: | Строка 2363: | ||
this.audioContext = new Ctx(); | this.audioContext = new Ctx(); | ||
} catch (e) { | } catch (e) { | ||
this.audioContext = null; | |||
return; | return; | ||
} | } | ||
| Строка 2387: | Строка 2369: | ||
var self = this; | var self = this; | ||
// Best-effort сразу | |||
if (this.audioContext.state === 'suspended') { | |||
this.audioContext.resume().catch(function () {}); | |||
} | |||
// Гарантия: как только пользователь взаимодействует — точно резюмим | |||
function unlock() { | function unlock() { | ||
if (!self.audioContext) return; | if (!self.audioContext) return; | ||
self.audioContext.resume().catch(function () {}); | |||
self.audioContext.resume( | |||
document.removeEventListener('pointerdown', unlock); | document.removeEventListener('pointerdown', unlock); | ||
document.removeEventListener('keydown', unlock); | document.removeEventListener('keydown', unlock); | ||
| Строка 2405: | Строка 2386: | ||
document.addEventListener('keydown', unlock, { once: true }); | document.addEventListener('keydown', unlock, { once: true }); | ||
document.addEventListener('touchstart', unlock, { once: true }); | document.addEventListener('touchstart', unlock, { once: true }); | ||
}, | }, | ||
startClockTick: function () { | startClockTick: function () { | ||
if (!this.audioContext | if (!this.audioContext || this.clockTimer) return; | ||
var self = this; | var self = this; | ||
function | function safeTick() { | ||
if (!self.audioContext) return; | |||
// Повторяем попытки resume, если браузер всё ещё держит suspended | |||
if (self.audioContext.state === 'suspended') { | |||
self.audioContext.resume().catch(function () {}); | |||
return; | |||
} | |||
self.playTick(); | self.playTick(); | ||
} | } | ||
safeTick(); | |||
this.clockTimer = setInterval( | this.clockTimer = setInterval(safeTick, 900); | ||
}, | }, | ||
| Строка 2432: | Строка 2416: | ||
}, | }, | ||
// Тик ближе к "механическому" | |||
playTick: function () { | playTick: function () { | ||
if (!this.audioContext || this.audioContext.state !== 'running') return; | if (!this.audioContext || this.audioContext.state !== 'running') return; | ||
| Строка 2437: | Строка 2422: | ||
var t = this.audioContext.currentTime; | var t = this.audioContext.currentTime; | ||
var | var clickNoiseBuffer = this.audioContext.createBuffer(1, 512, this.audioContext.sampleRate); | ||
var | var data = clickNoiseBuffer.getChannelData(0); | ||
for (var i = 0; i < data.length; i++) { | |||
data[i] = (Math.random() * 2 - 1) * (1 - i / data.length); | |||
} | |||
var source = this.audioContext.createBufferSource(); | |||
source.buffer = clickNoiseBuffer; | |||
var hp = this.audioContext.createBiquadFilter(); | var hp = this.audioContext.createBiquadFilter(); | ||
hp.type = 'highpass'; | hp.type = 'highpass'; | ||
hp.frequency.value = | hp.frequency.value = 1800; | ||
var bp = this.audioContext.createBiquadFilter(); | |||
bp.type = 'bandpass'; | |||
bp.frequency.value = 2600; | |||
bp.Q.value = 1.5; | |||
var gain = this.audioContext.createGain(); | |||
gain.gain.setValueAtTime(0.0001, t); | gain.gain.setValueAtTime(0.0001, t); | ||
gain.gain.exponentialRampToValueAtTime(0. | gain.gain.exponentialRampToValueAtTime(0.09, t + 0.002); | ||
gain.gain.exponentialRampToValueAtTime(0.0001, t + 0. | gain.gain.exponentialRampToValueAtTime(0.0001, t + 0.05); | ||
source.connect(hp); | |||
hp.connect(gain); | hp.connect(bp); | ||
bp.connect(gain); | |||
gain.connect(this.audioContext.destination); | gain.connect(this.audioContext.destination); | ||
source.start(t); | |||
source.stop(t + 0.06); | |||
}, | }, | ||
// "Сдвиг куба": whoosh + низкочастотный удар | |||
playCubeShiftSound: function () { | playCubeShiftSound: function () { | ||
if (!this.audioContext || this.audioContext.state !== 'running') return; | if (!this.audioContext || this.audioContext.state !== 'running') return; | ||
| Строка 2464: | Строка 2460: | ||
var t = this.audioContext.currentTime; | var t = this.audioContext.currentTime; | ||
var | // Whoosh (шумовой слой) | ||
var | var duration = 0.55; | ||
var | var bufferSize = Math.floor(this.audioContext.sampleRate * duration); | ||
var noiseBuffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate); | |||
var ch = noiseBuffer.getChannelData(0); | |||
for (var i = 0; i < bufferSize; i++) { | |||
var env = 1 - (i / bufferSize); | |||
ch[i] = (Math.random() * 2 - 1) * env; | |||
} | |||
var noise = this.audioContext.createBufferSource(); | |||
noise.buffer = noiseBuffer; | |||
var noiseFilter = this.audioContext.createBiquadFilter(); | |||
noiseFilter.type = 'bandpass'; | |||
noiseFilter.frequency.setValueAtTime(1200, t); | |||
noiseFilter.frequency.exponentialRampToValueAtTime(320, t + duration); | |||
noiseFilter.Q.value = 0.9; | |||
var noiseGain = this.audioContext.createGain(); | |||
noiseGain.gain.setValueAtTime(0.0001, t); | |||
noiseGain.gain.exponentialRampToValueAtTime(0.22, t + 0.03); | |||
noiseGain.gain.exponentialRampToValueAtTime(0.0001, t + duration); | |||
noise.connect(noiseFilter); | |||
noiseFilter.connect(noiseGain); | |||
noiseGain.connect(this.audioContext.destination); | |||
noise.start(t); | |||
noise.stop(t + duration); | |||
osc.type = ' | // Низкий "thud" | ||
osc.frequency.setValueAtTime( | var osc = this.audioContext.createOscillator(); | ||
osc.frequency.exponentialRampToValueAtTime( | osc.type = 'triangle'; | ||
osc.frequency.setValueAtTime(130, t + 0.12); | |||
osc.frequency.exponentialRampToValueAtTime(58, t + 0.35); | |||
var oscGain = this.audioContext.createGain(); | |||
oscGain.gain.setValueAtTime(0.0001, t + 0.1); | |||
oscGain.gain.exponentialRampToValueAtTime(0.18, t + 0.13); | |||
oscGain.gain.exponentialRampToValueAtTime(0.0001, t + 0.4); | |||
osc.connect( | osc.connect(oscGain); | ||
oscGain.connect(this.audioContext.destination); | |||
osc.start(t); | osc.start(t + 0.1); | ||
osc.stop(t + 0. | osc.stop(t + 0.45); | ||
} | } | ||
}; | }; | ||
function initAllModules() { | function initAllModules() { | ||
SidebarModule.init(); | SidebarModule.init(); | ||