Mediawiki:common.js

Вернуться

Строка 2220: Строка 2220:
     }
     }
};
};
var AprilFoolsCubeTransitionModule = {
var AprilFoolsCubeTransitionModule = {
     overlay: null,
     overlay: null,
     clockTimer: null,
     clockTimer: null,
     audioContext: null,
     audioContext: null,
    audioReady: false,
     started: false,
     started: false,


    // === БЫСТРЫЕ ПЕРЕКЛЮЧАТЕЛИ ===
     // Сейчас false: пранк только по ?prank=1
     // Сейчас: только ?prank=1
     // Когда захочешь включить всем 1 апреля — просто поставь true
     ENABLE_BY_FLAG: true,
     ENABLE_FOR_ALL_ON_APRIL_FIRST: false,
     ENABLE_FOR_ALL_ON_APRIL_FIRST: false,
    ENABLE_FOR_ALL_ALWAYS: false,
    // ============================


     init: function () {
     init: function () {
Строка 2242: Строка 2239:
         }
         }


         if (!this.shouldRun()) {
         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) {
             return;
            if (sessionStorage.getItem('april-fools-cube-shown') === '1') {
                return;
            }
             sessionStorage.setItem('april-fools-cube-shown', '1');
         }
         }
        sessionStorage.setItem('april-fools-cube-shown', '1');


         this.injectStyles();
         this.injectStyles();
         this.createOverlay();
         this.createOverlay();
         this.initAudioUnlock();
         this.initAudio();
        this.startClockTick(); // стартуем сразу
        this.showOverlay();


         var self = this;
         var self = this;
        this.showOverlay();
         setTimeout(function () {
         setTimeout(function () {
             self.startExitTransition();
             self.startExitTransition();
         }, 6500);
         }, 6000);
    },
 
    shouldRun: function () {
        if (this.ENABLE_FOR_ALL_ALWAYS) {
            return true;
        }
 
        if (this.ENABLE_FOR_ALL_ON_APRIL_FIRST && this.isAprilFirst()) {
            return true;
        }
 
        if (this.ENABLE_BY_FLAG && this.hasPrankFlag()) {
            return true;
        }
 
        return false;
    },
 
    isAprilFirst: function () {
        var now = new Date();
        return now.getMonth() === 3 && now.getDate() === 1;
    },
 
    hasPrankFlag: function () {
        var params = new URLSearchParams(window.location.search);
        return params.get('prank') === '1';
     },
     },


Строка 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;' +
                'overflow:hidden;' +
             '}' +
             '}' +
             '#april-fools-overlay.active{' +
             '#april-fools-overlay.active{' +
Строка 2315: Строка 2296:
             '}' +
             '}' +
             '#april-fools-overlay img{' +
             '#april-fools-overlay img{' +
                 'width:100%;' +
                 'width:100vw;' +
                 'height:100%;' +
                 '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:
     },
     },


     initAudioUnlock: function () {
     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().then(function () {
                self.audioReady = true;
                self.startClockTick();
            }).catch(function () {
                self.audioReady = false;
            });
 
             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 });
        if (this.audioContext.state === 'running') {
            this.audioReady = true;
            this.startClockTick();
        }
     },
     },


     startClockTick: function () {
     startClockTick: function () {
         if (!this.audioContext || !this.audioReady || this.clockTimer) return;
         if (!this.audioContext || this.clockTimer) return;


         var self = this;
         var self = this;


         function tick() {
         function safeTick() {
            if (!self.audioContext) return;
 
            // Повторяем попытки resume, если браузер всё ещё держит suspended
            if (self.audioContext.state === 'suspended') {
                self.audioContext.resume().catch(function () {});
                return;
            }
 
             self.playTick();
             self.playTick();
         }
         }


         tick();
         safeTick();
         this.clockTimer = setInterval(tick, 1000);
         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 osc = this.audioContext.createOscillator();
         var clickNoiseBuffer = this.audioContext.createBuffer(1, 512, this.audioContext.sampleRate);
         var gain = this.audioContext.createGain();
         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 = 900;
         hp.frequency.value = 1800;


         osc.type = 'square';
         var bp = this.audioContext.createBiquadFilter();
         osc.frequency.setValueAtTime(1400, t);
        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.08, t + 0.002);
         gain.gain.exponentialRampToValueAtTime(0.09, t + 0.002);
         gain.gain.exponentialRampToValueAtTime(0.0001, t + 0.035);
         gain.gain.exponentialRampToValueAtTime(0.0001, t + 0.05);


         osc.connect(hp);
         source.connect(hp);
         hp.connect(gain);
         hp.connect(bp);
        bp.connect(gain);
         gain.connect(this.audioContext.destination);
         gain.connect(this.audioContext.destination);


         osc.start(t);
         source.start(t);
         osc.stop(t + 0.04);
         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 osc = this.audioContext.createOscillator();
        // Whoosh (шумовой слой)
         var gain = this.audioContext.createGain();
        var duration = 0.55;
         var lp = this.audioContext.createBiquadFilter();
        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);


         lp.type = 'lowpass';
         noise.start(t);
        lp.frequency.setValueAtTime(1200, t);
         noise.stop(t + duration);
         lp.frequency.exponentialRampToValueAtTime(220, t + 0.45);


         osc.type = 'sawtooth';
        // Низкий "thud"
         osc.frequency.setValueAtTime(380, t);
        var osc = this.audioContext.createOscillator();
         osc.frequency.exponentialRampToValueAtTime(90, t + 0.45);
         osc.type = 'triangle';
         osc.frequency.setValueAtTime(130, t + 0.12);
         osc.frequency.exponentialRampToValueAtTime(58, t + 0.35);


         gain.gain.setValueAtTime(0.0001, t);
         var oscGain = this.audioContext.createGain();
         gain.gain.exponentialRampToValueAtTime(0.18, t + 0.02);
        oscGain.gain.setValueAtTime(0.0001, t + 0.1);
         gain.gain.exponentialRampToValueAtTime(0.0001, t + 0.5);
         oscGain.gain.exponentialRampToValueAtTime(0.18, t + 0.13);
         oscGain.gain.exponentialRampToValueAtTime(0.0001, t + 0.4);


         osc.connect(lp);
         osc.connect(oscGain);
         lp.connect(gain);
         oscGain.connect(this.audioContext.destination);
        gain.connect(this.audioContext.destination);


         osc.start(t);
         osc.start(t + 0.1);
         osc.stop(t + 0.52);
         osc.stop(t + 0.45);
     }
     }
};
};
     function initAllModules() {
     function initAllModules() {
         SidebarModule.init();
         SidebarModule.init();