Mediawiki:common.js

Вернуться

Строка 2222: Строка 2222:


var AprilFoolsCubeTransitionModule = {
var AprilFoolsCubeTransitionModule = {
    FORCE_FOR_ALL: false,
    ENABLE_APRIL_FIRST: false,
    DURATION_BEFORE_TRANSITION: 6500,
     overlay: null,
     overlay: null,
     clockTimer: null,
     cube: null,
     audioContext: null,
     frontImage: null,
    rightFrame: null,
    clockAudio: null,
    lampAudio: null,
     started: false,
     started: false,
    // Сейчас false: пранк только по ?prank=1
    // Когда захочешь включить всем 1 апреля — просто поставь true
    ENABLE_FOR_ALL_ON_APRIL_FIRST: false,


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


         var params = new URLSearchParams(window.location.search);
         var url = new URL(window.location.href);
         var prankMode = params.get('prank') === '1';
         var isPreview = url.searchParams.get('prank_preview') === '1';
        if (isPreview) {
            return;
        }


        var isPrankFlag = url.searchParams.get('prank') === '1';
         var now = new Date();
         var now = new Date();
         var isAprilFirst = now.getMonth() === 3 && now.getDate() === 1;
         var isAprilFirst = now.getMonth() === 3 && now.getDate() === 1;


         var allowForAll = this.ENABLE_FOR_ALL_ON_APRIL_FIRST && isAprilFirst;
         var shouldRun = isPrankFlag || this.FORCE_FOR_ALL || (this.ENABLE_APRIL_FIRST && isAprilFirst);
        var shouldRun = prankMode || allowForAll;
         if (!shouldRun) {
         if (!shouldRun) {
             return;
             return;
        }
        // Ограничение "один раз за сессию" — только для массового режима
        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.createDom();
         this.initAudio();
         this.createAudio();
         this.startClockTick(); // стартуем сразу
         this.show();
        this.showOverlay();


         var self = this;
         var self = this;
        this.tryPlayClockNow();
        this.bindAudioUnlock();
         setTimeout(function () {
         setTimeout(function () {
             self.startExitTransition();
             self.startCubeTransition();
         }, 6000);
         }, this.DURATION_BEFORE_TRANSITION);
     },
     },


     injectStyles: function () {
     injectStyles: function () {
         if (document.getElementById('april-fools-cube-styles')) return;
         if (document.getElementById('april-cube-prank-styles')) return;


         var style = document.createElement('style');
         var style = document.createElement('style');
         style.id = 'april-fools-cube-styles';
         style.id = 'april-cube-prank-styles';
         style.textContent =
         style.textContent =
             '#april-fools-overlay{' +
             '#april-cube-prank-overlay{' +
                 'position:fixed;' +
                 'position:fixed;' +
                 'inset:0;' +
                 'inset:0;' +
                 'z-index:2147483647;' +
                 'z-index:2147483647;' +
                 'background:#000;' +
                 'background:#000;' +
                'display:flex;' +
                'align-items:center;' +
                'justify-content:center;' +
                 'opacity:0;' +
                 'opacity:0;' +
                 'visibility:hidden;' +
                 'visibility:hidden;' +
                 'overflow:hidden;' +
                 'overflow:hidden;' +
                 'transform:translateZ(0);' +
                 'transition:opacity .2s linear;' +
                'will-change:transform,opacity;' +
             '}' +
             '}' +
             '#april-fools-overlay.active{' +
             '#april-cube-prank-overlay.active{' +
                 'opacity:1;' +
                 'opacity:1;' +
                 'visibility:visible;' +
                 'visibility:visible;' +
             '}' +
             '}' +
             '#april-fools-overlay img{' +
             '#april-cube-prank-scene{' +
                 'width:100vw;' +
                 'position:absolute;' +
                 'height:100vh;' +
                'inset:0;' +
                 'object-fit:cover;' +         // ширина/высота заполняются, лишнее режется
                 'perspective:1800px;' +
                 'object-position:center top;' + // чуть приятнее для такого скрина
                 'perspective-origin:50% 50%;' +
                 'display:block;' +
                 '--cube-depth:50vw;' +
                 'user-select:none;' +
            '}' +
                 '-webkit-user-drag:none;' +
            '#april-cube-prank-cube{' +
                 'pointer-events:none;' +
                'position:absolute;' +
                 'inset:0;' +
                 'transform-style:preserve-3d;' +
                 'transform:translateZ(calc(-1 * var(--cube-depth))) rotateY(0deg);' +
                 'will-change:transform,opacity;' +
             '}' +
             '}' +
             '#april-fools-overlay.exiting{' +
             '#april-cube-prank-cube.exiting{' +
                'transform-origin:left center;' +
                 'animation:aprilCubeRotateOut 1100ms cubic-bezier(.2,.8,.2,1) forwards;' +
                 'animation:aprilCubeOut 900ms cubic-bezier(0.2,0.8,0.2,1) forwards;' +
             '}' +
             '}' +
             '@keyframes aprilCubeOut{' +
             '@keyframes aprilCubeRotateOut{' +
                 '0%{' +
                 '0%{' +
                     'transform:perspective(1400px) rotateY(0deg) translateX(0);' +
                     'transform:translateZ(calc(-1 * var(--cube-depth))) rotateY(0deg);' +
                     'opacity:1;' +
                     'opacity:1;' +
                 '}' +
                 '}' +
                 '100%{' +
                 '100%{' +
                     'transform:perspective(1400px) rotateY(-88deg) translateX(-34vw);' +
                     'transform:translateZ(calc(-1 * var(--cube-depth))) rotateY(-90deg);' +
                     'opacity:0;' +
                     'opacity:1;' +
                 '}' +
                 '}' +
            '}' +
            '.april-cube-face{' +
                'position:absolute;' +
                'inset:0;' +
                'backface-visibility:hidden;' +
                'overflow:hidden;' +
                'background:#000;' +
            '}' +
            '.april-cube-face.front{' +
                'transform:translateZ(var(--cube-depth));' +
            '}' +
            '.april-cube-face.right{' +
                'transform:rotateY(90deg) translateZ(var(--cube-depth));' +
            '}' +
            '.april-cube-face.front img{' +
                'width:100%;' +
                'height:100%;' +
                'object-fit:cover;' +
                'object-position:center center;' +
                'display:block;' +
                'user-select:none;' +
                '-webkit-user-drag:none;' +
                'pointer-events:none;' +
            '}' +
            '.april-cube-face.right iframe{' +
                'width:100%;' +
                'height:100%;' +
                'border:0;' +
                'display:block;' +
                'background:#111;' +
             '}';
             '}';
         document.head.appendChild(style);
         document.head.appendChild(style);
     },
     },


     createOverlay: function () {
     createDom: function () {
         var overlay = document.createElement('div');
         var overlay = document.createElement('div');
         overlay.id = 'april-fools-overlay';
         overlay.id = 'april-cube-prank-overlay';


         var image = document.createElement('img');
         var scene = document.createElement('div');
         image.alt = 'Wikipedia homepage';
         scene.id = 'april-cube-prank-scene';
        image.src = 'https://upload.wikimedia.org/wikipedia/commons/d/d4/2019_Screenshot_of_English_Wikipedia_homepage.png';


         overlay.appendChild(image);
         var cube = document.createElement('div');
        document.body.appendChild(overlay);
         cube.id = 'april-cube-prank-cube';
         this.overlay = overlay;
    },
 
    showOverlay: function () {
        if (!this.overlay) return;
        this.overlay.classList.add('active');
    },


    startExitTransition: function () {
        var front = document.createElement('div');
         if (!this.overlay) return;
         front.className = 'april-cube-face front';


         this.stopClockTick();
         var img = document.createElement('img');
         this.playCubeShiftSound();
         img.src = 'https://upload.wikimedia.org/wikipedia/commons/d/d4/2019_Screenshot_of_English_Wikipedia_homepage.png';
        img.alt = 'Wikipedia home';


         var self = this;
         front.appendChild(img);
        this.overlay.classList.add('exiting');
        this.overlay.addEventListener('animationend', function handleEnd() {
            self.overlay.removeEventListener('animationend', handleEnd);
            if (self.overlay && self.overlay.parentNode) {
                self.overlay.parentNode.removeChild(self.overlay);
            }
            self.overlay = null;
        });
    },


    initAudio: function () {
         var right = document.createElement('div');
         try {
         right.className = 'april-cube-face right';
            var Ctx = window.AudioContext || window.webkitAudioContext;
            if (!Ctx) return;
            this.audioContext = new Ctx();
         } catch (e) {
            this.audioContext = null;
            return;
        }


         var self = this;
         var iframe = document.createElement('iframe');
        iframe.setAttribute('loading', 'eager');
        iframe.setAttribute('referrerpolicy', 'no-referrer-when-downgrade');
        iframe.src = this.buildPreviewUrl();


         // Best-effort сразу
         right.appendChild(iframe);
        if (this.audioContext.state === 'suspended') {
            this.audioContext.resume().catch(function () {});
        }


         // Гарантия: как только пользователь взаимодействует — точно резюмим
         cube.appendChild(front);
        function unlock() {
        cube.appendChild(right);
            if (!self.audioContext) return;
        scene.appendChild(cube);
            self.audioContext.resume().catch(function () {});
        overlay.appendChild(scene);
            document.removeEventListener('pointerdown', unlock);
        document.body.appendChild(overlay);
            document.removeEventListener('keydown', unlock);
            document.removeEventListener('touchstart', unlock);
        }


         document.addEventListener('pointerdown', unlock, { once: true });
         this.overlay = overlay;
         document.addEventListener('keydown', unlock, { once: true });
         this.cube = cube;
         document.addEventListener('touchstart', unlock, { once: true });
         this.frontImage = img;
        this.rightFrame = iframe;
     },
     },


     startClockTick: function () {
     buildPreviewUrl: function () {
         if (!this.audioContext || this.clockTimer) return;
         var url = new URL(window.location.href);
        url.searchParams.delete('prank');
        url.searchParams.set('prank_preview', '1');
        return url.toString();
    },


         var self = this;
    createAudio: function () {
         this.clockAudio = new Audio('https://zvukogram.com/mp3/34/the-ticking-of-a-quartz-clock.mp3');
        this.clockAudio.loop = true;
        this.clockAudio.preload = 'auto';
        this.clockAudio.volume = 0.45;


         function safeTick() {
         this.lampAudio = new Audio('https://zvukitop.com/wp-content/uploads/2021/04/luminesc-lampa-r4fh.mp3');
            if (!self.audioContext) return;
        this.lampAudio.loop = false;
        this.lampAudio.preload = 'auto';
        this.lampAudio.volume = 0.9;
    },


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


            self.playTick();
    tryPlayClockNow: function () {
         }
         if (!this.clockAudio) return;
 
         this.clockAudio.currentTime = 0;
        safeTick();
         this.clockTimer = setInterval(safeTick, 900);
    },


    stopClockTick: function () {
        var playPromise = this.clockAudio.play();
         if (this.clockTimer) {
         if (playPromise && typeof playPromise.catch === 'function') {
             clearInterval(this.clockTimer);
             playPromise.catch(function () {});
            this.clockTimer = null;
         }
         }
     },
     },


     // Тик ближе к "механическому"
     bindAudioUnlock: function () {
    playTick: function () {
         var self = this;
         if (!this.audioContext || this.audioContext.state !== 'running') return;


         var t = this.audioContext.currentTime;
         function unlockAndPlay() {
 
            if (self.clockAudio) {
        var clickNoiseBuffer = this.audioContext.createBuffer(1, 512, this.audioContext.sampleRate);
                self.clockAudio.play().catch(function () {});
        var data = clickNoiseBuffer.getChannelData(0);
            }
        for (var i = 0; i < data.length; i++) {
            document.removeEventListener('pointerdown', unlockAndPlay);
             data[i] = (Math.random() * 2 - 1) * (1 - i / data.length);
            document.removeEventListener('keydown', unlockAndPlay);
             document.removeEventListener('touchstart', unlockAndPlay);
         }
         }


         var source = this.audioContext.createBufferSource();
         document.addEventListener('pointerdown', unlockAndPlay, { once: true });
        source.buffer = clickNoiseBuffer;
         document.addEventListener('keydown', unlockAndPlay, { once: true });
 
         document.addEventListener('touchstart', unlockAndPlay, { once: true });
        var hp = this.audioContext.createBiquadFilter();
        hp.type = 'highpass';
         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.exponentialRampToValueAtTime(0.09, t + 0.002);
        gain.gain.exponentialRampToValueAtTime(0.0001, t + 0.05);
 
        source.connect(hp);
        hp.connect(bp);
        bp.connect(gain);
        gain.connect(this.audioContext.destination);
 
        source.start(t);
        source.stop(t + 0.06);
     },
     },


     // "Сдвиг куба": whoosh + низкочастотный удар
     startCubeTransition: function () {
    playCubeShiftSound: function () {
         if (!this.cube || !this.overlay) return;
         if (!this.audioContext || this.audioContext.state !== 'running') return;


         var t = this.audioContext.currentTime;
         if (this.clockAudio) {
            this.clockAudio.pause();
            this.clockAudio.currentTime = 0;
        }


         // Whoosh (шумовой слой)
         if (this.lampAudio) {
        var duration = 0.55;
            this.lampAudio.currentTime = 0;
        var bufferSize = Math.floor(this.audioContext.sampleRate * duration);
            this.lampAudio.play().catch(function () {});
        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();
         var self = this;
        noise.buffer = noiseBuffer;
        this.cube.classList.add('exiting');


         var noiseFilter = this.audioContext.createBiquadFilter();
         this.cube.addEventListener('animationend', function handleEnd() {
        noiseFilter.type = 'bandpass';
            self.cube.removeEventListener('animationend', handleEnd);
        noiseFilter.frequency.setValueAtTime(1200, t);
        noiseFilter.frequency.exponentialRampToValueAtTime(320, t + duration);
        noiseFilter.Q.value = 0.9;


        var noiseGain = this.audioContext.createGain();
            if (self.overlay && self.overlay.parentNode) {
        noiseGain.gain.setValueAtTime(0.0001, t);
                self.overlay.parentNode.removeChild(self.overlay);
        noiseGain.gain.exponentialRampToValueAtTime(0.22, t + 0.03);
            }
        noiseGain.gain.exponentialRampToValueAtTime(0.0001, t + duration);


        noise.connect(noiseFilter);
            if (self.lampAudio) {
        noiseFilter.connect(noiseGain);
                self.lampAudio.pause();
        noiseGain.connect(this.audioContext.destination);
                self.lampAudio.currentTime = 0;
            }


        noise.start(t);
            self.overlay = null;
        noise.stop(t + duration);
            self.cube = null;
 
            self.frontImage = null;
        // Низкий "thud"
            self.rightFrame = null;
        var osc = this.audioContext.createOscillator();
         });
        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(oscGain);
        oscGain.connect(this.audioContext.destination);
 
        osc.start(t + 0.1);
        osc.stop(t + 0.45);
     }
     }
};
};